Skip to content

Releases: nafphp/board

v0.1.9 — Ticket timer dialog and serialized account changes

Choose a tag to compare

@FloKnapp FloKnapp released this 24 Oct 12:31
e225646

The ticket view keeps title, description and sidebar editors in place, aligns status and assignee controls and stacks the sidebar on narrow screens. A compact stopwatch opens a dedicated timer dialog with explicit start, pause, resume and minute booking. It shows carried seconds and warns when another ticket is running. Booking refreshes the ticket version before later edits; closing or redrawing the dialog preserves the running display until a server response replaces it.

Timer actions serialize on the person's row and read current timer rows after that lock, preventing concurrent starts across projects from leaving two clocks running. Remembered sign-ins also serialize with password, email and account-state changes: the validator is re-read under the account lock and rotation/session publication complete before releasing it. Concurrent callers cannot both spend one remembered credential. Sign-in events retain their post-commit timing.

HTTP regression tests cover both valid rejection paths for a revoked confirmation request: the native CSRF guard can return 400 after session restoration clears the token, while a request that passed that guard is refused with 401 by the account recheck. Both paths verify one credential change, one audit entry, code consumption and revoked sessions.

Upgrade: update naf/board to v0.1.9, run vendor/bin/naf nafinity:assets:publish, and restart application and worker services using the installation's normal backup and readiness procedure. No schema migration or new package dependency is introduced.

Validation: 868 PHP tests / 6,956 assertions on local PHP 8.5 with MariaDB, including HTTP and database interleaving regressions; 154 browser-module tests; strict Composer validation, PHP style and Prettier checks passed. The exact release commit passed the PHP 8.3/8.5 matrix on MariaDB 11.4 and MySQL 8.4, including IMAP integration.

v0.1.8 — Compact follow eye and personal followed tickets

Choose a tag to compare

@FloKnapp FloKnapp released this 13 Oct 20:41
136fba3

Following a ticket now uses a compact eye beside its reference and status. The eye is muted when inactive and highlighted when following, with an accessible action label and pressed state. JavaScript updates it in place, preserving the open ticket drawer, unsaved edits and keyboard focus. Native forms continue to work without JavaScript, including for read-only project members.

Followed tickets sits beside My tickets in workspace navigation. This personal overview spans currently readable, active projects, with open/closed/all status, due-date or last-change sorting and pages of 50 tickets. It selects only the signed-in account’s subscriptions and excludes archived tickets, archived projects and projects whose access has been revoked. Assignments and notification preferences retain their existing behavior.

Upgrade

Update naf/board to v0.1.8, publish assets with nafinity:assets:publish, and restart application and worker services using the installation’s normal backup and readiness procedure. This release adds no migration or new package requirement and does not change role permissions.

Validation passed for the exact tagged merge commit on PHP 8.3 and 8.5 with MariaDB 11.4 and MySQL 8.4, including IMAP integration, strict Composer validation, PHP/asset formatting and browser-module tests. The package has 859 PHP tests and 152 browser-module tests. HTTP regressions cover personal isolation, revoked access, archives, pagination, invalid filters, guests, CSRF and native forms. Browser checks cover the eye, retained drafts, keyboard focus, the overview and responsive layouts.

v0.1.7 — Compact board filters and Advanced DQL

Choose a tag to compare

@FloKnapp FloKnapp released this 13 Oct 19:16
a092598

Board filters now live in a compact Filter disclosure beside Views, keeping the tickets in view. The existing dropdowns remain the simple mode. Advanced translates their values into Nafinity DQL; switching back fills the dropdowns and retains conditions they cannot represent as visible additional DQL. Queries are never silently widened when changing modes.

DQL supports ticket status, board identifier, assignee, column, swimlane, label, priority, title/text, ticket number and dates, with comparisons, lists, boolean operators and parentheses. For example, status = open AND assignee = me selects the reader’s open assignments. Shared me queries continue to mean the person opening the link. Expressions always stay inside the currently authorized project; cards, counts and live updates use the same bound query conditions.

Invalid syntax, unknown properties and invalid values return an editable HTTP 422 error. Query size, nesting, tokens and list sizes are bounded. Dropdown conversion and filtering also work through native GET forms without JavaScript. The controls include labels, keyboard focus handling, announced errors, responsive layouts and expandable query help.

Saved views move into the shared dialog and preserve dropdown filters, DQL and contributed filters in shareable authorized links. Extensions can add DQL properties through the existing BoardFilterDefinition registry and compact toolbar actions through board.filters.actions; structured or non-representable extension filters are preserved.

Upgrade

Update naf/board to v0.1.7, publish assets with nafinity:assets:publish, and restart application/worker services. Follow the installation’s normal backup and readiness checks. This release adds no database migration or new package requirement; existing dropdown links and personal saved views remain supported.

Validation passed for the exact tagged merge commit on PHP 8.3 and 8.5 with MariaDB 11.4 and MySQL 8.4, including IMAP integration, strict Composer validation, PHP/asset formatting and browser-module tests. The package has 856 PHP tests and 152 browser-module tests.

v0.1.6 — Accessible personal workflows, shared configuration and native mail replies

Choose a tag to compare

@FloKnapp FloKnapp released this 12 Oct 20:30
a5353ca

Nafinity now makes personal ticket work easier to find and operate. Save up to 20 named filter views per person and project, share ordinary authorized filter links, and open My tickets for current assignments across readable projects with status/sort choices and pagination. Workspace search groups matching projects and tickets, and linked-ticket search supports descriptive relationships with their inverses.

Board cards have clearer label contrast, larger action targets and readable screen-reader metadata for priorities, statuses, due dates and full assignee names. Keyboard navigation includes a skip link, labelled choice lists and focus restoration. Ticket creation, inline editing and discussion share the same drawer/page components, preserve unsaved drafts and allow personal sidebar visibility. Settings dialogs share stacked navigation, cancellation guards and reduced-motion behavior.

Notifications retain all-project changes by default. An optional relevance mode limits them to created, assigned, newly mentioned and personally followed tickets. Readers can follow tickets without editing them. Queued email checks current involvement, membership, notification preferences and project muting before delivery. Pre-upgrade in-app notifications have no relevance classification and remain available in all-changes mode.

Project invitations use expiring, one-use links so recipients choose their own credentials. Account administration adds bounded searchable tables, authorized lazy editors, transactional global-role updates and one-use local password recovery. Project management includes owner-confirmed deletion. Bulk properties are explicitly registered domain operations, preserving authorization and all-or-nothing transactions.

Administrators can manage shared branding and landing-page identity, publish custom interface themes and design outgoing email with isolated previews. Native email views share branding, CSS inlining and text alternatives. Installation overrides retain typed validation, source information, stale-write protection and encrypted private-file storage. Theme and preview handling includes Firefox-compatible isolated documents and the bundled symbols used by the interface.

Settled time now has a per-person journal, with personal PDF/CSV/text/JSON reports and invoice-position exports. Built-in adapters prepare invoice drafts for Lexware Office, sevdesk, easybill and FastBill using encrypted accounts, immutable previews and durable booking claims. Ambiguous external results require reconciliation instead of automatic resubmission; no adapter finalizes or sends an invoice. Existing export writers remain ticket-only by default and can opt into additional sources through the exporter registry.

Native Mail-to-Ticket polls a configured IMAP mailbox and appends authorized replies to existing tickets. A random public reference and the current requester address must match. Imports are deduplicated, and flags change only after the comment commits. Malformed or spoof-conflicting messages are skipped without blocking subsequent replies. SMTP and IMAP share host mail configuration; incoming mail does not create new tickets and does not import attachments.

An optional, separately installable Composer example demonstrates enforced column WIP limits through native settings and transactional change listeners. Refused admissions roll back ticket, audit, notification and queue changes. The core continues to use advisory WIP limits unless the example plugin is installed.

Upgrade

Back up the database and private storage, update Composer dependencies, run db:migrate up and rbac:sync, publish assets with nafinity:assets:publish, and restart long-running workers/services. The release requires PHP 8.3 or newer with sodium, naf/framework 0.2.10 or newer and naf/mail 0.2.3 or newer. Keep the private configuration files and their encryption key together in backups. Direct invoice drafts need a separate host export encryption key; IMAP remains disabled unless configured.

Apply the new migrations before serving the updated code. Existing ticket links remain symmetric relates to links. The time journal starts at migration; historical ticket totals and manual totals are not backfilled. Existing explicit theme choices are preserved.

Validation passed for the exact tagged merge commit on PHP 8.3 and 8.5 with MariaDB 11.4 and MySQL 8.4, including the IMAP integration suite, strict Composer validation and formatting checks. The package has 816 PHP tests and 146 browser-module tests. Browser acceptance covered personal-view persistence, pagination, subscription/preferences, accessibility-tree labels and layouts down to 320 CSS pixels. External invoice-provider accounts were not contacted by automated tests.

v0.1.5 — Faster cell rebalancing and MySQL compatibility

Choose a tag to compare

@FloKnapp FloKnapp released this 06 Oct 19:25
01bd9b0

Dense card positions now rebalance in bounded batches while preserving the requested order and unique position constraint. Previously, a large cell needed two SQL updates per card, and an existing negative position could collide with the temporary pass. The new pass reserves positions absent from both old and final sets, including the signed integer boundary, before writing batches of at most 250 cards.

MySQL installations can migrate roles: Board's historical role import now quotes the reserved system column and requires the published naf/rbac 0.1.3 correction. Stored roles, grants and the schema remain unchanged; no additional migration is required.

Classic dark and system-dark primary actions now use a darker foreground, improving their rendered contrast from 2.77:1 to 5.87:1. Anthracite retains its own colors.

PHP 8.5.10 passed 540 tests with 1,771 assertions on both MariaDB 11.4 and MySQL 8.4.11. All 38 browser-module tests, PHP/JavaScript formatting and strict Composer validation passed. CI also passed on PHP 8.3 and 8.5 with both supported databases for the tagged merge commit. The actual move/version-conflict workflow remains unchanged.

v0.1.4 — Independent tests, account appearance and complete core English UI

Choose a tag to compare

@FloKnapp FloKnapp released this 06 Oct 18:29
b2a331a

Nafinity now ships an independent package test host and CI on PHP 8.3 and 8.5. Database access remains lazy during bootstrap, board responses contain bounded card data, and HTTP behavior is handled by dedicated controllers.

Accounts can choose the Classic or Anthracite palette and save brightness independently. English now covers the core interface, validation, activity vocabulary, AI tool titles and browser messages. Bilingual action keywords correct a German keyword-fallback omission while retaining authorized read prerequisites. Extension cleanup runs once even when an asynchronous mount finishes after its drawer has closed.

The release passed 538 PHP tests with 1,767 assertions, 38 JavaScript tests, both formatting checks, and the package CI. Browser acceptance covers drawer lifecycle and drafts, keyboard/mobile moves, reload persistence, and account appearance through sign-out/sign-in. Reproducible query and authorized AI-selection measurements accompany the package. Native mouse drag acceptance in Chrome/Firefox remains explicitly unverified because the desktop input tool could not target their windows.

Requires naf/framework ^0.2.8. Apply migrations, sync declared roles, republish assets and restart background workers when upgrading. Existing accounts receive Classic as the palette default; their stored brightness is retained.

v0.1.3 — Package-owned extension order and scoped exports

Choose a tag to compare

@FloKnapp FloKnapp released this 22 Sep 21:21
802c28a

Board extensions now declare extra.naf.boot.before: ["naf/board"] in their own manifests. Board declares its bootstrap prerequisites and finalizes extension providers during its own boot, after those plugins have registered their contributions. The Nafinity host no longer needs a generated plugin-order file. Host overrides and routes still apply last.

Ticket exports now live in board and installation settings. They can select one board or all permitted active boards, combine filtered CSV/JSON output, and preserve each board's export and field permissions. The existing export API remains compatible.

This release also addresses the prepared reliability fixes: authorization and conflict checks use current state, live updates are queued transactionally, readiness inspects every registered migration and the configured attachment storage, and inline values remain accessible. It requires framework 0.2.7 and database 0.2.4. Board CI passed on PHP 8.3 and 8.5; the Nafinity integration suite exercised installed extensions, HTTP, databases and worker behavior.

v0.1.2 — Housekeeping, checks that run themselves, and MIT

Choose a tag to compare

@FloKnapp FloKnapp released this 21 Sep 21:20
15b1f92

Housekeeping, checks that run themselves, and a licence that matches where the code lives.

Two kinds of row that accumulate, with opposite defaults

An expired remember-me token does not work any more; it is only taking up room. Nothing is decided about it, so nothing needs configuring — over is over, and the maintenance job drops them.

A history is the other case. How long to keep one is a policy somebody answers for, so the installation states it: audit_retention_days, zero by default, and zero means forever. A log that quietly shortened itself would be worse than none at all, which is why the safeguard is the default rather than a warning.

Entries are deleted in batches of a thousand. The first time a year-old log is shortened is the only moment that matters, and one statement over a great many rows would take one lock on a table every page writes to. PostgreSQL has no LIMIT on DELETE and names the rows through a subquery instead.

An index on activities.created_at comes with it. The history has always been read newest first and paged by id, so nothing needed one; everything before a moment is a different question, and without the index a job running every minute would read the whole table to answer it.

Checks that run themselves

This package now checks every push: that the manifest is valid, that every PHP file and every template parses, and that the browser modules pass.

Not the whole suite, and the workflow says so. The database tests want MariaDB and PostgreSQL, the HTTP tests want a server answering over HTTPS, and the extension tests build throwaway hosts to install the examples into. That environment lives in the skeleton with its Compose file, and make test there runs all of it — 110 unit, 214 database on both engines, 3 worker, 158 HTTP, 49 JavaScript and 130 across four extension-host configurations, which is what this release was verified with.

naf/rbac and naf/websocket gained workflows too, and theirs do run their full suites.

MIT

The manifest said proprietary while this repository was private, which was consistent. It is public now and on Packagist, so that line had stopped describing anything: it told whoever installed the package that they may not use it, which is not a protection but an invitation to misread.

MIT, like the rest of the packages here. AGPL was considered and rejected — a plugin runs in this process and would arguably become a derivative work, which for an application built around plugins would poison the thing it is for.

Requires

naf/framework ^0.2.6, naf/rbac ^0.1.1. Unchanged from v0.1.1; nothing here needs a newer anything.

v0.1.1 — Events named by their class, and a sign-in that outlives its session

Choose a tag to compare

@FloKnapp FloKnapp released this 21 Sep 20:43
3aebbef

Events are the classes they carry, three more of them exist, and a sign-in can outlive its session.

Events are named by their class

// before
event()->dispatch('nafinity.changed', new Change(…));
event()->listen('nafinity.changed', fn(Change $c) => …);

// now
event()->dispatch(new Change(…));
event()->listen(Change::class, fn(Change $c) => …);

No class was written for this. Change, GrantsChanged and ExportLine already existed and were being announced under a string that repeated their name less precisely. ExportService::LINE is gone with them: a constant holding a second way to name one thing is a second way to get it wrong.

A misspelled event is now an error where it is written, rather than a listener that never runs and never says so — and finding every listener of an event is a question an IDE can answer.

Upgrading. A listener on 'nafinity.changed', 'rbac.granted' or 'export.line' stops hearing it. The two are separate keys and nothing warns you. Change them to Change::class, GrantsChanged::class and ExportLine::class in the same deployment.

Two spellings of rbac.granted deliberately did not move: the stored change type in the audit log and the activity vocabulary. Those are strings in rows that already exist.

Three events where a plugin could not stand

SignIn fires from the one method that decides either outcome, for the password provider and the directory one alike. Signing in writes no row, so the change event structurally could not carry it, and a plugin wanting to notice a sign-in from somewhere new, lock an account after a run of failures, or keep its own record had nowhere to stand.

Both outcomes travel on it, because the interesting one is usually the failure, and it carries the address as typed even when no account answers to it. It is announced after its transaction rather than inside it: by then the session is published and the person is in, and a listener rolling that back would leave them signed in with no record of it.

ExportStarted and ExportFinished frame the records. A package writing into somebody else's system opens something before the first and closes it after the last, and the count on the closing event is a number the export already knows.

The log knows who came in, and who tried

It knew when somebody changed their password and when they moved their address. It did not know that anybody had signed in — which in a system whose promise is accountability is the entry people come looking for first, and the one an intrusion is noticed by.

A refused attempt is recorded with no actor, and that is not a gap to be filled: naming somebody would mean asserting who they were on the strength of a password that did not match. The address as typed is kept instead, whether or not any account answers to it.

Both kinds sit behind audit.personal, beside the password and address entries they belong with. Change::$actorId is nullable to allow it; the column it is written to always was.

Staying signed in

A session cookie ends when the browser does, which for a board people live in all week is the wrong promise. A remember-me cookie is kept for ninety days — configurable through nafinity:remember_days — and is worth nothing the moment anything says it should be.

A selector to find the row by, a validator to prove it with, and only the validator's hash at rest, so a leak of that table is a list of names rather than a key ring. Every use rotates the validator and keeps the selector, and that asymmetry is the whole of the theft detection: a stale cookie still arrives at its row and fails there. When it does, every token of the account goes, because which of the two holders is the owner cannot be told from here.

And the promise this would otherwise have broken quietly: the account's security version is copied in at issue and compared at use, so changing a password still signs you out everywhere — including out of a cookie issued before it.

The box on the sign-in form is unchecked by default. Staying signed in is a choice made on the device somebody is sitting at.

Smaller things

The seven helpers in src/functions.php lose their if (!function_exists()) guards. Plugin files are loaded with require_once, so nothing could reach them twice.

The extension reference gains the Events chapter it never had, including what was true and written down nowhere: a listener on Change runs inside the transaction, so throwing refuses the write. That is how a rule no permission can express gets to stop something.

Requires

naf/framework ^0.2.6 for the object form of dispatch(), and naf/rbac ^0.1.1, which announces its own event the same way.

Verification

110 unit, 209 database on MariaDB and PostgreSQL, 3 worker, 158 HTTP — including the round trip a browser makes: sign in, lose the session cookie, come back with the other one — 49 JavaScript, 130 across four extension-host configurations.

v0.1.0 — Project-isolated Kanban boards, as a plugin

Choose a tag to compare

@FloKnapp FloKnapp released this 21 Sep 19:16
5cee1c2

Project-isolated Kanban boards, as a NAF plugin. The application behind a Nafinity installation.

The first release. It is a plugin rather than an application on purpose: the skeleton you install is yours to edit, and everything the product does lives here, where an upgrade can replace it.

Boards that cannot see each other

Every table carries project_id, and every foreign key is composite. A ticket references its column and its project, so a row cannot point across a boundary even by mistake — the schema refuses it rather than a query remembering to filter. Somebody who is not a member gets a 404 that does not say whether the project exists.

Membership decides whether you reach a board. What you may do there comes from naf/rbac, scoped to that board, which is how an administrator's rights apply inside a project they were never added to.

What a plugin can do to it

Sixteen registries for what exists, three events for what is happening, and no third mechanism. Not a hook manager beside an event system beside a filter chain.

A plugin adds ticket fields, field types, priorities, board filters with their own SQL, estimation scales, activity types, export formats, permissions that appear in the role editor, settings, navigation, slot contributions, and replacements for any view or core service. Contributed ticket fields are stored as ticket metadata, typed, permission-guarded, and inert when the plugin is gone: values of an absent package stay where they are and the application can name them.

Events are nafinity.changed for anything written, rbac.granted, and export.line. The first is dispatched inside the transaction that did the work, so a listener that throws refuses the write — which is how a rule no permission can express gets to stop something.

Two example extensions ship with the package and are booted by the test suite, once with them installed and once without.

Exporting

A board can be written as CSV or JSON, and a plugin can add a third format with one registration. The rows are built once, in one service, so every format sees the same board; export.line fires per record, and a listener may change what is reported without the ticket moving — ExportLine::$ticket is readonly and PHP enforces it.

Columns are the registered ticket fields, so a plugin's own field is in every export without the export having heard of it, with that field's read permission still deciding.

A complete audit trail

Every change to a project or the installation is recorded with its actor, its scope and what moved — settings, roles, grants, accounts, and each of the twenty-four things that can happen to a ticket. Reading it needs its own permission; personal account events need a second one. Deleting a user nulls the author rather than the entry, and the log says "deleted user".

Live updates

With naf/websocket installed, a board rearranges itself as other people work: the socket carries that something changed and never what, and the page fetches the new state through the ordinary path. A single card arrives on its own rather than the board reloading. Presence shows who else is in the project and what they have open, per person rather than per connection, and switching it off takes the channel out of the token rather than hiding a list.

Also in it

Comments with mentions, attachments on named storage disks, time tracking, ticket links and transfers between boards, swimlanes, WIP limits, estimation scales, saved filters, notification mail through the queue, LDAP and OAuth sign-in, German and English throughout, and optional tools for a local model.

Verification

98 unit, 195 database on MariaDB and PostgreSQL, 3 worker, 155 HTTP against a real server over HTTPS, 49 JavaScript, 128 across four extension-host configurations. PER Coding Style 3.0.

Requires

PHP 8.3 with pdo, mbstring, fileinfo and dom. Twenty NAF packages, including naf/rbac ^0.1.0; naf/websocket is optional and only needed for live updates. See naf/nafinity for the installation skeleton and docs/Extensibility.md for the extension reference.