Compare commits

...
900 Commits
Author SHA1 Message Date
james.maes 787a227602 Merge pull request 'fix(preferences): an action that answers with an address opens it; a section says how a connection ended' (#214) from fix/process-link-and-preference-redirect into main
build / maven (push) Successful in 10m17s
maven / build (push) Successful in 10m17s
2026-11-11 12:38:10 +00:00
james.maesandClaude Fable 5.2 30f4f9d586 fix(preferences): an action that answers with an address opens it; a section says how a connection ended
build / maven (push) Successful in 5m2s
maven / build (push) Successful in 5m2s
A preference action that starts an OAuth connection (Connect Slack)
answers with the consent page's address in the action result's message,
because the UI API has no redirect field. The address was the text of a
success toast: long, not clickable and gone in seconds.

- A message that is nothing but one absolute https address is where the
  browser goes, in this tab, and is never shown (continuationUrl,
  leaveTo). A message that only mentions an address is still a message.
  Plain http, credentials in the address and other schemes are not
  followed.
- A flow that returns to /app/preferences?section=<name>&result=<code>
  or &error=<code> gets its outcome shown once above the section's form,
  as the section's label and the code as words ("Slack: connected.",
  "Slack: authorization denied."), as a status or an alert, and taken
  off the address. Only a fixed lowercase code is read, never a sentence.
- Links in an HTML process component use the link color and an
  underline; they looked like the text around them. A link's own inline
  style still wins.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 06:31:38 -06:00
james.maes 24bbe4a038 Merge pull request 'fix(errors): the error banner says what failed and no longer stacks' (#210) from fix/error-banner-names-cause into main
build / maven (push) Successful in 5m7s
maven / build (push) Successful in 5m7s
2026-11-11 00:58:17 +00:00
james.maes 8903f272c4 Merge pull request 'fix(header): the Ask button's activity dot and unread badge sit on its icon' (#213) from fix/ask-indicator-anchor into main
build / maven (push) Successful in 8m37s
maven / build (push) Successful in 8m37s
2026-11-11 00:58:15 +00:00
james.maesandClaude Fable 5.2 2345039911 fix(header): the Ask button's activity dot and unread badge sit on its icon
build / maven (push) Successful in 14m44s
maven / build (push) Successful in 14m45s
The dot shown while a reply is being written was positioned against the
button (absolute bottom-0 right-0). The button's box is its hit area: 10 px
of padding around the icon and, from 1440 px, the "Ask" label, with no
visible edge at rest. The dot therefore landed past the label and under its
baseline, detached from the icon; the unread badge landed over the end of
the label.

Header indicators are now positioned against the icon: HeaderIconAnchor is
a box exactly the size of the icon, with HEADER_BADGE_CLASS (top-right) and
HEADER_STATUS_DOT_CLASS (bottom-right). Ask, the bell and Jobs share it; on
the 40 px icon-only buttons the badge keeps its pixels (2 px inside the
button's top-right corner).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 18:40:45 -06:00
james.maesandClaude Fable 5.2 4347924658 docs(releases): note the 403 fallback and the silent cases of the error banner
build / maven (push) Successful in 4m13s
maven / build (push) Successful in 4m13s
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 11:39:00 -06:00
james.maesandClaude Fable 5.2 523f59cf38 fix(errors): the error banner says what failed and no longer stacks
build / maven (push) Successful in 4m33s
maven / build (push) Successful in 4m33s
The global query/mutation error handler showed "Something went wrong" for
every failure without an expected status, and a polling query that kept
failing added one banner per attempt.

- No response: "No response from the server" (or "The server took too long
  to respond" for a timeout), with the request below it as METHOD /path:
  no query string, and ids shown as {id}.
- Unexpected status: the server's own message, else "Request failed (HTTP 409)".
- An error raised by the application shows its own sentence; a runtime error
  shows only its type.
- The banner's sonner id is its text plus the request, so a repeat updates
  the banner on screen. A lost connection has one banner for every request.
- 401, cancelled requests and aborted fetches stay silent. 403 falls back to
  "Permission denied" instead of the axios transport text.
- Each banner logs status, axios code, error name and request to the console,
  never a message, body, header or query string.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 11:29:49 -06:00
james.maes fefd5be0d4 Merge pull request 'fix(auth): every sign-in forces re-authentication (prompt=login, max_age=0)' (#209) from fix/signin-always-asks-credentials into main
build / maven (push) Successful in 4m22s
maven / build (push) Successful in 4m22s
2026-11-09 20:59:19 +00:00
james.maesandClaude Sonnet 5.5 c0e5b285db fix(auth): every sign-in forces re-authentication (prompt=login, max_age=0)
build / maven (push) Successful in 7m37s
maven / build (push) Successful in 7m37s
Sign out already ends the provider session (RP-initiated logout); the authorize
request now also sends prompt=login and max_age=0 so a lingering provider
session can never sign the next user in silently. prompt=login alone was seen
reusing retained SSO on real Authentik.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 14:43:02 -06:00
james.maes 42b509f35c Merge pull request 'ci: use shared ci-workflows Maven workflow' (#208) from ci/ci-workflows into main
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 7s
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (push) Successful in 29m53s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (push) Successful in 16s
QRun-IO/ci-policy: ci / node-playwright-11 (push) Successful in 30m15s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 6s
build / maven (push) Successful in 13m3s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 539bec410f7926fec3ef7fabe51934a91e0248be] (push) Successful in 9s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (push) Successful in 27s
QRun-IO/ci-policy: Shared Node CI with Playwright / node (push) Successful in 12m7s
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
maven / build (push) Successful in 13m3s
2026-11-06 21:50:54 +00:00
james.maesandClaude Opus 5.5 37c4234324 ci: pin v1, build the Next export before Maven, drop the old release caller
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 539bec410f7926fec3ef7fabe51934a91e0248be] (pull_request) Successful in 6s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
build / maven (push) Successful in 10m42s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 10s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 4s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (pull_request) Successful in 26m38s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (pull_request) Successful in 23s
maven / build (push) Successful in 10m42s
QRun-IO/ci-policy: Shared Node CI with Playwright / node (pull_request) Successful in 12m17s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (pull_request) Successful in 18s
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Successful in 27m2s
Every push verifies (pnpm typecheck/lint/test/build:export, then Maven);
vX.Y.Z and vX.Y.Z-RC.N tags publish to the Gitea registry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 21:38:06 +00:00
james.maesandClaude Opus 5.5 412d884402 ci: use shared ci-workflows Maven workflow
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow d20a578c7d818bc7ca32a2ac2c675da4e4f27385] (pull_request) Successful in 9s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
build / maven (push) Failing after 43s
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 4s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 7s
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (pull_request) Failing after 23m41s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (pull_request) Successful in 27s
maven / build (push) Failing after 43s
QRun-IO/ci-policy: Shared Node CI with Playwright / node (pull_request) Successful in 11m58s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (pull_request) Failing after 12s
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Failing after 28m35s
Replace the platform-ci release callers with one caller of
KofTwentyTwo/ci-workflows maven.yaml (qqq-orb port).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:15:45 +00:00
james.maes 27e8754f32 Merge pull request 'Preserve workflow, presence and dependency updates in the canonical Next release' (#207) from kof22/next-workflow-assets-on-pr206 into main
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 5s
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (push) Successful in 21m37s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (push) Successful in 8s
QRun-IO/ci-policy: ci / node-playwright-11 (push) Successful in 21m49s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 4s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 5e6bfd71ee8a5674882f7680c695a0ada599622f] (push) Successful in 5s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (push) Successful in 36s
QRun-IO/ci-policy: Shared Node CI with Playwright / node (push) Successful in 11m12s
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
2026-11-05 17:22:27 +00:00
james.maesandClaude Sonnet 5.5 00c00b0e71 Merge owner PR206 46e9f121 into the PR207 branch: scoped record filter control lookups
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 5e6bfd71ee8a5674882f7680c695a0ada599622f] (pull_request) Successful in 5s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 4s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 7s
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (pull_request) Successful in 49s
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (pull_request) Successful in 23m10s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / node (pull_request) Successful in 11m59s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (pull_request) Successful in 9s
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Successful in 23m21s
Takes the owner's test-only fix (src/components/query/RecordQuery.test.tsx scopes role queries with within(); docs/QBIT-RELEASE-STATUS.md)
for the 'confirms clearing filters and preserves the selected sort' case that timed out at the default 5000 ms on the loaded CI runner
(5150/5239 ms in CI 4993 node, twice; 1.3-1.5 s unloaded locally with the fix vs 1.5-1.7 s without). No assertion, user-semantic or
sort behavior changes; no f49, Tools, owner 205/206, zod 4 or lockfile content touched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 10:46:00 -06:00
james.maesandClaude Sonnet 5.5 3a72c8b88a fix(authentik): give the access editor's inherited-permissions schema its zod 4 key type
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 8s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 6s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 3d75c629c2706be133505268b6696fe9b1ae420c] (pull_request) Successful in 7s
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (pull_request) Successful in 21m26s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (pull_request) Successful in 36s
QRun-IO/ci-policy: Shared Node CI with Playwright / node (pull_request) Failing after 12m36s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (pull_request) Failing after 16s
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Failing after 12m57s
The owner's access editor (PR205/206) was written against zod 3's single-argument z.record(value). With zod ^4.6.7 (the reviewed f49
dependency set) z.record needs the key schema too: TS2554 at (32,16), then 'inherited' infers Record<..., unknown> giving TS18046 at (84,147)
and TS2322 at (103,128) (CI 4726, node 75004 and e2e 75005, the only three type errors). Use z.record(z.string(), z.array(z.string())), the
same validation and the exact Record<string, string[]> type the component already declares; no cast, no behavior or permission change.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 08:29:27 -06:00
James MaesandCodex 46e9f1212c test: scope record filter control lookups
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 7s
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 13s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow cf802461ca4ebfcf5c500440715cb333d92ea606] (pull_request) Successful in 12s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (pull_request) Successful in 24m36s
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (pull_request) Successful in 1m32s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / node (pull_request) Successful in 13m0s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (pull_request) Successful in 22s
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Successful in 1h18m32s
Preserve all filter, sorting, confirmation and accessibility assertions while avoiding repeated whole-page role scans. Focused RecordQuery tests and lint pass; release CI remains required.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
2026-11-05 07:22:21 -06:00
James MaesandClaude Sonnet 5.5 bfcc008ca2 test: carry the reviewed f49 test-only fixes that keep upstream coverage equivalent
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 7s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 60e5a80ed7a5a67877bfb3e129b1929c83464b6e] (pull_request) Successful in 13s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 10s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (pull_request) Failing after 2m20s
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (pull_request) Successful in 1m11s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / node (pull_request) Failing after 2m13s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (pull_request) Failing after 17s
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Failing after 1h10m50s
WidgetRenderer lazy-chunk wait, the mark-all-mentions-read My Work test (dce92cfb) and the help-results wait before
search navigation (8d372142), taken from f49. Owner-side test changes in PR205/206 (keyboard shortcuts, security headers
with blob: connect-src) are kept as they are newer.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 07:02:39 -06:00
James MaesandClaude Sonnet 5.5 fdf12d3c40 fix(deps,test): zod 4 migration and portable standalone tests from the reviewed f49 commit 0c156b22
The schema, form-adjuster, possible-values, assistant-normalize and zod-from-metadata changes zod 4 needs, plus the
existing Windows-portable standalone acceptance-stage and the two dev/write page test adjustments. Taken from f49;
on these files PR206 differed from f49 only by the zod 3 forms.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 07:01:30 -06:00
James MaesandClaude Sonnet 5.5 04ab9ad6c9 fix(deps): apply the reviewed f49 dependency advisory updates and zod 4 to the 206 line
next 16.4.1 -> 16.4.3, dompurify ^3.4.17, storybook 10.6.2, vitest/coverage/ui ^4.1.12, vite 7.3.7, zod ^4.6.7 and the
pnpm overrides (follow-redirects, undici@>=7 <8, fast-uri@>=3 <4) exactly as in the reviewed f49de335 package.json
dependency sections and pnpm-lock.yaml. PR206 carried no dependency change of its own beyond f49's (codemirror,
pdfjs-dist, typescript-editor are identical), so the f49 lockfile is used as is; `pnpm install --lockfile-only
--frozen-lockfile` (pnpm 9.15.9, Node 22.20.0) accepts it unchanged. Scripts, version RC.38 and the caller are untouched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 07:01:29 -06:00
James MaesandClaude Sonnet 5.5 70618bd143 build: pin the workflow bundle lock to the component main merge ede4eba
Component PR 5 merged normally as ede4eba875ca17c5921ad4caf61a08bdff6f794d; its tree 9efc0921... equals the qualified
source tree, so the qualified bundle and map (sha256 unchanged) are the canonical main output; no rebuild.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 06:57:56 -06:00
james.maesandCodex aaa77d7e6e fix: retain Auth0 display profile with canonical presence identity
Keep the backend user ID without dropping provider display fields on sign-in or reload. Do not reuse a different user's cached profile. Reproduced callback/reload failures; full 3664-test suite, TypeScript and lint pass. Real mobile flow assertions pass locally, with Windows export diagnostics tracked separately; Linux acceptance remains required.

Co-Authored-By: Codex <noreply@openai.com>
2026-11-05 06:57:31 -06:00
James MaesandClaude Sonnet 5.5 73b1ea16d7 feat(collaboration): use server-issued presence leases in the hook (port of published RC.37 de5c1bd0)
The current line has the lease API client (renew/open/beacon with sequence) but the hook still generated a client
connection id and never opened or ordered a lease. Port the hook, auth-storage, realtime invalidation, header and
mock changes of de5c1bd0 from the published v1.0.0-RC.37, so RC.38 does not regress against qbit-collaboration's
issued-lease protocol. CI, version and acceptance-stage conflicts resolved to the current line (no version or
dependency change); the people-tasks beacon test keeps the current line's equivalent assertion.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 06:57:30 -06:00
James MaesandClaude Sonnet 5.5 94726bb52c build: package the qualified precompiled QSequentialWorkflow bundle as tracked Next library assets
Track the component bundle and map compiled from the reviewed component source e6036d2 (Node 22.20.0, npm ci
--ignore-scripts, existing 3 suites passed, webpack) in dynamic-components/q-sequential-workflow/ with a sha256 lock
(scripts/dynamic-components.lock.json: source commit/tree, toolchain image digest, lockfile hash). The stage script
copies them into the export (build, dev and build:export) and refuses a missing or altered file; the pom enforcer
requires the staged bundle and map, so a release without the workflow editor fails packaging. No network, token or
component fetch is needed in the producer. The component main commit is recorded after its merge (pending in the lock).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 06:34:45 -06:00
James MaesandClaude Sonnet 5.5 56ab7fe21c fix(widgets): run dynamic components on the React 19 host with Material's defaultValues Map and qController.processRun
Port of the KOF22 bridge fixes onto the current line: widget defaultValues are handed to dynamic components as a
Map that still allows property access, and the bridge exposes Material's qController.processRun (real process
init/run through the existing UI API). Needed by the QSequentialWorkflow editor bundle.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 06:33:21 -06:00
James MaesandCodex 0f2eb173c1 chore(release): prepare QBit UI candidate RC.38
QRun-IO/ci-policy: ci / ci-result (pull_request) Canceled after 0s
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 13053bb397bf07f3eaacd6e51ef0ffad7f9f1eb3] (pull_request) Successful in 8s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (pull_request) Canceled after 20m6s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 6s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (pull_request) Successful in 47s
QRun-IO/ci-policy: Shared Node CI with Playwright / node (pull_request) Failing after 15m13s
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Canceled after 1h27m5s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (pull_request) Canceled after 0s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
Use the next unused Maven version and record the deployed publication route and current integration evidence. Release remains sequenced after Core and Collaboration.

Co-Authored-By: Codex <noreply@openai.com>
2026-11-05 05:55:49 -06:00
James MaesandCodex 22f5749e36 chore: preserve current permission editor in QBit UI release
Merge current main without changing the permission editor. Both branches set maxWorkers to 4; retain a single setting. All 110 affected widget/process tests and typecheck passed.

Co-Authored-By: Codex <noreply@openai.com>
2026-11-05 05:51:34 -06:00
james.maes 090f5febea Merge pull request 'feat(authentik): application permission editor RC checkpoint' (#205) from james.maes/authentik-access-rc-20261105 into main
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (push) Successful in 26m10s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (push) Successful in 26s
QRun-IO/ci-policy: ci / node-playwright-11 (push) Successful in 1h29m54s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 10s
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 13053bb397bf07f3eaacd6e51ef0ffad7f9f1eb3] (push) Successful in 9s
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 9s
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / node (push) Successful in 13m54s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (push) Successful in 52s
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
2026-11-05 11:40:13 +00:00
james.maes afb9d75c75 Merge remote-tracking branch 'origin/main' into feature/authentik-access-ui
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 11s
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (pull_request) Successful in 34m33s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow f1d46718955078331817c6206b822a94bc7d769f] (pull_request) Successful in 7s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 7s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (pull_request) Failing after 29s
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Failing after 1h39m12s
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (pull_request) Successful in 37s
QRun-IO/ci-policy: Shared Node CI with Playwright / node (pull_request) Failing after 14m6s
2026-11-05 05:23:23 -06:00
james.maesandGPT-6 119f1211d0 feat(authentik): add application access editor and paged permission matrix
Provide user, group and QQQ application-role permission editing with searchable,
paged controls and inherited-source visibility. Reuse the matrix in policy
editing and preserve form context for process widgets.

Co-Authored-By: GPT-6 <noreply@openai.com>
2026-11-05 05:23:11 -06:00
James MaesandCodex aba43263b2 ci: wire the supported Next dashboard Maven release
Pin the reviewed frontend-next producer and document the qualified publisher and remaining deployment gates. The immutable baseline version is unchanged until final release preparation.

Co-Authored-By: Codex <noreply@openai.com>
2026-11-05 05:22:01 -06:00
James MaesandGPT-6 ddc7de731b chore: reconcile current main with QBit UI integration
Preserve the upstream companion Tools workspace alongside the prepared
standard QQQ file, script, and process controls. Record the focused merge
checks and the verified missing Next Maven Central publication integration.

Validation: typecheck, changed-file lint, license headers, staged secret
scan, and 61 focused cases across the completed runs. The unchanged lazy
chart file passed with one worker after a cold parallel timeout; no test
assertion or timeout changed. Full release CI remains pending dependencies.

Co-Authored-By: GPT-6 (Codex) <noreply@openai.com>
2026-11-05 04:43:42 -06:00
james.maes 3d162dce74 feat: add companion Tools setup workspace (#204)
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 5s
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / e2e (push) Successful in 21m28s
QRun-IO/ci-policy: Shared Node CI with Playwright / gate (push) Successful in 10s
QRun-IO/ci-policy: ci / node-playwright-11 (push) Successful in 21m41s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 5s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / secrets (push) Successful in 25s
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: Shared Node CI with Playwright / node (push) Successful in 10m44s
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
2026-11-05 09:41:12 +00:00
James MaesandCodex d186ad7086 docs: clarify Next UI publication prerequisites
Co-Authored-By: Codex <noreply@openai.com>
2026-11-04 16:49:23 -06:00
James MaesandGPT-6 Codex 2186561950 fix: complete file previews and portable UI verification
Allow local PDF blob reads in standalone CSP and preview signed attachment PDFs without forwarding credentials. Make static export portable on Windows, bound test workers, cover transfer cancellation and size limits, and update integration evidence. Full coverage gates, 12 browser scenarios, live file/PDF flows, lint, typecheck and snapshot packaging pass.

Co-Authored-By: GPT-6 Codex <noreply@openai.com>
2026-11-04 12:02:20 -06:00
40ee257847 chore: preserve paused QBit work and document remaining issues
Save the current implementation and README, status, and known-issue documentation at the user-requested stop. No completed release or deployment is claimed.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-Authored-By: GPT-6 Codex <noreply@openai.com>
2026-11-04 10:45:31 -06:00
James MaesandClaude Sonnet 5.5 b67fb5fd20 feat(ui): file preview/delivery, PDF preview, isolated TypeScript editing and result fields
Reconcile the reviewed private UI source onto canonical main d04cfa4.
Source: private kof22-clients frontend-next commit
f49de335e8bfc88b2e96aa8a34343ac6b5e93003 (feature/kof22-rc37), files
limited to the file-preview/open/download/actions, PDF/document preview,
compiled TypeScript editor and generic API widget support
(commits b4542dbe, 0ab1f692, d0705481, 21caa18d of that branch).

Not taken: the sequential-workflow bundle staging and qfmd bridge changes,
the zod 4 migration, the RC.37 tag merge (presence, auth, My Work),
version/pom bumps and the old lockfile. Canonical next 16.4.1, zod 3 and
dompurify are unchanged; package.json and pnpm-lock.yaml add only the
editor/PDF dependencies, worker build scripts and CodeMirror pins
(lock change is additions only).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 10:28:55 -06:00
forge-recovery d04cfa4b58 Merge pull request 'ci: baseline reviewed historical fixture findings' (#203) from ci/historical-finding-baseline-20261101 into main
QRun-IO/ci-policy: ci / ci-result (push) Failing after 3s
QRun-IO/ci-policy: ci / gradle-0 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-1 (push) Skipped
QRun-IO/ci-policy: ci / node-3 (push) Skipped
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / java-maven-2 (push) Skipped
QRun-IO/ci-policy: ci / node-4 (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Failing after 4s
QRun-IO/ci-policy: ci / source-config-5 (push) Skipped
2026-11-02 01:46:35 +00:00
forge-recoveryandOpenAI GPT-6 94424b3f46 ci: baseline reviewed historical fixture findings
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-2 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 5s
QRun-IO/ci-policy: ci / gradle-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Failing after 5s
Co-Authored-By: OpenAI GPT-6 <noreply@openai.com>
2026-11-02 01:13:24 +00:00
e31716aa52 fix: update Next and harden the standalone runtime image (#201)
* fix: update Next and harden the standalone runtime image

Upgrade Next and eslint-config-next to 16.4.1; build and run on the same pinned Alpine Node image, exclude pnpm cache from context, and omit build-only package managers from production.

Co-Authored-By: Codex <noreply@openai.com>

* test: make standalone staging checks portable to Windows

Use platform paths for symlink comparison and the export-presence mock. Keep Unix permission-bit checks in an explicit Unix-only case; preserve file contents and relative-link behavior on Windows.

Co-Authored-By: Codex <noreply@openai.com>

* test: load the real developer view before DOM timing assertions

The first developer-view test also fails on unchanged main under cold Windows transforms. Compile the real lazy component in setup while retaining its rendering, API assertions and original timeouts. All nine targeted tests pass.

Co-Authored-By: Codex <noreply@openai.com>

---------

Co-authored-by: James Maes <jmaes@dmdbrands.com>
Co-authored-by: Codex <noreply@openai.com>
2026-10-29 17:19:37 -05:00
james.maesandCodex 9756b9d93b ci: adopt managed orb 0.8 patch track (#193)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 16:20:56 -05:00
james.maesandCodex f205992878 ci: adopt shared orb 0.7.17 (#192)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 14:37:37 -05:00
james.maesandCodex 5f503fa7e9 test: disambiguate async help from process search results (#191)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 09:15:38 -05:00
james.maesandCodex 09828ab1cc ci: adopt central orb 0.7.12 and stable gates (#190)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 03:49:28 -05:00
james.maesandCodex c39748b1a0 test: stabilize widget layout and report reload acceptance (#189)
* test: assert widget layouts at container breakpoints

Co-Authored-By: Codex <noreply@openai.com>

* test: wait for saved report readiness before reloading

Co-Authored-By: Codex <noreply@openai.com>

---------

Co-authored-by: Codex <noreply@openai.com>
2026-10-21 03:08:45 -05:00
james.maesandCodex 829ef6b384 ci: align canonical shared orb workers (#188)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 01:49:08 -05:00
james.maesandCodex a94e1e39c8 ci: adopt shared orb 0.7.11 (#187)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 01:12:27 -05:00
james.maesandCodex 9bea8cc63f ci: centralize workflows in QQQ orb (#185)
Co-authored-by: Codex <noreply@openai.com>
2026-10-20 20:04:31 -05:00
james.maes ada9946eea Merge pull request #179 from QRun-IO/feature/assistant-conversation-list
feat(assistant): conversation row actions and thread state indicators
2026-10-19 08:16:52 -05:00
james.maesandCodex fa960580f6 fix(assistant): integrate attachment and conversation recovery
Preserve both configuration normalizers and restore the loading status accessible name required by logout acceptance.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-19 07:45:41 -05:00
james.maes 596bee88cc Merge pull request #178 from QRun-IO/feature/security-findings-2026-10
fix(security): harden plain-text sanitizers and pin source-map-js
2026-10-19 07:41:18 -05:00
james.maes df764362b8 Merge pull request #184 from QRun-IO/fix/assistant-attachment-recovery
fix(assistant): contain upload failures and isolate conversation files
2026-10-19 07:38:03 -05:00
james.maesandCodex 07b4a973de fix(assistant): isolate conversation action rollback
Co-Authored-By: Codex <noreply@openai.com>
2026-10-19 07:11:40 -05:00
james.maes e36afde222 Merge pull request #183 from QRun-IO/fix/startup-branding
fix: show the dashboard only after application branding is ready
2026-10-19 07:11:37 -05:00
james.maesandCodex 69836fc62f fix(security): parse help and widget text with the shared sanitizer
Co-Authored-By: Codex <noreply@openai.com>
2026-10-19 07:07:22 -05:00
james.maes 3cb3659a41 Merge branch 'main' into feature/assistant-conversation-list 2026-10-19 07:06:27 -05:00
james.maes b30ff4e916 Merge remote-tracking branch 'origin/feature/security-findings-2026-10' into fix/source-map-security 2026-10-19 07:03:16 -05:00
james.maesandCodex d558c1378d fix(assistant): contain upload failures and isolate conversation files
Co-Authored-By: Codex <noreply@openai.com>
2026-10-19 07:01:25 -05:00
james.maes f1c5f09b4a Merge pull request #182 from QRun-IO/feature/audit-fallback-fixture
test: exercise the audit table fallback with independent permissions
2026-10-19 06:55:22 -05:00
james.maesandCodex c9a56b0d9f fix: wait for application branding before painting the dashboard
Co-authored-by: Codex <noreply@openai.com>
2026-10-19 06:48:51 -05:00
james.maesandCodex 3bab5596ab test: exercise audit fallback with independent process permission
Co-authored-by: Codex <noreply@openai.com>
2026-10-19 06:29:02 -05:00
james.maesandClaude Opus 5.5 ae2498868d feat(assistant): conversation row actions, thread state indicators, unread correctness
Rail rows are now a list of buttons with Archive/Restore and Delete (confirm,
optimistic with rollback and toast), a Manage all conversations link to the
conversations table when the server names one, a spinner for running threads,
an unread dot and bold title, and a dot on the History toggle for activity in
other conversations. A reply that finishes in the open conversation is marked
read (PATCH read:true). Fields are optional so older servers keep working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:40:18 -05:00
james.maesandClaude Opus 5.5 ea21505e96 fix(security): harden plain-text sanitizers and pin source-map-js
- Strip HTML tags until stable and decode &amp; last in helpPlainText and
  widget plainText, so nested or split tags cannot survive.
- Match closing script tags with attributes or whitespace in the CSP
  inline-script hasher.
- Replace every newline in the child-record export aria-label.
- Override source-map-js to >=1.2.2 (GHSA advisory, dev dependency).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:36:24 -05:00
james.maes 1b37e5efa9 Merge pull request #176 from QRun-IO/feature/next-rc36-prep
chore(#712): prepare Next UI 1.0.0-RC.36
2026-10-16 10:39:55 -05:00
james.maesandClaude Opus 5.5 ba20307fa0 chore(#712): prepare Next UI 1.0.0-RC.36
PR175: accessibility fixes (Dashboard Wave 0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 10:22:41 -05:00
james.maes 81a03eab2d Merge pull request #175 from QRun-IO/feature/home-a11y-contrast
fix(a11y): readable link text, 24px clear target, h2 widget titles, skip link focus
2026-10-16 10:19:17 -05:00
james.maesandClaude Sonnet 5.5 ab9cf837df fix(a11y): readable text on accent-filled surfaces
White text on a light brand accent (for example #059669, 3.8:1) fails
contrast on the active sidebar item, selected tabs and primary buttons.
applyBrandingTheme now sets --color-primary-foreground to white when that
reaches 4.5:1 on the accent and black otherwise, so the brand fill is kept.
The axe spec no longer excludes the sidebar.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 09:24:32 -05:00
james.maesandClaude Sonnet 5.5 5c9307015c fix(a11y): readable link text, 24px clear target, h2 widget titles, skip link focus
App homes failed axe "serious" checks on 18 kof22 pages (A10/L10 = 0):

- Accent-colored text (the table widget link row, cell links, widget links)
  used text-primary, which fails 4.5:1 for light brand colors such as #059669.
  Add a --color-link token (text-link utility) derived per theme from the
  accent: applyBrandingTheme darkens (light theme) or lightens (dark theme)
  the accent until it reaches 4.5:1 on the card and muted surfaces; without
  branding the stylesheet derives it from --color-primary.
- The widget dropdown icon buttons (Clear range, previous/next) are at least
  24 x 24 px (WCAG 2.2 target size).
- Widget titles on an app home render as h2 under the page h1 (a nested
  widget stays h3); no visual change.
- main is focusable (tabIndex -1) and the skip link moves focus to the first
  widget of the page, else to main.

Adds unit tests and a Playwright axe spec (light and dark, 390 and 1440 px).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 09:20:21 -05:00
james.maes 55c150136b Merge pull request #174 from QRun-IO/feature/next-rc35-prep
chore(#712): prepare Next UI 1.0.0-RC.35
2026-10-15 22:13:38 -05:00
james.maesandClaude Opus 5.5 d644995c85 docs(release): RC35 notes state only the verified requirement
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:04:28 -05:00
james.maesandClaude Opus 5.5 6f95554362 chore(#712): prepare Next UI 1.0.0-RC.35
PR164: profile photo uploader and avatars everywhere (UI API 1.8.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:04:15 -05:00
james.maes ba38367244 Merge pull request #164 from QRun-IO/feature/user-profile-avatars
feat(profile): profile photo uploader and avatars everywhere (UI API 1.8.0)
2026-10-15 22:03:46 -05:00
james.maes c623b3bc87 Merge remote-tracking branch 'origin/main' into feature/user-profile-avatars 2026-10-15 21:32:25 -05:00
james.maes 17f605261a Merge pull request #173 from QRun-IO/feature/next-rc34-prep
chore(#712): prepare Next UI 1.0.0-RC.34
2026-10-15 21:17:33 -05:00
james.maesandClaude Opus 5.5 0db8ab0bfa chore(#712): prepare Next UI 1.0.0-RC.34
PR172: fail soft (error boundaries, validated assistant payloads, unstuck streams).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:10:37 -05:00
james.maes 68a2b9de0c Merge pull request #172 from QRun-IO/feature/fail-soft-boundaries
feat: fail soft — layered error boundaries, validated assistant payloads, unstuck streams
2026-10-15 21:10:07 -05:00
james.maesandClaude Opus 5.5 af42c7613f chore(openapi): vendor the assistant UI API extension 1.5.0 (qbit-assistant#60)
Re-vendors extensions/assistant.json from qbit-assistant#60's assistant.yaml:
POST /assistant/client-errors (AssistantClientErrorReportV1) replaces the
provisional entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:46:00 -05:00
james.maesandClaude Opus 5.5 f5170ed130 Merge origin/main (IA phase 1): the avatar moves to the header AccountMenu
The user menu moved from the sidebar footer to the header AccountMenu
(#168). Take main's Sidebar; AccountMenu now draws UserAvatar from the
profile (photo, chosen Gravatar via the server, else initials) with the
profile's display name, and no longer builds Gravatar URLs from branding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:38:02 -05:00
james.maesandClaude Opus 5.5 77fc832f9e test(e2e): a crashing assistant result and a crashing widget leave the page usable
Injects a real render-time crash (no app test hook): an init script makes
Number.prototype.toLocaleString throw for one sentinel value, which the
count render and the table widget format. Asserts the failure stays in its
part/widget with details and Retry, the rest of the reply/dashboard and the
shell keep working, records renders without rows/columns and unknown render
types still show, and the client error report reaches the backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:34:56 -05:00
james.maesandClaude Opus 5.5 627ad7ae34 feat(assistant): fail soft at panel, message, part and render level
- The panel sits behind its own boundary: a crash shows "The assistant
  hit a problem" with Retry, Copy details, Report a bug (QA capture) and
  Close, while the page keeps working; reopening retries.
- Each message, part and tool render has its own boundary with a small
  inline "couldn't be displayed" note and a details toggle; unknown parts
  and renders show as raw JSON.
- Failed replies offer Retry (asks again); running replies offer Stop
  inline. A second Stop, or a failed stop request, stops listening at once
  and keeps the reply shown as stopped, so the input is never locked.
  A reply left running with no live connection can be stopped too.
- A stream event the reducer cannot apply is skipped and reported instead
  of breaking the stream.
- Failures are reported (console with request id; POST
  /assistant/client-errors when the assistant extension is 1.5.0+, so they
  show in Admin). Vendors the operation into the assistant extension.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:26:58 -05:00
james.maesandClaude Opus 5.5 03f67def30 feat(assistant): validate payloads at the API boundary; streams never hang
- lib/api/assistant-normalize: zod validation of conversation messages,
  parts, renders, presentation blocks and stream events against the
  /qqq/v1 assistant shapes. Missing lists become [], missing text '',
  bad list items are dropped, and unknown part or render types become
  `unknown` (shown as raw JSON) instead of throwing.
- getAssistantConversation and the SSE reader apply it, so a stored or
  streamed malformed payload can no longer reach a renderer.
- A connection that delivers nothing (not even the 15 s ping) for 45 s is
  treated as lost and resumed; refused turns carry their X-Request-Id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:26:57 -05:00
james.maesandClaude Opus 5.5 cc64297d83 fix(avatars): read the profile from the preferences index (no 404 without profiles)
The real-backend acceptance apps advertise the avatars feature but install
no profile section, so GET /preferences/profile answered 404 on every page.
useMyProfile now finds the profile section in the shared preferences index
(which saving any section already invalidates); nothing is requested that
can 404, and other users' avatars are only looked up when profiles exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:15:42 -05:00
james.maesandClaude Opus 5.5 1d7c1a5ebf feat(errors): layered fail-soft boundaries with PII-safe client error reports
- lib/errors/client-errors: one reporter for every boundary; logs with the
  X-Request-Id correlation id, redacts and bounds text, dedupes, caps per
  session, and sends to a registered sink when one exists.
- ErrorBoundary gains scope reporting, resetKeys and fallbackRender
  (backward compatible); InlineFailure is the small in-place fallback with
  a details toggle.
- (dashboard)/error.tsx: a page that throws is replaced inside the app
  shell, so navigation, header and assistant keep working.
- Every ConnectedWidget, SeededWidget and record section renders behind its
  own boundary, so one failing widget or section cannot take down a page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:13:46 -05:00
james.maes 960538ebc0 Merge pull request #171 from QRun-IO/feature/next-rc33-prep
chore(#712): prepare Next UI 1.0.0-RC.33
2026-10-15 20:02:15 -05:00
james.maesandClaude Opus 5.5 67eca54900 chore(#712): prepare Next UI 1.0.0-RC.33
PR168 (navigation, IA phase 1) and PR170 (assistant empty-results crash fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:56:17 -05:00
james.maes b4b59cee6e Merge pull request #170 from QRun-IO/fix/assistant-empty-records
fix(assistant): empty query results no longer crash the page
2026-10-15 19:55:53 -05:00
james.maes 28fdb2a781 Merge pull request #168 from QRun-IO/feature/ia-phase1-nav
feat(nav): sidebar sections and favorites, one search, header Search/Ask/Jobs/Notifications/Help/Account, single-key shortcut switch
2026-10-15 19:48:09 -05:00
james.maes 524adff0d5 Merge remote-tracking branch 'origin/main' into feature/user-profile-avatars
# Conflicts:
#	docs/releases/unreleased.md
#	src/lib/api/openapi/qqq-v1.json
2026-10-15 19:42:02 -05:00
james.maesandClaude Opus 5.5 893bbf26df fix(assistant): empty query results no longer crash the page
A records render with no rows or columns (servers omit empty lists when a
query matches nothing) threw "Cannot read properties of undefined
(reading 'length')" and took down the whole page. Renders now treat
missing lists as empty and show "no rows match", and each reply part
renders inside an error boundary, so one malformed part cannot crash the
reply or the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:30:51 -05:00
james.maesandClaude Opus 5.5 1df522097f test(acceptance): NAV-031 checks that no record is listed (the search box always offers help and Search everything)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:25:24 -05:00
james.maesandClaude Opus 5.5 f5a56eaf7e test(keyboard): name the query client wrapper (react/display-name)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:00:10 -05:00
james.maesandClaude Opus 5.5 9b475797a0 chore(avatars): UI API 1.8.0 (QQQ 4.1.0-RC.17); re-vendor the 1.8.0 document
QQQ 4.1.0-RC.16 shipped UI API 1.7.0 without profiles; they are 1.8.0 in
RC.17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:59:27 -05:00
james.maesandClaude Opus 5.5 5283503e89 fix(keyboard): read the Keyboard section from the preferences index (no 404 on servers without it); NAV-032 on the search box
A server without the Keyboard preference section answered the per-page
GET /preferences/keyboard with 404, which acceptance rightly counts as a
failed request. The setting now comes from the preferences index (shared
with the account menu, skipped without the preferences feature), so no
request fails; the switch saves there and updates both caches.

NAV-032: the search box always offers Search everything once text is
typed, so the spec checks that and that no record is listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:48:41 -05:00
james.maesandClaude Opus 5.5 bb9e083539 chore(perf): drop the quickSearchPanel lazy-chunk budget (the inline header search and / dialog are retired)
Record results render in the search box (CommandRecordResults), which
is already in the palette's lazy chunks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:36:33 -05:00
james.maesandClaude Opus 5.5 d5003ebdd5 Merge main (RC.32: What's new) into feature/ia-phase1-nav; What's new moves to the Help menu
The release-notes What's new item (#165) moves from the retired sidebar
user menu into the header Help menu, with the same extension probe,
path and unread summary; its tests move with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:34:26 -05:00
james.maes 9a63173efe Merge pull request #167 from QRun-IO/feature/next-rc32-prep
chore(#712): prepare Next UI 1.0.0-RC.32
2026-10-15 17:04:55 -05:00
james.maesandClaude Opus 5.5 93e0fd7303 chore(#712): prepare Next UI 1.0.0-RC.32
PR165: What's new page and user-menu entry (release-notes UI API 1.0.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:58:59 -05:00
james.maes b24b74e30d Merge pull request #165 from QRun-IO/feature/whats-new
feat(release-notes): What's new page and user-menu entry (release-notes UI API 1.0.0)
2026-10-15 16:58:08 -05:00
james.maesandClaude Opus 5.5 ea028dc86f docs(releases): avatars via lookup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:25:21 -05:00
james.maesandClaude Opus 5.5 3e5b097667 fix(avatars): opaque avatar URLs via batched POST /avatars/lookup
Follows the core security review (qqq#1238): other people's avatars come
from one POST /avatars/lookup per tick (user ids never in a URL), answers
are kept for the session, and every image URL is GET /avatars/{avatarId}.
Re-vendor the 1.7.0 document (lookupAvatars, opaque avatarId); e2e mocks
the lookup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:25:14 -05:00
james.maes 98229202f3 Merge remote-tracking branch 'origin/main' into feature/whats-new
# Conflicts:
#	docs/releases/unreleased.md
2026-10-15 16:19:09 -05:00
james.maesandClaude Opus 5.5 9df3324687 fix(nav): menu actions run after focus returns; drawer search; palette active option; specs for the new header
- HeaderMenu close(then): a chosen item's action (search help, keyboard
  shortcuts, Ask, sign out) runs once focus is back on the menu button,
  so the dialog it opens returns focus there.
- The drawer's search field opens search after the drawer has closed
  and focus is back on the menu button.
- The search box input names the highlighted option
  (aria-activedescendant), which cmdk does not track for a controlled
  value.
- e2e and acceptance specs move to the one search box, the Help and
  account menus, sidebar favorites, the 1024 px drawer breakpoint,
  Shift+D and the single-key switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:14:58 -05:00
james.maes ae6649f8e1 Merge pull request #166 from QRun-IO/feature/next-rc31-prep
chore(#712): prepare Next UI 1.0.0-RC.31
2026-10-15 16:10:23 -05:00
james.maesandClaude Opus 5.5 a504a21718 chore(#712): prepare Next UI 1.0.0-RC.31
PR162: adopt QQQ UI API 1.6.0. Requires QQQ 4.1.0-RC.15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:00:12 -05:00
james.maes 9aff198a60 Merge pull request #162 from QRun-IO/feature/ui-api-1.6
feat(ui-api): adopt QQQ UI API 1.6.0 - clientCapabilities, field validation, error schema, v1 branding
2026-10-15 15:49:54 -05:00
james.maesandClaude Opus 5.5 2d125e4f90 feat(nav): one search, header Search/Ask/Jobs/Notifications/Help/Account, sidebar sections and favorites
The IA review of 2026-10-15 (Phase 1), rendered generically from
metadata (no application names):

- Sidebar sections from app section roles (UI API 1.7.0, inferred for
  older servers): Primary without a heading, History and group headings
  as role=group labels, Settings collapsed with a count. A single-child
  app is one leaf. A section name used as a path (an old group app URL)
  redirects to the app that heads it.
- Sidebar Favorites (collaboration): collapsed, up to five.
- One search box: Cmd/Ctrl+K, /, the header field and the drawer field
  open the palette (pages with search aliases, records, actions, help,
  Ask). The inline header search and the / dialog are retired.
- Header, from general to personal: Search, Ask (labelled from 1440 px),
  Jobs (always shown, idle or with a count), Notifications (severity in
  the name), Help, Account avatar. 40 px targets, 44 px on touch.
  Compact screens fold Help and Ask into the avatar menu.
- Help opens a Help menu (WCAG 3.2.6): search help, keyboard shortcuts,
  Report a bug. The user menu moves from the sidebar footer to the avatar.
- The persistent sidebar starts at 1024 px; below that it is a drawer.
- Single-key shortcuts can be turned off (WCAG 2.1.4), via the core
  Keyboard preference section or this browser. Record delete moves
  from d to Shift+D (still confirmed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:32:53 -05:00
james.maesandClaude Opus 5.5 ec974fa121 fix(processes): keep the 403 permission refusal message; carry its traceId
A refused step (HTTP 403) again becomes the user-facing 'You do not have
permission to run this process.' even when the 1.6.0 body has a message
(PRC-042); the body's traceId is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:26:18 -05:00
james.maesandClaude Opus 5.5 4f1641cb72 fix(auth): abort and discard the old session's requests at logout
A /qqq/v1/preferences response landing after the logout response set the
sessionId cookie again (SEC-037, SEC-048). Logout now ends the API
client's session generation before calling /logout: requests in flight
are aborted through a session AbortController (also passed to the
realtime and assistant fetch streams), and any response of the ended
generation is discarded as a cancellation, so it can neither set the
cookie nor trigger the 401 flow. Cancelled requests raise no error toast.
Replaces the earlier wait-for-idle approach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:02:41 -05:00
james.maesandClaude Opus 5.5 154be4f827 feat(release-notes): What's new page and user-menu entry (release-notes UI API 1.0.0)
Vendored release-notes extension contract; hand-written client for releases,
summary and seen; /app/whats-new lists published releases newest first,
grouped by category, with New badges, deep link highlight (?release=id),
paging and safe links/screenshots, and marks them seen once loaded. The user
menu shows What's new with an unread count when the extension is installed.
The bell needs no change: release notifications link to /app/whats-new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:59:25 -05:00
james.maesandClaude Opus 5.5 04fc4d7692 fix(ui-api): keep legacy behaviour where 1.6.0 changed it; settle requests before logout
- error text keeps userFacingError, error, then message; process errors are
  user-facing only from userFacingError
- the full materialDashboardTheme wins over the uiSettings.theme subset
- uiSettings are read setting by setting, falling back to materialDashboard
- audits fall back to instance metadata when the table capability is
  unavailable (RC.15 marks unaudited tables unavailable)
- global error toasts unchanged from main (no trace description)
- logout waits for in-flight queries and mutations so a late response
  cannot refresh the expired sessionId cookie
- acceptance specs accept the 1.6.0 error and icon fields, and use a
  server-only uniqueness error now that exclusive bounds are published

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:54:30 -05:00
james.maesandClaude Opus 5.5 81fa126305 test(profile): photo uploader unit and e2e coverage, full-row image fields
- ImageUploadField and UserAvatar unit tests (crop, upload, save ref,
  remove, SVG refused, a11y; initials, misses, sizes).
- tests/e2e/profile-avatars.spec.ts: upload flow against mocks and the
  6-width light/dark screenshot matrix (SCREENSHOTS_DIR).
- Image fields span the whole form row so they never squeeze at 768.
- Release notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:39:21 -05:00
james.maes 1fc984a3c6 Merge remote-tracking branch 'origin/main' into feature/ui-api-1.6
# Conflicts:
#	src/lib/query-client.ts
2026-10-15 14:39:05 -05:00
james.maesandClaude Opus 5.5 a5fb8dd8d5 feat(profile): avatars everywhere and a photo uploader on the Profile preferences
- Profile (core 'profile' section, UI API 1.7.0) renders generically; any
  FILE_UPLOAD field with valueIsUploadRef gets ImageUploadField: drag and
  drop or choose, square crop with zoom (pointer and keyboard), preview,
  remove; the crop goes to POST /uploads and the ref is saved with the form.
- One UserAvatar (photo via GET /avatars/{userId}, else initials; misses
  remembered for the session) for the user menu, presence, comments,
  mentions, people picker, activity and the assistant's user messages.
- useMyProfile reads the profile section through the shared query, so a
  save updates the header at once.
- Privacy: branding gravatarDefault no longer sends the user's email hash
  to Gravatar; a Gravatar shows only when the user chose it (server side).
- Vendor the UI API 1.7.0 core document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:23:24 -05:00
james.maes 692d0cb166 Merge pull request #161 from QRun-IO/feature/next-rc30-prep
chore(#712): prepare Next UI 1.0.0-RC.30
2026-10-15 13:22:52 -05:00
james.maes e9208fc34c Merge branch 'main' into feature/next-rc30-prep 2026-10-15 13:14:31 -05:00
james.maes 2cbfb6d962 Merge pull request #163 from QRun-IO/feature/qa-capture-spec-sync
chore(qa-capture): re-vendor qa-capture.json from qbit-qa-capture 0.1.0-RC.1
2026-10-15 13:14:02 -05:00
james.maesandClaude Opus 5.5 b2a41b3eb9 chore(qa-capture): re-vendor qa-capture.json from qbit-qa-capture 0.1.0-RC.1
Replaces the hand-written document with the QBit's generated
openapi/qqq-v1/qa-capture.yaml. Same paths, operations and fields; the
document now has the QBit's schema names (QaCaptureReportResultV1),
descriptions and nullable markers. OFF_SPEC_CEILING stays 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:42:26 -05:00
james.maes 35d937ed30 Merge pull request #160 from QRun-IO/feature/qa-capture
feat(qa-capture): optional Jam QA capture on the qa-capture UI API extension
2026-10-15 12:41:30 -05:00
james.maesandClaude Opus 5.5 a00473c305 chore: target QQQ 4.1.0-RC.15 (UI API 1.6.0)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:27:09 -05:00
james.maes e76f0325a5 Merge branch 'feature/qa-capture' into feature/next-rc30-prep 2026-10-15 12:15:46 -05:00
james.maesandClaude Opus 5.5 de1d4a5295 test(standalone): include the qa backend prefix in staged-build fixtures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:15:44 -05:00
james.maesandClaude Opus 5.5 42ee6b87ce feat(ui-api): adopt QQQ UI API 1.6.0 (QQQ 4.1.0-RC.14)
- vendor core 1.6.0 and the extension documents (alerts 1.2.0, assistant
  1.3.0, collaboration 1.1.0, data-analysis 1.5.0, edi 1.1.0, esb 1.1.0,
  quick-search 1.1.0, secured-data 1.1.0, sql-workbench 1.1.0)
- saved views, audits, sharing and bulk processes come from the table's
  clientCapabilities (fallback to the old names only for pre-1.6.0 servers)
- record forms use field.validation (required, maxLength/tooLongBehavior,
  inclusive/exclusive min/max, pattern + patternMessage)
- errors prefer BasicErrorResponseV1.message and show the traceId
- standalone branding uses GET /qqq/v1/branding/{role} (falls back on 404)
- uiSettings preferred over materialDashboard(Theme)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:09:20 -05:00
james.maesandClaude Opus 5.5 a73e4e245c chore(#712): prepare Next UI 1.0.0-RC.30
PR160: optional Jam QA capture on the qbit-qa-capture UI API extension.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:04:31 -05:00
james.maesandClaude Opus 5.5 e0ca5b1bb4 perf(qa-capture): load the sanitizers with the Jam chunk
The page URL filter moves to page-url.ts; sanitizers.ts is now imported only
by the dynamic Jam import, so users without capture never download it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:02:59 -05:00
james.maesandClaude Opus 5.5 c9773cf45f docs(qa-capture): QA capture guide and acceptance matrix row SEC-050
SEC-050 is recorded as excluded (pending owner approval) until
qbit-qa-capture ships a real-backend fixture; Vitest covers it meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:00:51 -05:00
james.maesandClaude Opus 5.5 15cdffe7f3 feat(standalone): opt-in Jam CSP origins and forward /qa to the backend
QQQ_QA_CAPTURE_CSP=true adds js.jam.dev to script-src and *.jam.dev to
frame-src and connect-src. /qa/recorder (the qbit's Jam domain verification
page) is a backend route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:58:51 -05:00
james.maesandClaude Opus 5.5 41462cc4f6 feat(qa-capture): mark secure field values for Jam blur
Password, REVEAL and secured-data masked/revealable values and inputs carry
data-jam-blur and data-qqq-secure (inert when capture is off).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:58:50 -05:00
james.maesandClaude Opus 5.5 039fa56a0b feat(qa-capture): load Jam for enabled users and add Report a bug
The root providers load the capability once after sign-in when /version lists
qa-capture. Only an enabled capability dynamic-imports the Jam SDKs, adds the
jam:team/jam:blur meta, initializes Jam once with the sanitizers and registers
jam.metadata (route context, versions, backend context, request ids). The user
menu gains Report a bug (POST report, then Recorder.open with jam-title and
jam-reference) and QA reports; the header shows QA mode on; saving the QA mode
preference section refetches the capability.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:58:48 -05:00
james.maesandClaude Opus 5.5 f93a1d3c8f feat(qa-capture): keep Jam Recording Link parameters across sign-in
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:58:38 -05:00
james.maesandClaude Opus 5.5 600af361f4 feat(qa-capture): keep the last 50 request ids while capture is enabled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:58:37 -05:00
james.maesandClaude Opus 5.5 0969231c95 feat(qa-capture): build Jam sanitizers from the server's rules
Header removal, JSON (any depth, case-insensitive) and form field redaction,
query parameter redaction, value patterns, same-origin body drops, plus a
credential/JWT baseline. Pure and synchronous; an invalid pattern fails closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:58:36 -05:00
james.maesandClaude Opus 5.5 99170dbff8 feat(qa-capture): vendor the qa-capture UI API extension and its client
Hand-written per the shared qa-capture 1.0.0 contract (qaCaptureCapability,
qaCaptureReportCreate) until qbit-qa-capture publishes its generated fragment.
The capability is normalized so anything incomplete reads as disabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:58:35 -05:00
james.maesandClaude Opus 5.5 74cf6e88d7 build(deps): add Jam recording-links 0.4.0 and sdk 1.0.1 for QA capture
Pinned exactly. Electron (an optional peer of the recording-links electron
entry point) is ignored so installs stay small.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:41:11 -05:00
james.maes 0f6c37c998 Merge pull request #159 from QRun-IO/feature/next-rc29-prep
chore(#712): prepare Next UI 1.0.0-RC.29
2026-10-15 05:14:29 -05:00
james.maesandClaude Opus 5.5 4306da9cdd chore(#712): prepare Next UI 1.0.0-RC.29
PR136: EDI mapper page on the qbit-edi UI API extension.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:51:02 -05:00
james.maes abfbbb2e55 Merge pull request #136 from QRun-IO/feature/edi-mapper
feat(edi): EDI mapper page on the qbit-edi UI API extension
2026-10-15 03:50:37 -05:00
james.maesandClaude Opus 5.5 4139316789 test(edi): mock a UI API 1.4.0 server in the mapper e2e
Next main requires UI API 1.4.0 or later (sign-in refuses older servers),
so the mapper spec's version mock moves from 1.1.0 to 1.4.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:25:44 -05:00
james.maesandClaude Opus 5.5 aa0bc65774 Merge origin/main into feature/edi-mapper
Resolves src/lib/query-client.ts by keeping main's jobs, secured-data and
preference keys alongside the EDI mapper keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:25:42 -05:00
james.maes 86b54181f7 Merge pull request #158 from QRun-IO/feature/next-rc28-prep
chore(#712): prepare Next UI 1.0.0-RC.28
2026-10-15 01:33:47 -05:00
james.maesandClaude Opus 5.5 b32141cf65 chore(#712): prepare Next UI 1.0.0-RC.28
PR141: zero off-spec UI API calls (minimum UI API 1.4.0). Requires QQQ
4.1.0-RC.9 (RC.8's /table/{t}/apis was shadowed, qqq#1216).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:59:19 -05:00
james.maes 16699aedfa Merge pull request #141 from QRun-IO/feature/ui-api-zero-off-spec
feat(ui-api): zero off-spec calls (OFF_SPEC_CEILING 0, UI API >= 1.4.0)
2026-10-15 00:58:51 -05:00
james.maesandClaude Opus 5.5 e0ff899cb6 fix(developer): table spec URL ends in json so RapiDoc offers "View OpenAPI spec"
RapiDoc shows the View button only for a spec URL ending in "json"; the
v1 document operation is /openapi, so add ?format=json (ignored by the
server). Fixes acceptance REC-053.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:37:25 -05:00
james.maesandClaude Opus 5.5 e807d30209 test(acceptance): ESB-003 allows the refused table-APIs lookup
Without person READ, the table developer view's GET /table/person/apis
is refused (403) like the ESB section's request; /apis.json used to
answer 200 for any caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:10:07 -05:00
james.maesandClaude Opus 5.5 11d0f34639 test(e2e): the route mocks answer GET /version as a 1.4.0 server
With SUPPORTED_UI_API.minimum at 1.4.0, a mocked app without a /version
route falls back to the pre-semver 1.0.0 and refuses to load. Specs that
need an extension still register their own /version route on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:57:08 -05:00
james.maesandClaude Opus 5.5 45928e8abf test(dev): give the associated-script card test room for its three 5 s waits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:14:32 -05:00
james.maesandClaude Opus 5.5 1a381c2a68 Merge origin/main; vendor the RC.8 documents (UI API 1.4.0)
Re-vendor qqq-v1.json and api-aware.json from the v4.1.0-RC.8 tag and
move main's new e2e /version mocks to 1.4.0 (SUPPORTED_UI_API.minimum).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:12:21 -05:00
james.maes ee3682e859 Merge pull request #157 from QRun-IO/feature/next-rc27-prep
chore(#712): prepare Next UI 1.0.0-RC.27
2026-10-14 21:08:42 -05:00
james.maesandClaude Opus 5.5 62ec386035 chore(#712): prepare Next UI 1.0.0-RC.27
PR124 (secured-data UI), PR155 (jobs tray and live job view), PR156.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:19:11 -05:00
james.maes 427a037d53 Merge pull request #155 from QRun-IO/feature/jobs-tray
feat(jobs): live job view, header Jobs tray and Jobs page
2026-10-14 20:18:29 -05:00
james.maes 2381579a52 Merge remote-tracking branch 'origin/main' into feature/jobs-tray
# Conflicts:
#	src/lib/query-client.ts
2026-10-14 19:49:22 -05:00
james.maes 112bbc557a Merge pull request #156 from QRun-IO/fix/large-table-default-columns
test(acceptance): prfWide declares its 40 default columns (PRF-001/002 after #142)
2026-10-14 19:45:22 -05:00
james.maesandClaude Opus 5.5 463e9cc8e9 test(acceptance): the 40-column performance table names all columns as its default
#142 made tables wider than twelve fields open with a short default column
set, so PRF-001/PRF-002 saw 6 of prfWide's 40 columns (and 250 x 6 = 1,500
cells instead of 10,000). The budgets measure the full 40-column grid, so
the fixture now declares every column through the nextUi.defaultColumns
supplemental hint, which is the metadata route #142 added for this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:16:16 -05:00
james.maes 20fd1fa3d3 Merge pull request #124 from QRun-IO/feature/secured-data-ui
feat(secured-data): masked values, reveal, restricted filters and break-glass banner
2026-10-14 19:14:52 -05:00
james.maesandClaude Opus 5.5 4f685d200b test(acceptance): PRC-019 and PRF-005 read the live job view
The Working screen is replaced by the live job view, so the progress
assertions use its hooks: job-progress-message, job-progress-stats
(N / M and elapsed) and the Job progress bar. Feature matrix and
Material parity rows updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:14:20 -05:00
james.maes 9fc9cab48f Merge remote-tracking branch 'origin/main' into feature/secured-data-ui
# Conflicts:
#	src/app/(dashboard)/layout.tsx
#	src/components/forms/DynamicFormField.tsx
#	src/components/forms/EntityForm.tsx
#	src/components/query/DataGrid.tsx
#	src/components/query/FilterBuilder.tsx
#	src/lib/query-client.ts
#	src/lib/utils/filter-utils.ts
#	src/types/metadata.ts
2026-10-14 18:48:55 -05:00
james.maesandClaude Opus 5.5 aafeabfb33 fix(jobs): only call the jobs operations when the instance has the jobs tables
durableJobs is listed by every 1.3+ server, but the jobs operations exist
only where the instance defines jobs_job and jobs_log. Gate the tray,
page and inline tracking on both, so older servers and apps without
durable jobs never see a jobs request (acceptance flagged GET /jobs 404).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:47:33 -05:00
james.maes 4663557ca4 Merge pull request #154 from QRun-IO/feature/next-rc26-prep
chore(#712): prepare Next UI 1.0.0-RC.26
2026-10-14 18:08:31 -05:00
james.maesandClaude Opus 5.5 3cf64ce24c chore(#712): prepare Next UI 1.0.0-RC.26
PR142: tables open with a short default column set (audit High, #114).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:59:34 -05:00
james.maesandClaude Opus 5.5 b19d4c09b4 feat(jobs): live job view, header Jobs tray and Jobs page
- Header Jobs button next to the bell: active count badge, a subtle ring
  while jobs run, and a popover listing running jobs (eased mini bars,
  percent, ETA) and recently finished ones; clicking reopens a job
- Live job view replaces Working / Executing Process: phase stepper,
  eased determinate bar (shimmer when indeterminate), percent, counts,
  bytes, ETA, elapsed, message, collapsible live log with copy
- Run in background (POST /jobs/track for inline runs) and auto-detach
  for durableJobAlways processes after 5 s unless the user stays
- /app/jobs (Active, Recent, All for jobs_admin) and /app/jobs/{id}
- Realtime job.progress with since-cursor polling fallback; only
  /qqq/v1 operations from the re-vendored 1.4.0 document
- Motion on transform/opacity only, static under reduced motion;
  light and dark; Vitest, mocked e2e and screenshots at 390 and 1440

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:59:19 -05:00
james.maes caa89dc24f Merge pull request #142 from QRun-IO/feature/default-list-columns
feat(query): open tables with a short default column set
2026-10-14 17:56:20 -05:00
james.maes 5f3b7c697d Merge pull request #153 from QRun-IO/feature/next-rc25-prep
chore(#712): prepare Next UI 1.0.0-RC.25
2026-10-14 17:45:49 -05:00
james.maesandClaude Opus 5.5 bebb4f2444 chore(#712): prepare Next UI 1.0.0-RC.25
PR151 (assistant re-attach, never block asking, progress, Stop), paired
with qbit-assistant 0.1.0-RC.13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:35:13 -05:00
james.maesandClaude Opus 5.5 5afd785a22 test(search): wait for the first record suggestion to be selected
GlobalSearch's grouped-suggestions test asserted the selected option
synchronously after the list appeared. Under the full CI suite the
selection had not settled yet and the search-everything row was still
selected. The assertion now waits, as the next one already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:35:09 -05:00
james.maes 2d6a33ea9f Merge remote-tracking branch 'origin/main' into feature/default-list-columns 2026-10-14 17:33:00 -05:00
james.maes 70594c40ba Merge pull request #151 from QRun-IO/feature/assistant-reattach
feat(assistant): never block asking, re-attach after refresh, live progress and Stop
2026-10-14 17:20:08 -05:00
james.maes 9f3c48f4c7 Merge pull request #152 from QRun-IO/feature/next-rc24-prep
chore(#712): prepare Next UI 1.0.0-RC.24
2026-10-14 17:04:15 -05:00
james.maesandClaude Opus 5.5 38d2c62f3a chore(#712): prepare Next UI 1.0.0-RC.24
RC23's tag acceptance failed on tablet (REC-004, REC-019, NAV-015);
PR149 fixes it. Also includes PR150, PR148 and PR145.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:57:15 -05:00
james.maes 3ab4815526 Merge pull request #150 from QRun-IO/feature/empty-state-table-help
feat(query): empty tables explain what they are for and how rows get created
2026-10-14 16:56:28 -05:00
james.maes f0b4c775f0 Merge pull request #149 from QRun-IO/fix/record-tabs-tablet
fix(record): section tabs wrap instead of folding into More (tablet regression from #144)
2026-10-14 16:56:07 -05:00
james.maesandClaude Opus 5.5 f29bedd437 test(e2e): the turn POST now carries concurrency (supersede by default)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:51:32 -05:00
james.maesandClaude Opus 5.5 336affb9e2 chore(openapi): vendor the assistant UI API extension 1.2.0 (qbit-assistant#49)
turns/active, concurrency on a new turn, 503 SHUTTING_DOWN, turn.phase and
message.start.startedAt; the contract test now covers the new call.
Also: text buttons in the background-run banner, a check icon in the
concurrency menu, and the Enter hint kept to fine pointers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:37:57 -05:00
james.maesandClaude Opus 5.5 92b165eb9c feat(query): an empty, unfiltered table explains itself from its help slot
When a table has no records and no filter or search is active, the empty
state shows 'No <Table> yet' and the table's help (the 'help' slot: what the
table is for and how its rows get created) instead of 'Try adjusting your
filters'. Filtered lists and tables without help keep the existing text.
Adds helpPlainText (whole help as plain text); helpSummary reuses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:33:56 -05:00
james.maesandClaude Opus 5.5 42fa4c3c5f feat(assistant): never block asking; re-attach after refresh; live progress
Several runs per conversation: sending while an answer runs replaces it
(default) or runs both (panel menu choice). On load, focus and online the
panel re-attaches to running answers found through the UI API, shows a
banner with Open and Stop for answers in other conversations, and the
header button shows a busy state. Each running answer shows its phase
(thinking, tool, writing) with elapsed time; Stop is always available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:26:26 -05:00
james.maesandClaude Opus 5.5 72872f5ccd feat(assistant): find active turns, retry restarts, resume until the turn ends
listActiveAssistantTurns (GET /assistant/turns/active, [] on an older
server), the optional concurrency on a new turn, a retried 503
SHUTTING_DOWN start, the turn.phase event, an idempotent cancel (409 is
finished), and a resume that keeps going with capped backoff for a time
budget instead of giving up after three attempts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:26:23 -05:00
james.maesandClaude Opus 5.5 b89029c0ff test(record): import QRecord from the types barrel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:05:10 -05:00
james.maesandClaude Opus 5.5 bb81e3c9a4 test(acceptance): NAV-015 waits for each list before navigating on
WebKit reports an API read that a navigation cuts off as "due to access
control checks", which the diagnostics fixture counts as an error
(tablet failure on the RC.23 tag). The title test now lets the Person and
Nav Deep Item lists load before opening the next page, as NAV-016 does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:04:01 -05:00
james.maesandClaude Opus 5.5 413c736050 fix(record): section tabs wrap onto rows instead of folding into More
#144 folded tabs that did not fit into a More menu. At tablet widths that
hid sections from the tab bar, so they could not be opened by touch or
reached by the arrow keys, and their panels were unnamed (acceptance
REC-004, plus REC-019, whose Time Zones tab was folded). Every section is
now a tab again. The tab bar wraps onto more rows and never scrolls
sideways (#116), and a label longer than the bar wraps inside its tab.
The More menu component is removed. Unit tests cover the wrapping, the
keyboard navigation and tapping the last section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:03:57 -05:00
james.maes c07fba1d4b Merge pull request #148 from QRun-IO/feature/policy-editor-full-access
feat(authentik): Full access checkbox in the policy editor
2026-10-14 16:03:44 -05:00
james.maesandClaude Opus 5.5 8437aeb0eb test(acceptance): REC-030 shows the two columns it measures
Record Lab has more than twelve fields, so it now opens with the short
default column set; the SIZE test turns on Short Code and Website before
measuring them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:48:24 -05:00
james.maes e39d4b847b Merge pull request #145 from QRun-IO/feature/home-no-duplicate-grids
fix(app-home): no automatic card grids on homes that declare widgets
2026-10-14 15:48:01 -05:00
james.maesandClaude Opus 5.5 9b596289af feat(authentik): Full access checkbox in the policy editor
A prominent "Full access (all current and future resources)" checkbox emits
the single {Action:["*"],Resource:["*"]} statement and disables the matrix.
Resource "*" with any other action is reported invalid. Opening a
full-access group shows it ticked; unticking returns an empty matrix. The
member preview says "Full access: everything in this app".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:27:30 -05:00
james.maesandClaude Opus 5.5 e434716cbd chore(ui-api): UI API 1.4.0 - re-vendor and require 1.4.0
RC.7 shipped UI API 1.3.0; the operations this PR uses ship in 1.4.0
(QRun-IO/qqq#1188, #1189, #1206). Re-vendor qqq-v1.json and
api-aware.json at 1.4.0, set SUPPORTED_UI_API.minimum to 1.4.0 and move
the mocked /version responses to 1.4.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:20:32 -05:00
james.maes dcc3a47522 Merge pull request #147 from QRun-IO/feature/next-rc23-prep
chore(#712): prepare Next UI 1.0.0-RC.23
2026-10-14 15:18:43 -05:00
james.maesandClaude Opus 5.5 90fc22327c chore(#712): prepare Next UI 1.0.0-RC.23
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:11:52 -05:00
james.maes 39b8f8b7ae Merge pull request #127 from QRun-IO/feature/help-everywhere
feat(help): help everywhere — icons, long-help panel, help search, author links
2026-10-14 15:09:41 -05:00
james.maesandClaude Opus 5.5 8f90df5ae9 fix(app-home): keep section grids by default; hint hides both
Hiding the section grids on every home with widgets removed the only way
to launch app-level processes on some homes (acceptance NAV-008/009,
PRC-004, SEC-008). By default only the Apps grid now gives way to widgets;
nextUi.showChildGrid=false hides both grids for homes whose widgets cover
them (kof22 sets it on its own and the qbit homes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:06:15 -05:00
james.maesandClaude Opus 5.5 e8bcb0bbe3 fix(query): keep every column on tables with at most twelve fields
Narrow tables do not have the wide-grid problem, so the short default set
applies only above twelve visible fields (or when the table names its own
set). Existing tables of that size, and the acceptance fixtures, open as
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:05:16 -05:00
james.maes 28b2de402c Merge pull request #146 from QRun-IO/feature/nested-app-urls
fix(routing): redirect app-nested URLs to their canonical path
2026-10-14 15:01:23 -05:00
james.maes acd324e410 Merge pull request #144 from QRun-IO/feature/record-tabs-wrap
fix(record): section tabs fold into a More menu instead of scrolling
2026-10-14 15:01:14 -05:00
james.maes 4c9d0ab9e1 Merge pull request #139 from QRun-IO/fix/breadcrumb-no-hscroll
fix(layout): breadcrumb without inner horizontal scroll
2026-10-14 14:59:26 -05:00
james.maes cd2754b93c Merge pull request #138 from QRun-IO/fix/dark-mode-os-default
feat(theme): default to the OS color scheme (System / Light / Dark)
2026-10-14 14:59:19 -05:00
james.maes be72703bd7 Merge pull request #143 from QRun-IO/feature/quick-search-suggest
feat(search): typeahead suggestions from the quick-search extension in the global search box
2026-10-14 14:56:10 -05:00
james.maes 219db989ed Merge pull request #140 from QRun-IO/feature/policy-editor-features
feat(authentik): policy editor feature permissions and member preview
2026-10-14 14:56:00 -05:00
james.maes 4cfb03c6b7 Merge pull request #99 from QRun-IO/dependabot/github_actions/github-actions-56a18c2c80
build(deps): Bump the github-actions group across 1 directory with 4 updates
2026-10-14 13:54:23 -05:00
james.maes 496cb0a14c Merge pull request #68 from QRun-IO/dependabot/npm_and_yarn/rapidoc-10.1.0
build(deps): Bump rapidoc from 9.3.8 to 10.1.0
2026-10-14 13:54:14 -05:00
james.maesandClaude Opus 5.5 996b0271de fix(routing): redirect app-nested URLs to their canonical path (#117)
The breadcrumb shows app > table > record, but /app/{app}/{table} said
"Table metadata is unavailable" and /app/{app}/{table}/{id} was a 404.
When the first segment is an app (and not also a table) and the second is a
table, process or app, the page now replaces the URL with /app/{second}/...,
keeping the rest of the path, the query string and the hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:53:52 -05:00
james.maesandClaude Opus 5.5 2580cf95a6 fix(app-home): no automatic card grids on homes that declare widgets (#115)
App homes appended automatic section (Actions, Reports, Data) and child-app
grids under their own widgets, repeating the nav as cards (a screen or two
on a phone). Homes that declare widgets now skip both grids; homes without
widgets keep them. The app hint nextUi.showChildGrid overrides either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:36:16 -05:00
james.maesandClaude Opus 5.5 a07213f633 fix(record): section tabs fold into a More menu instead of scrolling (#116)
The record view's section tab strip scrolled sideways inside its box when a
record had many sections (13 on a blog post). Tabs that do not fit now move
into a More dropdown, measured with ResizeObserver; the active section always
stays visible, arrow-key navigation and tab roles are kept, and the phone
accordion is unchanged. Nothing in the strip scrolls horizontally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:36:13 -05:00
james.maesandClaude Sonnet 5.5 17203f9876 test(search): mocked e2e for typeahead suggestions at 390 and 1440 px, light and dark
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 13:30:51 -05:00
james.maesandClaude Sonnet 5.5 e30c23dcbf feat(search): header search, dialog and palette use typeahead suggestions when installed
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 13:30:48 -05:00
james.maesandClaude Sonnet 5.5 3fb7002a52 feat(search): useRecordSearch suggest source with core-search fallback
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 13:30:46 -05:00
james.maesandClaude Sonnet 5.5 05ceeb1925 feat(search): quick-search suggest API client and vendored extension contract
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 13:30:44 -05:00
james.maesandClaude Opus 5.5 305d85bf61 feat(query): open tables with a short default column set (#114)
With no saved view, the query screen shows the primary key, then T1 and T2
section fields, skipping hidden, heavy and long-text fields, capped at six;
every other column starts unchecked. A table can name its own set with the
supplemental hint nextUi.defaultColumns. Saved and legacy views are kept;
reset goes back to the short set. Blog Posts no longer opens with 29 columns
and an inner horizontal scroll.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:20:40 -05:00
james.maesandClaude Opus 5.5 4bd2c95d01 feat(ui-api): zero off-spec calls - every UI call is a spec'd /qqq/v1 operation
- Form adjusters: POST /table/{t}/formAdjuster/{id}/{event} (JSON) instead
  of the legacy multipart /material-dashboard-backend route.
- API discovery: GET /table/{t}/apis (versions included) instead of
  /apis.json and {api}/versions.json; the table document through
  GET /table/{t}/apis/{api}/{version}/openapi.
- Vendor the refreshed UI API document (1.3.0) and the API-aware
  document (extensions/api-aware.json) for the API-versioned
  metadata/query/count routes.
- OFF_SPEC_CALLS is empty and OFF_SPEC_CEILING is 0.
- SUPPORTED_UI_API.minimum is 1.3.0 (the operations above are new in it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:08:43 -05:00
james.maesandClaude Opus 5.5 863099b95f feat(authentik): policy editor feature permissions and member preview
- Table features (Analyze, Comment, ..., and READ_WRITE `write`) are
  selectable checkboxes per table, included in the row, column and app
  select-alls and in search; policy-document parses and generates them
  in canonical order and round-trips documents that use them.
- "Preview for a member" (qbit-authentik#29 payload): pick a member and
  see their effective permissions with this document, with added and
  removed markers against the saved document and a mapper-off note.
  Lazy-rendered; no extra network calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:03:19 -05:00
james.maesandClaude Opus 5.5 9d57861672 fix(layout): keep every breadcrumb a visible link; truncate and wrap instead of collapsing
The ellipsis menu removed middle crumbs from the DOM, breaking the
acceptance specs that click each crumb (NAV-014, 007, 018, 019, 028) and
the 44 px touch-target check on the header (NAV-038). Every crumb is a link
again, in order. Long labels truncate with an ellipsis and a title; the
trail wraps onto another line (the header grows by min-height) so it never
scrolls sideways and never drops below the touch-target width.

e2e still checks no overflow at 320, 390, 768 and 1440 px, and now that
all crumbs stay visible links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:00:04 -05:00
james.maesandClaude Opus 5.5 ce8da2d2a4 fix(theme): keep the appearance control's Light/Dark behavior; System is the unset state
NAV-056 acceptance failed because the control now reported the saved
preference instead of the active mode. Under an application theme the
Light radio is again the active one (Dark stays disabled, the explanation
still shows, the saved choice is kept). System remains a third radio that
means no saved key.

Spec changes, because the old assertions encode the retired "OS never
matters" default: with the OS dark and no saved choice the app is dark and
System is selected; Reset to Defaults removes the key (it no longer writes
"false") and follows the OS, including a live OS change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:55:50 -05:00
james.maes 4c40679023 Merge remote-tracking branch 'origin/main' into feature/help-everywhere
# Conflicts:
#	src/components/forms/DynamicForm.tsx
#	src/components/forms/DynamicFormField.tsx
2026-10-14 12:55:38 -05:00
james.maes 40cc60b1d4 Merge pull request #120 from QRun-IO/feature/contingent-possible-values
feat(forms): contingent possible values across forms, bulk edit, processes and filters
2026-10-14 12:42:43 -05:00
james.maes 292a2a9aea Merge remote-tracking branch 'origin/main' into feature/help-everywhere
# Conflicts:
#	tests/setup.ts
2026-10-14 12:26:20 -05:00
james.maesandClaude Opus 5.5 771e46ecc3 fix(layout): breadcrumb never scrolls sideways; middle crumbs collapse into a menu
Removes the breadcrumb's horizontal scroll. At narrow widths the middle
crumbs collapse into an ellipsis menu, the current page truncates with a
tooltip, and the header keeps one line. e2e checks no overflow at 320,
390, 768 and 1440 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:14:35 -05:00
james.maesandClaude Opus 5.5 51775ab0cb feat(theme): follow the OS color scheme unless the user picked light or dark
With no qqq-dark-mode preference the app follows prefers-color-scheme and
reacts to OS changes; an explicit choice still wins. Display preferences
offer System / Light / Dark, and a pre-hydration script applies the same
rule to avoid a flash of the wrong theme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:14:29 -05:00
james.maesandClaude Opus 5.5 4b8226a4cc Merge origin/main into feature/contingent-possible-values
Picks up the realtime-singleton test isolation fix (#137).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:06:31 -05:00
james.maes db941a8cc5 Merge pull request #137 from QRun-IO/fix/association-metadata-dedupe
test: isolate the realtime client between tests (fixes the metadata dedupe flake)
2026-10-14 12:06:20 -05:00
james.maes 65db5355af Merge remote-tracking branch 'origin/main' into fix/association-metadata-dedupe
# Conflicts:
#	tests/setup.ts
2026-10-14 11:44:59 -05:00
james.maes 085430e17f Merge remote-tracking branch 'origin/main' into feature/help-everywhere
# Conflicts:
#	src/lib/api/openapi/qqq-v1.json
2026-10-14 11:38:28 -05:00
james.maesandClaude Opus 5.5 daf4005186 test: isolate the realtime singleton between tests; preload the lazy RecordView
"keeps arbitrary aliases distinct and deduplicates target metadata loads"
intermittently saw the pet metadata handler run twice under load. The
second request was not a product duplicate: it came from the realtime
tab singleton, which outlived each test. Once a test held a record view
past the 150 ms leader election, the singleton connected and kept the
stream cursor. The next test's first connection then resumed with
Last-Event-ID, the MSW realtime mock answered it with `resync` (a replay
miss), and invalidateRealtime refetched every active query in that
test's cache, including the already-loaded pet metadata.

Each test is a fresh page load, so the test setup now closes and forgets
the singleton after every test (new resetRealtimeClient). The page test
also preloads the lazy RecordView in beforeAll, as other page tests do,
so a single case run with -t no longer misses its first deadline on cold
compilation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:33:47 -05:00
james.maesandClaude Opus 5.5 de51fcbf83 Merge origin/main into feature/contingent-possible-values
Picks up the record-page association dedupe fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:27:44 -05:00
james.maes a521be57cc Merge pull request #121 from QRun-IO/feature/user-preferences
feat(preferences): Preferences page, notification settings drawer and section actions
2026-10-14 11:17:02 -05:00
james.maesandClaude Opus 5.5 4e6473aae2 test(help): WID-044 checks the summary tooltip and the help panel; HLP-003 is desktop-only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:11:22 -05:00
james.maesandClaude Opus 5.5 34efebb434 fix(edi): exact field labels in the preview pane; stabilize mapper e2e
- Sample and Guide revision selects get standalone labels, so their
  accessible names are exact.
- The overflow check ignores visually hidden (1px) elements, and the
  axe run disables color-contrast like the shared a11y helper (the
  mocked indigo accent fails repo-wide).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:08:38 -05:00
james.maesandClaude Opus 5.5 439c4b00e4 test(edi): open the guide segment through its summary element
The segment label is a span inside the summary, so pressing Enter on it
never toggled the details element.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:49:44 -05:00
james.maesandClaude Opus 5.5 648d2d8bf8 Merge origin/main into feature/user-preferences
Preferences now use the shared UI API discovery (#133): a core-feature
check isUiApiFeatureSupported sits next to the extension check, replacing
the separate cache; the version mock helper announces preferences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:36:32 -05:00
james.maesandClaude Opus 5.5 f95baaf8c7 feat(edi): EDI mapper page on the qbit-edi UI API extension
- Vendor the edi extension document (qbit-edi 1.0.0) with generated
  types, API client and hooks; all calls are on spec.
- "Open Mapper" record action on edi_map (only when the edi extension is
  registered and the map is readable); route /app/edi_map/{id}/mapper.
- Guide tree, target fields and associations, keyboard and drag rule
  creation, transform argument forms, validation of unsaved rules,
  sandbox preview with rule overrides and a PHI-suppressed notice, and
  Save through the core table API for DRAFT maps only.
- Three panes at xl, two at lg, tabs below; saved rules collapsed.
- Unit and Playwright tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:27:01 -05:00
james.maes e78537ad36 Merge remote-tracking branch 'origin/main' into feature/help-everywhere
# Conflicts:
#	src/components/forms/DynamicFormField.tsx
#	src/components/forms/EntityForm.tsx
#	src/components/process/ProcessRun.tsx
#	src/components/process/ProcessStepScreen.tsx
#	src/components/query/RecordQuery.tsx
#	src/components/records/RecordViewHeader.tsx
#	src/components/records/RecordViewSection.tsx
#	src/lib/api/openapi/qqq-v1.json
#	src/types/metadata.ts
2026-10-14 10:26:06 -05:00
james.maesandClaude Opus 5.5 8478404802 Merge origin/main into feature/contingent-possible-values
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:13:20 -05:00
james.maes 4ba1bb4cae Merge pull request #135 from QRun-IO/feature/next-rc22-prep
chore(#712): prepare Next UI 1.0.0-RC.22
2026-10-14 10:09:20 -05:00
james.maesandClaude Opus 5.5 c50675109a test: reset the preferences support cache synchronously in setup
The cache lives in an import-free module, so the global afterEach no longer
awaits a dynamic import between tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:08:20 -05:00
james.maes ab9245e66c Merge pull request #133 from QRun-IO/feature/108-qbit-presence-from-version
feat(ui-api): qbit presence from GET /version; vendor assistant extension (#108, off-spec 7)
2026-10-14 10:02:18 -05:00
james.maes be2d0fc66e Merge branch 'main' into feature/next-rc22-prep 2026-10-14 09:56:31 -05:00
james.maes 652ade887f Merge pull request #134 from QRun-IO/fix/collaboration-e2e-conflict-race
test(e2e): harden the collaboration conflict test against the mock stream attach race
2026-10-14 09:54:47 -05:00
james.maesandClaude Opus 5.5 3330f1bb63 Merge origin/main into feature/108-qbit-presence-from-version
Off-spec ceiling: main dropped POST /uploads (20); minus the 13
assistant calls is 7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:31:25 -05:00
james.maes 1bfebd09fb Merge remote-tracking branch 'origin/main' into feature/user-preferences
# Conflicts:
#	src/lib/api/openapi/ui-api-calls.ts
2026-10-14 09:29:39 -05:00
james.maesandClaude Opus 5.5 fb8b4efa6a Merge origin/main into feature/contingent-possible-values
Kept main's ui-api-calls.ts (#128 carries the same off-spec fix); re-applied the one-line formMethods prop on main's EntityForm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:26:36 -05:00
james.maesandClaude Opus 5.5 d3629d72b1 chore(#712): prepare Next UI 1.0.0-RC.22
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:25:55 -05:00
james.maes 48e81b4d12 Merge pull request #128 from QRun-IO/feature/collaboration-presence-share-tasks
feat(collaboration): realtime presence, conflict review, sharing, tasks and My work
2026-10-14 09:24:02 -05:00
james.maesandClaude Opus 5.5 94494fc34f Merge origin/main into feature/user-preferences
Re-vendors the core document from qqq develop (1.3.0, with preferences)
plus main's vendored jobs and uploads operations; POST /uploads is now
spec'd, so its off-spec entry goes and the ceiling drops to 20.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:18:10 -05:00
james.maesandClaude Opus 5.5 2bdd7d980b test(e2e): resend the concurrent edit until the conflict banner shows (mock stream attach race on mobile)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:12:33 -05:00
james.maesandClaude Opus 5.5 ff1ea58cf1 fix(ui-api): drop the POST /uploads off-spec exception now that it is vendored
Main vendored /qqq/v1/uploads (#132) but kept the exception, which fails the contract test's 'only shrinks' check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:57:35 -05:00
james.maesandClaude Opus 5.5 58cca84fda Merge origin/main into feature/contingent-possible-values
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:55:43 -05:00
james.maesandClaude Opus 5.5 91b335a42d fix(ui-api): extension discovery does not import the query client
The test setup resets discovery after each test; importing the query
client there pulled sonner into every test document and broke the
toast-observation tests. The version query key now lives beside the
discovery code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:53:38 -05:00
james.maesandClaude Opus 5.5 617bd1962d chore(ui-api): drop the POST /uploads off-spec exception now that the vendored document has it (ceiling 20)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:40:20 -05:00
james.maes cfba697339 Merge remote-tracking branch 'origin/main' into fix/collaboration-live-review
# Conflicts:
#	src/components/records/RecordView.tsx
#	src/lib/api/openapi/qqq-v1.json
2026-10-14 08:38:41 -05:00
james.maes 3db5a0507d Merge pull request #132 from QRun-IO/feature/vendor-alerts-sqlwb-ui-api
feat(ui-api): vendor the alerts and sql-workbench extension documents (off-spec 46 -> 21)
2026-10-14 08:30:26 -05:00
james.maes 36c07c7aa7 Merge pull request #123 from QRun-IO/feature/data-analysis-saved
feat(data-analysis): save, share, star, export and watch analyses
2026-10-14 08:29:50 -05:00
james.maesandClaude Opus 5.5 2ba63346f0 Merge origin/main into feature/contingent-possible-values
Picks up the off-spec ceiling fix (#131) that broke the UI API contract test on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:28:11 -05:00
james.maes a1c4a7de1a Merge pull request #126 from QRun-IO/feature/run-in-background
feat(processes): run in background, background-job banner, widget refresh
2026-10-14 08:28:03 -05:00
james.maes 4d4ba49e8f Merge pull request #129 from QRun-IO/fix/chart-sr-table-overflow
fix(widgets): chart data table overflow and doubled widget section headings
2026-10-14 08:27:50 -05:00
james.maesandClaude Opus 5.5 4f2cf6eb64 feat(ui-api): qbit presence from GET /version; no hard-coded qbit names (#108)
- ui-api-extensions: one shared /version discovery (isUiApiExtensionInstalled,
  useUiApiExtensionInstalled, UI_API_EXTENSION names); data-analysis
  delegates to it; reset on sign-out and user change.
- Alerts: presence from the "alerts" extension instead of the hard-coded
  alerts app (per-user access still from the inbox summary's 403/404);
  record-panel actions come from the panel payload (permission-aware
  hrefs) instead of hard-coded alertsAcknowledge / alertsSnooze /
  alerts_alert.
- Assistant: /assistant/config is probed only when the "assistant"
  extension is registered.
- Mocks announce their extensions from one /version handler; tests and
  mocked e2e routes updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:23:53 -05:00
james.maesandClaude Opus 5.5 4ad866286b feat(ui-api): vendor the assistant extension document
qbit-assistant#19 serves its /qqq/v1/assistant operations as the
assistant UI API extension (1.0.0). Vendor it and drop its 13 calls
from the off-spec list (ceiling 21 -> 8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:05:12 -05:00
james.maes 09d1c2b793 Merge remote-tracking branch 'origin/main' into feature/user-preferences 2026-10-14 07:57:19 -05:00
james.maesandClaude Opus 5.5 dc6b1e2373 feat(ui-api): vendor the alerts and sql-workbench extension documents
The Alerts (qbit-alerts#19) and SQL Workbench (qbit-sql-workbench#21)
qbits now serve their /qqq/v1 operations as UI API extensions (1.0.0).
Vendor both documents and drop their 25 calls from the off-spec list
(ceiling 46 -> 21).

- Inbox actions call their literal operations (/alerts/inbox/read,
  /unread, /dismiss).
- The alerts extension removed the record-panel watch operation; the
  record panel drops its "Watch alerts for this record" button and
  keeps showing the deprecated watching state read-only. Following a
  record belongs to the collaboration panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:54:15 -05:00
james.maes 96b789088e Merge remote-tracking branch 'origin/main' into feature/data-analysis-saved 2026-10-14 07:53:09 -05:00
james.maes a75fd8b5f6 Merge remote-tracking branch 'origin/main' into fix/collaboration-live-review 2026-10-14 07:51:46 -05:00
james.maes 728dd2b559 Merge pull request #131 from QRun-IO/fix/off-spec-ceiling-uploads
fix(ui-api): off-spec ceiling 46 while POST /uploads waits for qqq#1181
2026-10-14 07:51:31 -05:00
james.maes 635def9ba8 Merge remote-tracking branch 'origin/main' into feature/data-analysis-saved 2026-10-14 07:49:51 -05:00
james.maes bdc38f08e9 Merge pull request #118 from QRun-IO/feature/data-analysis-compare
feat(data-analysis): compare, correlation, crosstab and shift explorers
2026-10-14 07:49:24 -05:00
james.maes df9265e877 Merge pull request #106 from QRun-IO/feature/data-analysis-pivot
feat(data-analysis): pivot / group-by explorer
2026-10-14 07:49:10 -05:00
james.maesandClaude Opus 5.5 bb05d81cdb chore(ui-api): vendor the 1.3.0 core document (preferences, bulk load, realtime)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:46:59 -05:00
james.maesandClaude Opus 5.5 54a2db37b7 Merge origin/main into feature/contingent-possible-values
Resolved FilterBuilder.tsx: keep both the contingent dependencyField and main's supportedOperators.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:44:30 -05:00
james.maes 3dfc93612f Merge remote-tracking branch 'origin/main' into fix/collaboration-live-review 2026-10-14 07:20:24 -05:00
james.maesandClaude Opus 5.5 89c35e9f2c fix(ui-api): off-spec ceiling 46 while POST /uploads waits for qqq#1181
POST /uploads: vendor once QRun-IO/qqq#1181 ships in an RC, then lower to 45.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:16:05 -05:00
james.maes 2e43dc23d8 Merge pull request #130 from QRun-IO/feature/next-rc21-prep
chore(#712): prepare Next UI 1.0.0-RC.21
2026-10-14 07:13:24 -05:00
james.maes 5efb0782cd Merge remote-tracking branch 'origin/main' into feature/user-preferences
# Conflicts:
#	src/lib/query-client.ts
2026-10-14 07:13:10 -05:00
james.maes bac9215e32 Merge pull request #112 from QRun-IO/feature/ui-api-off-spec-ceiling
feat(ui-api): cap off-spec calls, vendor the ESB extension, CODEOWNERS (closes #107)
2026-10-14 07:09:22 -05:00
james.maes cc13b045f4 Merge pull request #113 from QRun-IO/feature/collaboration-record-panel
feat(collaboration): record panel (comments, files, activity), tags and follow
2026-10-14 07:09:09 -05:00
james.maes 1e82e8696d Merge remote-tracking branch 'origin/feature/data-analysis-compare' into feature/data-analysis-saved 2026-10-14 07:09:07 -05:00
james.maes 62d76af238 Merge remote-tracking branch 'origin/feature/data-analysis-pivot' into feature/data-analysis-compare 2026-10-14 07:08:32 -05:00
james.maes 974407da47 Merge remote-tracking branch 'origin/main' into feature/data-analysis-pivot 2026-10-14 07:07:52 -05:00
james.maesandClaude Opus 5.5 bfe8a6e31c chore(#712): prepare Next UI 1.0.0-RC.21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:07:41 -05:00
james.maes a6c95b1ec7 Merge pull request #125 from QRun-IO/feature/assistant-file-attach
feat(assistant): attach a file in the assistant panel (bulk load)
2026-10-14 07:05:59 -05:00
james.maesandClaude Opus 5.5 e2838496b6 test(api): cover directory search, presence beacon, my tasks and the realtime transport
Lifts src/lib/api function coverage above its 93% threshold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:08:46 -05:00
james.maesandClaude Opus 5.5 05be095a8d fix(realtime): stop the subscription sync loop when the server neither activates nor refuses a channel
The sync loop re-added a channel forever (never yielding to the event loop) when an update
response listed it as neither active nor refused, exhausting the heap of a test worker.
Channels a response leaves unresolved are now treated as refused until the next reconnect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:08:45 -05:00
james.maesandClaude Opus 5.5 3fcefdb6b4 fix(collaboration): keep the established edit payload and not-found copy outside collaboration tables
Versioned, dirty-only edit saves apply only to tables with PRESENCE or
FOLLOW (acceptance REC-009, REC-016, REC-023 expect the full payload and
defaults on other tables). The not-found view keeps its original wording
and Back button; the neutral wording and Request access appear only on
SHARING tables, below the Back button (REC-003).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:41:18 -05:00
james.maesandClaude Opus 5.5 5cafa460b0 test(acceptance): flip the playground theme live from the user menu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:30:52 -05:00
james.maesandClaude Opus 5.5 a0a26b547b fix(collaboration): reset the touch minimum on tag chip controls with important
The global 44 px touch-target rule is unlayered CSS, so it outranks the
layered min-h-0/min-w-0 utilities whatever their specificity, and tag
chips on touch screens were still 50 px tall. The chip controls now use
the important modifier (min-h-0!, min-w-0!): chips measure 26 px on a
Pixel 7 while each control keeps a 44 x 44 px transparent touch overlay.
The record-panel screenshot test asserts the chip height.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:28:57 -05:00
james.maesandClaude Opus 5.5 8408f6c445 fix(collaboration): hide empty activity details and word old audit summaries
- An activity entry shows "Details" only when at least one permitted
  detail has a message or a value change, so audits whose details are
  all empty no longer leave a Details header over nothing.
- Summaries are shown as the service sends them. Older core audit
  summaries of the form "Record was Edited" read "Edited", and "Record
  was Inserted" reads "Created" (other verbs in sentence case).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:16:32 -05:00
james.maesandClaude Opus 5.5 ebcd8aec30 feat(collaboration): show people by display name, falling back to the id
The service is adding display names beside user ids. The UI now renders
them when present and falls back to the id otherwise:

- Tasks: "Assigned to <assigneeDisplayName>", plus "Created by ..." and,
  for done tasks, "Completed by ...".
- Files: the uploader's createdByDisplayName.
- Comments already used authorDisplayName.
- Share results prefer the share's toDisplayName.

src/types/collaboration.ts gains assigneeDisplayName,
createdByDisplayName and completedByDisplayName (tasks),
createdByDisplayName (attachments, kept by the normalizer),
fromDisplayName/toDisplayName (shares) and requesterDisplayName (access
requests). The vendored extensions/collaboration.json is unchanged until
the service publishes them. The MSW fixtures carry the names, and the
mock task service records who completed a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:15:54 -05:00
james.maesandClaude Opus 5.5 ed0f056c85 test(collaboration): type the self-exclusion people fixture
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:15:04 -05:00
james.maesandClaude Opus 5.5 acfd28eea1 fix(collaboration): leave yourself out of share recipients and keep the dialog on screen
- The share picker drops the signed-in user from the people results,
  matching the app's user id (the session email) against each entry's
  user id and email, case-insensitively. The service is leaving the
  caller out too; this guards older services. The assign picker still
  lists you, since you may assign a record to yourself.
- Collaboration dialogs are a flex column no taller than the viewport
  less 2rem: the title bar stays put and only the body scrolls, and only
  when it does not fit, so there is no second page scroll.
- The link field shows the URL exactly as the service returns it.
- The MSW directory carries the mock session's own entry so the
  exclusion is exercised; unit and e2e tests cover it, and the e2e
  screenshot run checks the dialog fits a 1000 px viewport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:12:26 -05:00
james.maesandClaude Opus 5.5 365f57c21a fix(collaboration): agree counts with their nouns
"1 people viewing", "1 unread mentions" and "1 followers" now read
"1 person viewing", "1 unread mention" and "1 follower". A small
pluralize(count, singular, plural?) helper in string-utils (there was
none) formats each count; a paged follower count still reads "N+
followers".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:10:41 -05:00
james.maesandClaude Opus 5.5 b0e30cfe36 fix(collaboration): compact tag chips on touch screens and keep copy-id by the title
- On touch screens the global 44 px touch-target rule lifted each tag's
  remove button (and Add tag) to 44 x 44, so chips grew into ~50 px pills,
  two per row. The chip controls now keep their drawn size and carry a
  transparent overlay of at least 44 x 44 px as the touch target, so chips
  stay compact inline chips that wrap naturally at every width.
- The record collaboration header (presence, owner/assignee, Assign and
  Share) was mounted between the follow bell and the copy-record-id
  button; when it wrapped it left the copy button alone on its own row.
  The header now comes after the copy button and inline actions and takes
  its own full-width row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:09:53 -05:00
james.maesandClaude Opus 5.5 9aacbf0052 fix(records): one heading for a widget section titled like its card
A record section that shows a card widget drew the section label as a
heading and then the card's own title, so "Live Progress", "Chunks" and
other child lists read twice. When the card's label equals the section
label, the card's title stands alone and the section keeps the label as
its accessible name. Non-card widgets and differing labels are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:08:34 -05:00
james.maesandClaude Opus 5.5 3e140cb6c6 test(collaboration): expect the "@Name" mention chip in the record panel e2e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:07:27 -05:00
james.maesandClaude Opus 5.5 d7266d2951 fix(collaboration): render a mention as one "@Name" chip
A mention token `@[Name](user:id)` rendered the literal "@" outside the
chip, followed by a gap, and the chip could wrap across two lines. The
comment renderer now drops the token's "@" and the chip draws "@Name"
itself, with no wrapping inside the chip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:07:21 -05:00
james.maesandClaude Opus 5.5 b2a9e28ce0 fix(collaboration): separate Show and Reopen and keep disabled Post readable
- A collapsed resolved thread now reads "Resolved thread", then two
  separate actions, Show and Reopen, instead of "ShowReopen" run together.
  Resolve and Reopen sit in the thread's action row with Edit, Delete
  and Reply.
- The thread actions use the primary text token, which is lightened on
  dark surfaces (about 9.6:1 on the dark card, 5.2:1 on the light card).
- A disabled Post comment button uses the muted surface and muted
  foreground tokens with a border, instead of a 50% faded blue fill that
  was hard to read in dark mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:06:43 -05:00
james.maesandClaude Opus 5.5 c6f0fc87f6 fix(widgets): keep the hidden chart data table from widening the page
The screen-reader table of chart values had sr-only on the table itself.
A table cannot shrink to the 1px sr-only sets, so on chart pages it widened
main-content (915px at a 390px viewport), a horizontal scroll at phone widths.
Wrap it in an sr-only div instead; the table stays in the accessibility tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:37 -05:00
james.maesandClaude Opus 5.5 f1bff85157 fix(help): no RSC prefetch on dashboard create links; NAV-017 includes the Help Lab fixture
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:51:57 -05:00
james.maesandClaude Opus 5.5 33f67fa8a8 feat(help): help everywhere — shared help icons, long-help panel, help search, author links
- HelpIcon (summary tooltip, keyboard, More help) and one lazy HelpPanel
- Help on app, table, section, field labels, list columns, widgets,
  processes, steps, reports and record/table actions
- Help search in the command palette and search page (GET /help/search,
  gated on the 'help' feature)
- Author mode: Add help / Edit help links for helpContent authors
- Vendored UI API 1.2.0 with typed HelpContentV1; acceptance HLP-001..006

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:40:14 -05:00
james.maes 69aef5f05c Merge remote-tracking branch 'origin/main' into feature/collaboration-presence-share-tasks 2026-10-13 22:40:11 -05:00
james.maesandClaude Opus 5.5 db0b421678 test(assistant): list POST /uploads as an off-spec call until the UI API document is re-vendored
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:36:25 -05:00
james.maesandClaude Opus 5.5 01fb7f6965 fix(preferences): only call /preferences when the server lists the feature
Servers before QQQ's preferences release 404 on /qqq/v1/preferences; the
user menu, bell gear and Preferences page now check GET /version features
first (once per page load) and show only the device Display section there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:35:19 -05:00
james.maes d8bc9282db Merge branch 'feature/data-analysis-compare' into feature/data-analysis-saved 2026-10-13 22:31:44 -05:00
james.maesandClaude Opus 5.5 1a6c126721 chore(ui-api): re-vendor the data-analysis extension document built on QQQ RC.6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:31:41 -05:00
james.maes f833454c17 Merge branch 'feature/data-analysis-pivot' into feature/data-analysis-compare 2026-10-13 22:31:35 -05:00
james.maesandClaude Opus 5.5 d01d470b6e chore(ui-api): re-vendor the data-analysis extension document built on QQQ RC.6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:31:31 -05:00
james.maes 67cf8ed57e Merge remote-tracking branch 'origin/feature/data-analysis-compare' into feature/data-analysis-saved 2026-10-13 22:30:11 -05:00
james.maes 0997deb12c Merge remote-tracking branch 'origin/feature/data-analysis-pivot' into feature/data-analysis-compare 2026-10-13 22:29:31 -05:00
james.maes 196c899389 Merge remote-tracking branch 'origin/main' into feature/data-analysis-pivot 2026-10-13 22:28:49 -05:00
james.maesandClaude Opus 5.5 7c755d5741 test(acceptance): contingent specs accept trailing-slash record URLs and use field labels in the filter builder
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:26:11 -05:00
james.maesandClaude Opus 5.5 38204cdacc feat(processes): run in background, background-job banner, widget refresh
Generic support for QQQ durable jobs (QRun-IO/qqq#1178, #1181); names no
qbit.

- Run in background: on a non-final step of a process whose metadata says
  durableJobEligible, a secondary button POSTs /processes/{name}/jobs with
  the values collected so far, the launch selection (filter or record ids)
  and startAfterStep, then opens the job record. Server errors (400, 403,
  404, 429) are shown.
- When a step's values carry durableJobId (a run over the server's
  threshold), the process shows "Running as a background job" with an Open
  job link, plus its summary lines.
- Widgets whose metadata sets defaultValues.refreshSeconds re-fetch on that
  interval (minimum 2s) while the page is visible, keeping the old data on
  screen; this is how the Jobs app home shows live progress.
- Types for durableJobEligible / durableJobAlways; the processJob client.
- Re-vendored the UI API 1.2.0 document with the jobs and uploads
  operations.

Bundle (report-only budget): total JS +1.8 KB gzip (1217.5 -> 1219.3 KB),
/app/_/_ first-load +0.9 KB (350.4 -> 351.3 KB), 126 chunks unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:24:59 -05:00
james.maesandClaude Opus 5.5 622ae28ceb fix(collaboration): keep record header controls together and readable at phone width
- Presence avatars, owner/assignee and the Assign/Share buttons are now
  three wrapping groups, so Share no longer drops onto its own line beside
  free space. Owner and Assignee render as muted label plus value.
- The avatar row is omitted when nobody else is viewing.
- The people search shows a "Search by name" placeholder.
- My work uses the page gutter instead of doubling it at 390 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:19:10 -05:00
james.maesandClaude Opus 5.5 63dcf7e144 feat(assistant): attach a file in the assistant panel (bulk load)
A paperclip in the composer uploads the file with POST /qqq/v1/uploads as
the user (QRun-IO/qqq#1181) and attaches its opaque ref. The file stays
on the server, in object storage. Attachment chips can be removed. Each
message carries the attachments in pageContext.attachments
({uploadRef, fileName}, at most 5), so the assistant's bulk-load tools
(QRun-IO/qbit-assistant#38) can profile, map, dry-run and load the file.

The attach button is offered only where saved bulk load profiles exist
(storeSavedBulkLoadProfile in the metadata). Attachments belong to one
conversation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:15:11 -05:00
james.maesandClaude Opus 5.5 9d2ece4228 test(secured-data): open the filter builder like the other specs; wait for the record before the banner shot
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:09:14 -05:00
james.maesandClaude Opus 5.5 1bb7db1924 fix(tasks): show a task's done/open toggle immediately
The checkbox was bound to the server status, so a click snapped back
until the refetch landed: users saw no response and the Playwright check
failed on every browser. The toggled state now shows at once, the box is
disabled while the write is in flight, a failed write rolls it back, and
the entry clears when the server status agrees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:09:03 -05:00
james.maesandClaude Opus 5.5 cce505e5a5 fix(data-analysis): readable correlation heatmap in dark mode, repair text encoding
The heatmap uses translucent tints over the theme background with the
theme's text colour (AA checked over both themes) and aligned row
labels; restore the en and em dashes an earlier edit had mis-encoded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:03:38 -05:00
james.maesandClaude Opus 5.5 2625355d88 fix(palette): list "Go to My work" after favorites
Favorites lead the command palette; the Work group came first and broke
the ordering test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:56:32 -05:00
james.maesandClaude Opus 5.5 6fa2ccf301 fix(realtime): hand over leadership on pagehide and never revive an ended session
- A closing or bfcache-frozen leader tab posts "bye" and drops its stream
  on pagehide, so a follower takes over at once (React cleanups never run
  on tab close; before, the handover waited for the 6.5 s lease expiry).
- An ended client stops leasing: a still-mounted surface could otherwise
  re-announce channels and make live tabs defer to a leader that never
  connects.
- A closed client drops its BroadcastChannel so late releases cannot
  post to a closed channel.
- realtimeClient() replaces an ended singleton, so signing in again as the
  same user restores realtime; the cache bridge is per client.
- The serialization test asserted against a transport mock that lost its
  state; it now checks the remove follows the in-flight add.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:56:31 -05:00
james.maesandClaude Opus 5.5 ac5aa368d7 fix(collaboration): load the edit, presence and header layers only for tables that enable them
With the qbit installed, every edit form and record screen loaded the
collaboration edit layer and subscribed a record channel, whatever the
table's features. The edit layer then threw outside a QContextProvider,
which broke 26 EntityForm and RecordViewHeader tests.

- The edit layer loads for PRESENCE or FOLLOW tables, or once a save is
  rejected with 409; the header for realtime, sharing, assignment or
  ownership; table presence for realtime tables only.
- Record and table channels subscribe only where core publishes events.
- Realtime and presence hooks read the user id without requiring the
  provider.
- "Keep mine" closes the review before re-sending; a newer stream hint
  never replaces a pending 409 and its server version.
- The theirs/yours comparison stacks on phones.
- Test: a 409 sends nothing else and keeps the typed value; "Keep mine"
  re-sends the dirty values with the new expectedVersion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:56:30 -05:00
james.maesandClaude Opus 5.5 3509ff8452 feat(data-analysis): save, share, star, export and watch analyses
Every analysis view can be saved (optionally shared) as a
data_analysis_saved record, opened again with ?view=saved (re-run as the
viewer, with a star toggle), exported as CSV, XLSX or chart PNG, and
handed to SQL Workbench as a draft when it is installed. Profiles offer
Watch this metric, which creates a data_analysis_watch and previews its
series against the detector's baseline. Charts clip their tooltips so
nothing widens the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:56:20 -05:00
james.maesandClaude Opus 5.5 46436866be feat(secured-data): masked values, reveal, restricted filters and the break-glass banner
Renders fields the server marks MASKED or REVEALABLE as masked values with a
lock chip; offers Reveal (reason dialog, countdown, re-mask on expiry) when
the secured-data extension is present; filter builders, quick filters and
column menus offer only the allowed operators; restricted fields are
read-only and never sent in insert/update payloads; a break-glass banner
shows an active session with a countdown. Reveal dialog and banner are
lazy-loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:55:14 -05:00
james.maesandClaude Opus 5.5 35cc9da17c feat(preferences): dark mode toggle in the user menu
The old Preferences dialog let a user flip the theme without leaving the
page; the menu now keeps that as a live Dark mode switch next to the
Preferences link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:52:09 -05:00
james.maesandClaude Opus 5.5 1a7b8e371f wip(collaboration): realtime, presence, conflicts, sharing, tasks and My work (Codex draft, tests pending)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:46:44 -05:00
james.maesandClaude Opus 5.5 d011c546c0 test(acceptance): follow the sample rename to placeAddress / placeAddressPick
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:28:21 -05:00
james.maes e2e0c984ce Merge remote-tracking branch 'origin/main' into feature/user-preferences
# Conflicts:
#	src/lib/query-client.ts
#	src/mocks/handlers/index.ts
2026-10-13 21:21:23 -05:00
james.maes 60d0b0ffc7 Merge pull request #119 from QRun-IO/feature/next-rc20-prep
chore(#712): prepare Next UI 1.0.0-RC.20
2026-10-13 21:05:42 -05:00
james.maesandClaude Opus 5.5 805ce7d72c refactor(preferences): export OneTimeSecretDialog for reuse by process results
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:44:52 -05:00
james.maesandClaude Opus 5.5 690737fa46 chore: normalize line endings to LF
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:43:45 -05:00
james.maesandClaude Opus 5.5 1f4a38b9ff feat(preferences): Preferences page, notification settings drawer and section actions
Renders every /qqq/v1/preferences section generically from its metadata:
scalar fields with the standard editors, row lists as a table (cards on
narrow widths), read-only reasons, partial saves, section/list/row actions
with input and confirmation dialogs, and a show-once dialog for one-time
secrets. The user menu opens /app/preferences (device display settings are
its Display section) plus any section marked showInUserMenu; the bell
popover and /app/notifications get a gear that opens the alerts section in
a drawer. Merges main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:42:59 -05:00
james.maesandClaude Opus 5.5 236fa03923 feat(forms): contingent possible values across forms, bulk edit, processes and filters
- Contingent fields (possibleValueSourceDependsOn, or ${input.x} in a
  legacy filter) re-query only when their dependencies change, are
  disabled with "Choose <A> first" until set (DISABLE_UNTIL_SET), and
  clear with an inline notice when A changes and the value no longer
  fits; clears cascade A -> B -> C. Loading a saved record never clears.
- Server "not a valid choice" errors map to the dependent field.
- Bulk edit: B requires A enabled and set; process forms use the
  screen's metadata and values; quick-create gets sibling metadata.
- Filter builder: sends useCase filter (contingent fields showed no
  options before) and narrows B from A's EQUALS/IN criteria.
- Re-vendored UI API 1.2.0 document.
- Unit tests and real-backend acceptance specs (addressBook sample).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:40:14 -05:00
james.maesandClaude Opus 5.5 1afd72ad32 chore(#712): prepare Next UI 1.0.0-RC.20
Source is main at 438eda3 (merge of #111). Merged since RC19: #92,
#104, #109, #103, #105 and #111. The new screens need QQQ 4.1.0-RC.6;
assistant presentation blocks need qbit-assistant 0.1.0-RC.9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:40:08 -05:00
james.maesandClaude Opus 5.5 7c130187b9 fix(assistant): resolve the markdown merge (GFM and breaks from main, mention schema from collaboration)
The previous merge commit left conflict markers in AssistantMarkdown.tsx.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:39:25 -05:00
james.maesandClaude Opus 5.5 35de9ef8bb test(e2e): keep every collaboration spec (MSW) out of the route-mocked e2e config
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:37:00 -05:00
dependabot[bot] 68eb31d18d build(deps): Bump rapidoc from 9.3.8 to 10.1.0
Bumps [rapidoc](https://github.com/rapi-doc/RapiDoc) from 9.3.8 to 10.1.0.
- [Release notes](https://github.com/rapi-doc/RapiDoc/releases)
- [Commits](https://github.com/rapi-doc/RapiDoc/compare/v9.3.8...v10.1.0)

---
updated-dependencies:
- dependency-name: rapidoc
  dependency-version: 10.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-14 01:32:26 +00:00
james.maesandClaude Opus 5.5 1f624a2544 feat(data-analysis): compare, correlation, crosstab and shift explorers
An Explore menu on the query toolbar opens Pivot, Compare (current query
vs the whole table, a saved view or another filter, with deltas and
plain significance sentences), Correlation (an accessible heatmap with a
scatter and trend line per pair), Crosstab (counts, shares, chi-square)
and Shift over time. Layouts live in the URL, every number drills to the
records, and tables are the only horizontal scrollers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:31:46 -05:00
james.maes 438eda36e3 Merge pull request #111 from QRun-IO/feature/assistant-answer-presentation
feat(assistant): readable answers: GFM markdown and presentation blocks
2026-10-13 20:29:51 -05:00
dependabot[bot] 7a75f5762c build(deps): Bump the github-actions group across 1 directory with 4 updates
Bumps the github-actions group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [actions/setup-node](https://github.com/actions/setup-node), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/checkout` from 6.1.0 to 7.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.1.0...3d3c42e5aac5ba805825da76410c181273ba90b1)

Updates `actions/setup-node` from 7.0.0 to 7.1.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1)

Updates `actions/upload-artifact` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

Updates `actions/download-artifact` from 8.0.1 to 8.0.2
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c...9000827ccba6bdab643e8b6fd33ac0654aef8333)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-14 01:16:56 +00:00
james.maes d1c181aae9 Merge pull request #105 from QRun-IO/feature/collaboration-favorites
feat(collaboration): favorites, server recents and palette Favorites
2026-10-13 20:11:58 -05:00
james.maes c09dd23fc3 Merge pull request #103 from QRun-IO/feature/data-analysis-profiles
feat(data-analysis): column and table profile panels with drill-through
2026-10-13 20:11:32 -05:00
james.maesandClaude Opus 5.5 9b006df8b2 fix(collaboration): replace the native file picker text with a themed button
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:11:01 -05:00
james.maesandClaude Opus 5.5 0ef9fa7273 test(collaboration): count CONTAINS_ALL and separate HTML blocks in the sanitizer test
OPERATOR_CONFIG now covers the 25 operators of the UI API FilterCriteria enum.
The markdown test put HTML and markdown on adjacent lines, which CommonMark
parses as one HTML block that skipHtml drops whole; blank lines end each block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:11:00 -05:00
james.maesandClaude Opus 5.5 3d30490859 fix(collaboration): make record calls resolvable against the UI API document
The contract test could not match ${recordPath(record)}/... or the computed
resolve/reopen and preview/download segments to operations of the vendored
collaboration extension. Register recordPath as a path helper and write the
two-way branches as literal calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:10:58 -05:00
james.maesandClaude Opus 5.5 4376c28896 feat(ui-api): cap the off-spec calls, vendor the ESB extension, CODEOWNERS (#107)
- OFF_SPEC_CEILING (45) caps OFF_SPEC_CALLS; the contract test fails if it grows.
- Vendor the qqq-esb extension document (extensions/esb.json, 1.0.0): the five
  ESB calls are now spec'd and leave the off-spec list.
- CODEOWNERS on src/lib/api/openapi/; CONTRIBUTING documents the merge order
  (extension, then vendored document, then UI).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:04:50 -05:00
james.maesandClaude Opus 5.5 5fd1015b80 fix(assistant): concrete chart palette (unemitted --color-chart-1 rendered black)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:03:03 -05:00
james.maesandClaude Opus 5.5 4604312b18 feat(assistant): readable answers: GFM markdown and presentation blocks
Owner priority: an answer table rendered on one line with no links.

- Root cause: AssistantMarkdown rendered react-markdown without
  remark-gfm, so GFM tables never parsed and their lines collapsed into
  one paragraph. Add remark-gfm and remark-breaks (sanitize schema, link
  and image rules unchanged); table styling for light and dark.
- SSE: multi-line data fields keep their newlines.
- New presentation message part (qbit-assistant contract): summary,
  metrics, table with row links, lazy-loaded recharts bar/pie/line,
  filter and record links, follow-up actions; unknown kinds ignored,
  markdown fallback. Links only for internal /app hrefs.
- Tests with the owner's exact answer, split across stream deltas, and
  every block kind; Storybook example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:50:37 -05:00
james.maes 295cf16bbf Merge remote-tracking branch 'origin/main' into feature/data-analysis-profiles 2026-10-13 19:42:16 -05:00
james.maes fc5f5d946d Merge remote-tracking branch 'origin/feature/collaboration-favorites' into feature/collaboration-record-panel 2026-10-13 19:42:00 -05:00
james.maesandClaude Opus 5.5 fce8aad1ad wip(collaboration): record panel, tags and follow bell (Codex draft, tests pending)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:41:58 -05:00
james.maes 14ec5cc390 Merge remote-tracking branch 'origin/main' into feature/collaboration-favorites 2026-10-13 19:41:26 -05:00
james.maes 35249098bb Merge pull request #109 from QRun-IO/fix/acceptance-esb-extension
fix(acceptance): check the esb UI API extension instead of EsbRouteProvider
2026-10-13 19:40:24 -05:00
james.maesandClaude Opus 5.5 d4f5bf9b7b fix(acceptance): check the esb UI API extension instead of EsbRouteProvider
QRun-IO/qqq#1129 moved the /qqq/v1/esb endpoints onto spec classes registered as
the esb UI API extension and removed EsbRouteProvider, so the fixture no longer
compiled against qqq develop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:19:15 -05:00
james.maesandClaude Opus 5.5 8f7e741bac chore(ui-api): vendor the qbit-collaboration 1.0.0 extension document
Generated from QRun-IO/qbit-collaboration src/main/resources/openapi/qqq-v1/collaboration.yaml
(PR #8) for the UI API contract test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:13:39 -05:00
james.maes 77d61670c3 Merge remote-tracking branch 'origin/main' into feature/collaboration-favorites 2026-10-13 19:12:32 -05:00
james.maes 33d7d6e9c8 Merge remote-tracking branch 'origin/feature/data-analysis-profiles' into feature/data-analysis-pivot 2026-10-13 19:12:08 -05:00
james.maesandClaude Opus 5.5 dc1afc146f test(data-analysis): pivot e2e mocks the version endpoint and only the metaData root
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:12:07 -05:00
james.maes 343e603874 Merge remote-tracking branch 'origin/main' into feature/data-analysis-profiles 2026-10-13 19:09:57 -05:00
james.maes df8e27708f Merge pull request #104 from QRun-IO/fix/ui-api-contract-picker-paths
fix(ui-api): literal related-record picker paths; vendor UI API 1.2.0
2026-10-13 19:08:19 -05:00
james.maesandClaude Opus 5.5 e598437ef5 feat(data-analysis): pivot / group-by explorer
A Pivot button on the query toolbar opens a full-width explorer for the
current table and filter: rows, columns, date buckets and numeric bins,
measures (count, sum, average, min, max, distinct, percentile), totals,
Other groups, hidden small cells, a drill on every cell, and an optional
chart. The layout is in the URL (view=pivot&pivot=...), so it can be
shared and Back returns to the records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:02:52 -05:00
james.maesandClaude Opus 5.5 816dad4a37 fix(data-analysis): ask capabilities only when the server registers the extension
Apps without qbit-data-analysis no longer see a failed capabilities
request (the real-backend acceptance diagnostics flagged the 404): the UI
reads GET /version once per page load and asks capabilities only when the
data-analysis extension is registered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:02:40 -05:00
james.maesandClaude Opus 5.5 277116e35e feat(collaboration): favorites, server recents and palette Favorites (record collaboration phase b)
Star toggle on record, table and app headers; a header Favorites menu
(folders, Unavailable entries, empty state, Manage link); the command
palette's Favorites group first, server-side recently viewed records with
the local fallback, and Star/Unstar this page. Everything collaboration
loads lazily and only when the qbit's UI API extension is present (GET
/version extensions, or a favorites probe on older backends). MSW mocks,
unit tests, and a Playwright suite with screenshots run on chromium,
firefox, webkit, mobile and tablet in CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:45:19 -05:00
james.maesandClaude Opus 5.5 8a0fbbd334 fix(ui-api): literal related-record picker paths; vendor UI API 1.2.0
The picker (#96) and the UI API contract test (#92) crossed on main: the picker
built its paths through a helper, which the contract test reports as unlisted
computed call paths, and the vendored document predated the picker operations.
The four picker calls now use literal templates, and the vendored document is
QQQ develop's 1.2.0 (picker operations, client channels).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:43:02 -05:00
james.maesandClaude Opus 5.5 eb3ad6853c chore(ui-api): vendor the data-analysis extension document
The contract test now finds every data-analysis call in the vendored
extension document (qbit-data-analysis src/main/resources/openapi/qqq-v1/data-analysis.yaml,
as JSON); call paths are written as literals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:35:25 -05:00
james.maesandClaude Opus 5.5 b7f3c71b74 fix(data-analysis): themed chart palette, combined time series, formatted cards
Charts use the shared widget palette (theme variables were dropped by
Tailwind), the time series draws count and its moving average in one
chart with labels from the API, metric cards show the API's labels and
formatted values, Run exact appears only for estimates, and warnings
render as notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:33:44 -05:00
james.maesandClaude Opus 5.5 6316df0b4a feat(data-analysis): column and table profile panels with drill-through
When qbit-data-analysis is installed and the user can analyze a table,
the query grid offers Analyze on each analyzable column header and an
Analyze table button. The panels render the extension's typed results
(metrics, charts as data, top and distinct values, completeness,
candidate keys, duplicates), run exact analyses with progress, and every
number drills through to the filtered record list (applied in place on
the same table). Charts are recharts in a lazily loaded chunk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:33:44 -05:00
james.maes 455d8a587c Merge pull request #92 from QRun-IO/feature/ui-api-version-contract
feat(ui-api): UI API version check, bundled OpenAPI document and contract test
2026-10-13 18:32:30 -05:00
james.maes 88ac531e50 Merge pull request #102 from QRun-IO/feature/next-rc19-prep
chore(#712): prepare Next UI 1.0.0-RC.19
2026-10-13 17:55:31 -05:00
james.maesandClaude Opus 5.5 16a3c0797d chore(#712): prepare Next UI 1.0.0-RC.19
Source is main at a2f2318 (merge of #101). Merged since RC18: #96
(related-record picker) and #101 (assistant Read-only / Write switch).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:51:25 -05:00
james.maes a2f231862c Merge pull request #101 from QRun-IO/feature/assistant-write-mode
feat(assistant): Read-only / Write switch per conversation
2026-10-13 17:50:01 -05:00
james.maes 11a8f275d4 Merge pull request #96 from QRun-IO/feature/related-record-picker
feat(picker): generic related-record picker (single, multi-add with review, link)
2026-10-13 17:44:20 -05:00
james.maesandClaude Opus 5.5 248bca9f43 test(ui-api): list the new assistant action calls as off-spec until qbit-assistant ships its extension
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:27:11 -05:00
james.maesandClaude Opus 5.5 220f106d62 Merge main into feature/ui-api-version-contract
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:26:44 -05:00
james.maesandClaude Opus 5.5 c2d0f61c15 feat(ui-api): name the client on every request (X-QQQ-Client: next-ui)
For audit attribution (QRun-IO/qqq docs/UI-API-CLIENT-CHANNELS.md); servers
that predate client channels ignore the header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:30:59 -05:00
james.maes 509ab237cf Merge pull request #100 from QRun-IO/feature/next-rc18-prep
chore(#712): prepare Next UI 1.0.0-RC.18
2026-10-13 16:30:03 -05:00
james.maesandClaude Opus 5.5 641f4718e5 feat(assistant): Read-only / Write switch per conversation
When the server offers write mode (config.features.writeMode), the panel
header shows a switch for the active conversation: Read-only (default)
or Write. It PATCHes writeMode; the server decides whether the user may
(policy, role, writable tables) and answers 403 otherwise. In Write mode
the assistant may propose changes, which the user still confirms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:28:46 -05:00
james.maesandClaude Opus 5.5 46c38f5c05 chore(#712): prepare Next UI 1.0.0-RC.18
Source is main at 0282328 (merge of #94). Merged since RC17: #91, #97,
#98, #95 and #94. The bundle budget is report-only (owner decision).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:16:32 -05:00
james.maes 0282328bb0 Merge pull request #94 from QRun-IO/feature/assistant-pending-actions
feat(assistant): confirmation card for proposed writes, and the configured persona
2026-10-13 16:08:57 -05:00
james.maesandClaude Opus 5.5 7fcb6ac301 Merge main into feature/ui-api-version-contract
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:07:57 -05:00
james.maesandClaude Opus 5.5 4b65167cb9 ci: re-run checks against qqq develop with GET /qqq/v1/version
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:06:45 -05:00
james.maes 385aa693b1 Merge branch 'main' into feature/assistant-pending-actions 2026-10-13 15:31:12 -05:00
james.maes 347d314ab0 Merge pull request #95 from QRun-IO/feature/authentik-policy-editor
feat(widgets): AUTHENTIK_POLICY_EDITOR, the qbit-authentik policy editor
2026-10-13 15:30:45 -05:00
james.maesandClaude Opus 5.5 6989970011 Merge origin/main (#98 honor enforce:false in the bundle budget) into feature/related-record-picker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:26:05 -05:00
james.maesandClaude Opus 5.5 d62a63eb2e fix(ui-api): treat a non-JSON /version answer as a pre-semver server
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:59:56 -05:00
james.maes 39acafc61d Merge branch 'main' into feature/authentik-policy-editor 2026-10-13 14:57:18 -05:00
james.maes 4792a5b66f Merge pull request #98 from QRun-IO/fix/perf-budget-enforce-flag
fix(perf): honor "enforce": false in the bundle budget script
2026-10-13 14:57:03 -05:00
james.maes be6217537d Merge pull request #97 from QRun-IO/feature/faster-acceptance-ci
ci: build once, shard acceptance, PR-scoped specs, quarantine WID-029
2026-10-13 14:48:15 -05:00
james.maesandClaude Opus 5.5 6d0558653f fix(perf): honor "enforce": false in the bundle budget script
#91 added "enforce": false to perf-budget.json and documented it, but
perf-budget.mjs never read the flag and still exited 1 on any breach, so
PR95 (total JS 1102.4 KB) fails the required Bundle budget check. Size
overruns are now warnings while enforce is false; structural problems
(a lazy chunk on a first load, a marker no chunk contains) still fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:34:02 -05:00
james.maesandClaude Opus 5.5 ffde7af138 fix(assistant): refresh the page's data after a confirmed write
After the user confirms a proposed write, the card invalidates the cached
queries, so the record screen behind the panel shows the saved values
without a reload (seen against the demo server: the page kept its old
meta description).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:32:17 -05:00
james.maesandClaude Opus 5.5 30b61917fc Merge origin/main (#91 report-only bundle budget) into feature/related-record-picker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:25:54 -05:00
james.maes 705defe5f6 Merge branch 'main' into feature/authentik-policy-editor 2026-10-13 14:21:33 -05:00
james.maesandClaude Opus 5.5 2b84663d7b ci: build once, shard acceptance, PR-scoped specs, quarantine WID-029
- One build job (QQQ from source, static export, dashboard and sample
  jars) feeds every shard; QQQ's Maven install is cached per commit.
- Full mode (main, v* tags, dispatch, or the full-acceptance label):
  chromium 3, firefox 3, webkit 4, mobile 4, tablet 4 shards. PRs run the
  changed areas plus a smoke set on chromium and mobile, 2 shards each.
- Shards emit blob reports; a single "acceptance gate" job merges them
  and runs the matrix gate (partial on PRs).
- WID-029 is tagged @quarantine (#93): excluded from the gate, run in a
  separate non-blocking job in full mode.
- pnpm and Playwright browser caches; Maven Central retries with backoff
  (scripts/ci/retry.sh plus resolver retry handlers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:17:34 -05:00
james.maesandClaude Opus 5.5 820f5e2e21 feat(assistant): show the configured persona in the panel
/assistant/config now returns the admin-configured profile. The panel
header and the empty state use its display name and greeting, and its
starter prompts are buttons that send the prompt. The empty state also
says the assistant sees and does only what the user can, through the same
API as the screens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:12:11 -05:00
james.maesandClaude Opus 5.5 aab7acf7a6 feat(picker): generic related-record picker (single, multi-add with review, link)
One picker dialog family: PickerDialogFrame is the shared chrome, used by the
media RecordPickerDialog (no behaviour change; its tests are unchanged) and
the new RelatedRecordPickerDialog, which serves a table's
relatedRecordPickers (QQQ docs/RELATED-RECORD-PICKER.md):

- SINGLE: Browse/Change under a foreign-key field opens the picker. It lists
  the target with the picker's columns, the target's filter builder (seeded
  from the picker's resolved default filter), saved views, search, sorting,
  Load more and a preview pane. Enter, a double click or Select fills the
  field. Quick-create shows the target's quick-create section (or its
  required fields), saves through the insert route and selects the new
  record. A link opens the full create screen.
- MULTI_CHILD: the parent's child list offers the picker. It is
  multi-select (targets already on the record are marked). Next prepares one
  row per pick, the user fills the review fields (child metadata, standard
  form controls), and Add saves all or nothing.
- LINK: linked targets start checked; checking links and unchecking unlinks
  in one save.

Every call goes through the v1 relatedRecordPicker routes. The dialog is
lazy-loaded, keyboard operable (arrows, Space, Enter, Escape, which leaves
quick-create or the review first), full screen on phones, and uses theme
tokens only. Quick-create's submit never reaches a form hosting the picker.

Tests: dialog unit tests (modes, search, default filter, quick-create,
review, errors, host form, axe), utils tests, and real-backend acceptance
REC-066 to REC-068 against the sample's Sales tables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:09:55 -05:00
james.maesandClaude Opus 5.5 1056674285 feat(widgets): AUTHENTIK_POLICY_EDITOR, the qbit-authentik policy editor
A lazy widget type for qbit-authentik's Edit Policy step: the app's
resources grouped by app with search, a per-resource action matrix with
select-all per row, column and app, resources the app does not define listed
for removal, and a live read-only JSON preview generated exactly as the qbit
does (canonical form, fail-closed parser, round-trip check). On a process
step it hands { policyDocument: compactJson } to the step.

Container queries keep the matrix readable in any column width. Acceptance
PRC-067 (fixture prcPolicyEditor) passes on chromium, firefox, webkit and
mobile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:59:49 -05:00
james.maesandClaude Opus 5.5 b6688d780b feat(assistant): confirmation card for writes the assistant proposes
The assistant's create/update/delete tools now only propose a change
(qbit-assistant pending actions). The panel renders the pending_action
payload as a card with the proposed values and Confirm / Cancel. Confirm
calls POST /assistant/actions/{id}/confirm, and the server runs the write
through the UI API with the user's own session. The card loads the
action's state, so a reloaded conversation shows the outcome. It is
code-split, so the panel's first load is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:59:18 -05:00
james.maes c63510e200 Merge pull request #91 from QRun-IO/feature/bundle-budget-report-only
build(perf): make the bundle budget report-only (owner decision)
2026-10-13 13:40:19 -05:00
james.maesandClaude Opus 5.5 01dd7e9586 feat(ui-api): UI API version check, bundled OpenAPI document and contract test
- Check the server's GET /qqq/v1/version before sign-in; a server outside the
  supported range (>=1.0.0 <2.0.0) stops with a clear message. Servers without the
  endpoint (QQQ 4.1.0-RC.5 and earlier) are the pre-semver 1.0.0 surface.
- Vendor the UI API document (src/lib/api/openapi/qqq-v1.json, 1.1.0).
- ui-api-contract.test.ts fails on any src call that is not an operation of the
  document or a listed, shrinking exception (ui-api-calls.ts), on unlisted computed
  call paths, and on stale exceptions.
- scripts/check-ui-api-spec.mjs: acceptance CI checks the bundled document against
  the QQQ checkout under test (major, newer-than-server, missing operations).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:32:55 -05:00
james.maes c7fe584911 Merge pull request #90 from QRun-IO/feature/next-rc17-prep
chore(#712): prepare Next UI 1.0.0-RC.17
2026-10-13 12:19:16 -05:00
james.maesandClaude Opus 5.5 6abfb8bdaa build(perf): make the bundle budget report-only
Owner decision 2026-10-13: "we can remove the size limits". perf-budget.json
gains "enforce": false. scripts/perf-budget.mjs still measures and prints every
route, the total, the largest chunks and the lazy chunks, writes
test-results/performance/bundle.json, and lists every size over its reference
value as a warning. It no longer fails CI. Setting enforce back to true (or
removing it) restores the gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:17:42 -05:00
james.maesandClaude Opus 5.5 05479bfcdf chore(#712): prepare Next UI 1.0.0-RC.17
Source is main at 6ed2575 (merge of #89, notification center). Total JS
1098.0/1100 KB and /app first load 349.2/350 KB are within 2 KB of their
caps; the next feature needs a measured raise or a code-split pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:13:36 -05:00
james.maes 6ed257565a Merge pull request #89 from QRun-IO/feature/notification-center
feat(alerts): notification center - badge, popover, page and banners
2026-10-13 12:11:23 -05:00
james.maesandClaude Opus 5.5 9169112f18 fix(alerts): refuse open-redirect notification links
linkKind treated `/\evil.com` as an in-app path, and browsers normalize
`\` to `/`. Refuse any href with a backslash, whitespace or a control
character, or whose second character is `/` or `\`; parse it against the
page origin and treat it as in-app only when the origin matches and it
starts with a single `/`; external only for https:. Everything else is
plain text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:15:06 -05:00
james.maesandClaude Opus 5.5 03d7bae9e4 feat(alerts): notification center - badge, popover, page and banners
Turn the Alerts bell into the notification center (qbit-alerts
NOTIFICATIONS.md section 8, phase b):

- Badge: unread count (99+), red for an unread CRITICAL, labelled
  "Notifications, N unread". Polls the summary every pollSeconds, backs
  off exponentially to 5 minutes after errors, pauses while the tab is
  hidden and refetches when it shows. Still no bell and no request unless
  the metadata lists the alerts app.
- Popover (lazy): severity icon, title, source, relative time, link
  (marks read), per-item mark read/unread and dismiss, Mark all read and
  View all. Focus-trapped; Escape closes.
- /app/notifications (lazy, fixed route like /app/search): All/Unread,
  category and severity filters and paging in the URL, markdown bodies
  through MarkdownView (no HTML; relative app links now allowed), same
  actions.
- Banners: the summary's sticky items above the page until dismissed.
- Actions are optimistic on the summary and every loaded page, rolled
  back with a toast on failure, then refetched.
- Older backends (0.1.0-RC.1: alertId, group, no id) still render, with
  per-item actions hidden and read-by-alertId on open.

Bundle: /app routes +0.7 KB first load (/app/_/_ 349.2 of 350 KB); the
three lazy chunks (15.2 KB) get a notificationCenter lazyChunks budget;
total JS 1098.0 KB of 1100; CSS unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:13:12 -05:00
james.maes ef7f1c5714 Merge pull request #88 from QRun-IO/feature/next-rc16-prep
chore(#712): prepare Next UI 1.0.0-RC.16
2026-10-13 06:20:09 -05:00
james.maesandClaude Opus 5.5 42ddc06864 chore(#712): prepare Next UI 1.0.0-RC.16
Source is main at 6dea1c3 (merge of #80). Merged since RC15: #79, #84,
#86, #87, #85, #82, #81 and #80. The record picker, validation limits
and date presets need QQQ 4.1.0-RC.5; the Alerts bell needs qbit-alerts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 06:09:38 -05:00
james.maes 6dea1c3a86 Merge pull request #80 from QRun-IO/feature/record-picker
feat(forms): record picker for possible-value fields over large tables (thumbnails, paged dialog)
2026-10-13 06:01:41 -05:00
james.maesandClaude Sonnet 5.5 ebc734545e Merge main (#85, #82, #81) into feature/record-picker
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 01:06:13 -05:00
james.maes 67e817adfa Merge pull request #81 from QRun-IO/feature/form-validation-ux
feat(forms): quiet, metadata-driven validation on record create and edit
2026-10-13 01:06:10 -05:00
james.maesandClaude Sonnet 5.5 47dcd6ff50 Merge main (#85, #82) into feature/form-validation-ux
Keeps this branch's zod-from-metadata.ts: it carries the same backend-worded range
messages as #87, plus the label-aware required, number and too-long messages.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 00:04:02 -05:00
james.maes 7720ce2fbe Merge pull request #82 from QRun-IO/feature/date-filters
feat(query): date-first filters for DATE and DATE_TIME fields (dates, presets, Include time)
2026-10-13 00:03:08 -05:00
james.maes e7ab2f8f9b Merge pull request #85 from QRun-IO/feature/alerts-bell-panel
feat(alerts): header bell and record "Open alerts" panel for the Alerts qbit
2026-10-12 23:04:44 -05:00
james.maesandClaude Sonnet 5.5 bc79580464 Merge main (#85) into feature/date-filters
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 23:04:44 -05:00
james.maesandClaude Sonnet 5.5 008aa8daf8 perf(forms): one lazy import site for the process runner and the file preview lightbox
The record picker dialog lazy-imports ProcessRun (upload) and FilePreviewLightbox, which
other screens already lazy-import. Each import site made the bundler emit its own variants of
those chunk groups: stubbing the two in the dialog saved 12 KB, and sharing one import site
for each saves 25.2 KB of total JS (1099.7 KB to 1074.5 KB on this branch, same tree).
The picker dialog itself stays a dynamic import, budgeted under lazyChunks.recordPicker.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 22:18:00 -05:00
james.maesandClaude Sonnet 5.5 22648a5c79 Merge main (#87) into feature/record-picker
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 22:04:01 -05:00
james.maesandClaude Sonnet 5.5 78edbdf4c3 Merge main (#87) into feature/alerts-bell-panel
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 22:03:45 -05:00
james.maes 4d673edb5a Merge pull request #87 from QRun-IO/fix/range-error-backend-wording
fix(forms): word client-side range errors as the backend does
2026-10-12 22:03:28 -05:00
james.maesandClaude Sonnet 5.5 a3ad88bc6f Merge the range-error wording fix (#87) into feature/date-filters
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 20:49:21 -05:00
james.maesandClaude Sonnet 5.5 12b2eee0a3 fix(query): keep the quick filter operator name, and walk the specs through the date-first flow
The date quick filter's comparison select lost its "Operator for <Field>" accessible name
(it was "Filter operator"); it keeps the name the quick filters always had.

The advanced rows keep "Filter operator", but date fields now offer whole-day comparisons
("is on", "is not on") first. QRY-012, QRY-013, QRY-080 and QRY-086 follow the new flow:
"is on"/"is not on" for the equality cases, "is relative to today" (dates) or "Include time"
(date-times) to reach exact values and relative expressions, and a new date quick filter starting as
"is between". Their assertions on the rows returned are unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 20:49:08 -05:00
james.maesandClaude Sonnet 5.5 6bc5ea875f fix(forms): backend wording for too-long values, and focus the schedule editor on a failed save
An over-long value on a field with the ERROR policy is now stopped at the form with the
backend's own sentence ("The value for <Label> is too long (max allowed length=N)").
REC-024 asserts the typing cap and sends the over-long value straight to the API to
keep covering the backend refusal.

A failed save moved focus only to fields the form renders itself, so the schedule editor
(WID-064) lost the focus it had on main; fall back to the field's own ref.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 20:35:18 -05:00
james.maesandClaude Sonnet 5.5 9a5acd9e9e fix(forms): word client-side range errors as the backend does
QQQ #1100 now publishes inclusive ERROR value ranges in field metadata, so the
form refuses an out-of-range number before any request and the backend
message never appeared (REC-026, REC-048, INT-007 fail on main). The client
message now reads exactly as the backend's ("The value for <Label> is too large
(maximum allowed value is N)"), so users see one text either way.

Those specs sent Bounded Value 150, which the form now blocks first. They assert
the client message for that, and exercise the real backend rejection through
Exclusive Bounded Value 0, a bound the metadata does not publish.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 20:32:36 -05:00
james.maesandClaude Sonnet 5.5 363f68eb40 fix(forms): keep the standard "<Field> is required" and backend range wording
Quiet validation had shortened the messages to "Required", "Enter a whole number" and
"Must be between a and b", which broke the acceptance expectations for the standard
sentences. Validation stays quiet (checked on blur or save, one line under the field),
but the text is the standard one again: "<Label> is required", "<Label> must be a
whole number", and range errors worded exactly as the backend words them ("The value
for <Label> is too large (maximum allowed value is N)"). Server messages mapped to a
field keep the backend's range sentence.

Read-only fields now render as plain values (a quieter screen), so REC-010 asserts the
value and the absence of an input instead of a disabled input. REC-026, REC-048 and
INT-007 sent Bounded Value 150, which QQQ #1100's published range makes the form refuse
first; they assert the client message for that and exercise the backend rejection
through Exclusive Bounded Value 0, a bound the metadata does not publish.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 19:49:50 -05:00
james.maesandClaude Sonnet 5.5 1187660904 fix(forms): word client-side range errors as the backend does
QQQ #1100 now publishes inclusive ERROR value ranges in field metadata, so the
form refuses an out-of-range number before any request and the backend
message never appeared. The client message now reads exactly as the
backend's ("The value for <Label> is too large (maximum allowed value is N)"),
so users see one text either way.

The REC-026, REC-048 and INT-007 acceptance tests asserted the backend
rejection by sending Bounded Value 150, which the form now blocks first. They
assert the client message for that, and exercise the real backend rejection
through Exclusive Bounded Value 0, a bound the metadata does not publish.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 19:49:30 -05:00
james.maesandClaude Opus 5.5 92bcefddb8 fix(alerts): gate the bell and record panel on the Alerts app in metadata
Merge origin/main into feature/alerts-bell-panel, then stop the bell and
the record "Open alerts" panel from probing /alerts/inbox/summary on
instances without qbit-alerts. That probe 404'd on every page against the
QQQ sample app and failed 599 real-backend acceptance tests on the
console-error guard.

Like the Assistant, the gate is now instance metadata: isAlertsInstalled
checks for the qbit's "alerts" app, which QQQ publishes only when the
qbit is installed and the user holds alerts.view (the permission its
/alerts/* routes require). qbit-alerts publishes no supplemental metadata
or frontend setting, so its app is the cleanest signal. Without it the
bell and panel render nothing and send no request or poll. The popover
and record panel stay lazy-loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:30:42 -05:00
james.maes be6ea9a723 Merge pull request #84 from QRun-IO/feature/search-preview-thumbnails
feat(search): file thumbnails, chips and dates in the record preview
2026-10-12 19:23:24 -05:00
james.maesandClaude Opus 5.5 6e7c908c06 Merge branch 'main' into feature/form-validation-ux
Takes main's budget caps (CSS 24 KB, total JS 1100 KB, owner decision
2026-10-12) instead of this branch's 1060 KB. Merged tree: CSS 23.2 KB,
total JS 1050.4 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:38:30 -05:00
james.maes ae47adc03b Merge branch 'main' into feature/date-filters 2026-10-12 18:37:24 -05:00
james.maes 15a7e43e6f Merge branch 'main' into feature/record-picker 2026-10-12 18:37:21 -05:00
james.maesandClaude Opus 5.5 8d02617aad Merge origin/main (#86 CSS budget 24 KB) into feature/search-preview-thumbnails
perf-budget.json keeps main's #79/#86 CSS note (cap 24 KB) and this
branch's preview measurement note. After the merge: total JS 1053.0 KB
(cap 1100), CSS 23.1 KB (cap 24, identical to main), preview chunk 7.1 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:17:30 -05:00
james.maes 54a67f69ef Merge branch 'main' into feature/alerts-bell-panel 2026-10-12 18:12:42 -05:00
james.maes 8ea0c89312 Merge pull request #86 from QRun-IO/feature/css-budget-24
chore(perf): CSS budget 24 KB after the resizable navigation
2026-10-12 18:12:11 -05:00
james.maesandClaude Opus 5.5 ed079d632b chore(perf): CSS budget 24 KB after the resizable navigation
Main measures 23.07 KB of shared CSS after #79, over the 23 KB cap, and
the RC.16 candidates each add a little. Raised under the owner's budget
decision of 2026-10-12, with the measurement in the comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:05:45 -05:00
james.maesandClaude Opus 5.5 ebdd4c2289 Merge origin/main (#79 resizable nav, RC.15 prep) into feature/search-preview-thumbnails
perf-budget.json: main's owner-decision note (total cap 1100 KB) and this
branch's measurement note are both kept. Measured on the static export
after the merge: total JS 1053.0 KB against main f11cc85's 1043.7 KB
(+9.3 KB, as before); route first loads and CSS identical to main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:00:00 -05:00
james.maesandClaude Opus 5.5 64cd1e6dc9 feat(alerts): header bell and record "Open alerts" panel for the Alerts qbit
- AlertsBell replaces the placeholder bell: polls /alerts/inbox/summary
  every 60 s while the tab is visible, renders nothing when Alerts is not
  available (403/404/non-summary), badge red for unread CRITICAL, polite
  live announcement; the popover (lazy) lists the latest items, marks read
  on open, Mark all read, Open Alerts, focus trap and Escape
- AlertsRecordPanelSlot on the record view: only when the probe succeeds,
  never on alerts_* tables; the lazy AlertsRecordPanel shows open alerts
  (Acknowledge / Snooze links for managers), resolved in 30 days, View all
  and Watch alerts for this record; hidden when there is nothing to show
- inline styles for the severity dots and popover placement so no new CSS
  utilities ship (shared CSS stays under the 23 KB cap)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:15:10 -05:00
james.maes f11cc85685 Merge pull request #79 from QRun-IO/feature/resizable-nav
feat(layout): resizable desktop navigation (drag, keyboard, persisted width)
2026-10-12 17:03:42 -05:00
james.maes 7398f1aa01 Merge pull request #83 from QRun-IO/feature/next-rc15-prep
chore(#712): prepare Next UI 1.0.0-RC.15
2026-10-12 17:01:35 -05:00
james.maesandClaude Opus 5.5 061a7cd09b feat(search): file thumbnails, chips and dates in the record preview
The search record preview shows FILE_PREVIEW thumbnails, CHIP values and
compact dates again, as the record view does. It stays a lazy chunk and
keeps its own queries, so no route's first load grows.

Measured on the static export against main 5de69be (total JS 1042.7 KB):
total JS 1052.0 KB (+9.3 KB, the shared file-preview, gallery, adornment
and date modules re-bundled into more on-demand screen chunks), the
preview chunk 7.1 KB (was 3.7), every route first load and the CSS
(23.0 KB) unchanged. The owner approved a 1100 KB total JS cap; the
first-load and CSS caps stay. perf-budget.json records the numbers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:54:34 -05:00
james.maesandClaude Opus 5.5 596815c605 chore(#712): prepare Next UI 1.0.0-RC.15
Source is main at 5de69be (merge of #59). Merged since RC14: #58, #55,
#54, #77, #78, #61, #60 and #59. The total JS budget is 1100 KB (owner
decision 2026-10-12); route, CSS and lazy-chunk caps are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:49:32 -05:00
james.maes 5de69be7e3 Merge pull request #59 from QRun-IO/feature/search-ux
feat(search): search-as-you-type quick search, richer results and a URL-addressable search page
2026-10-12 16:48:24 -05:00
james.maesandClaude Opus 5.5 8880ec7974 fix(query): keep exact date values where a filter value may be a variable
A report setup lets a criterion value be a filter variable, which a whole-day range (its end is
the next day's start) cannot take, so those rows keep the exact-value inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:08:17 -05:00
james.maesandClaude Opus 5.5 df59430694 feat(query): date-first filters for DATE and DATE_TIME fields
A DATE_TIME field is now filtered by picking dates: "is on", "is between" (whole days,
inclusive), "is before/after" and "is within" a relative range (Today, Last 7 days, This month,
Last quarter, Last N days/weeks/months, ...). Dates become exact bounds in the user's time zone:
>= the local midnight that starts the first day and < the local midnight that starts the day after
the last, so there is no 23:59:59 edge and a 23 or 25 hour day around daylight saving is covered.
Relative ranges are stored as StartOfPeriod expressions (QRun-IO/qqq), so a saved view stays
relative. An unobtrusive "Include time" switch gives exact date-time pickers; older saved views
with plain datetime bounds still work and open with it on. DATE fields get the same comparisons
and presets, without the switch.

The filter builder shows a day or range as one condition, quick filter chips say it in plain
language (Created: Oct 1 - Oct 12, 2026; Created: Last 7 days), a tooltip names the time zone and
week start, and the API client sends X-QQQ-UserTimezone so the backend evaluates relative dates in
the zone the dates are shown in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:05:29 -05:00
james.maesandClaude Opus 5.5 4644b4023d chore(budget): raise the total JS cap to 1060 KB for form validation
Measured +6.6 KB gzip across all chunks (1042.7 to 1049.3 KB) with +0.1 KB on
the create and edit routes and +0.1 KB CSS; recorded in perf-budget.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 15:17:58 -05:00
james.maes 9842bc6554 Merge remote-tracking branch 'origin/main' into feature/form-validation-ux 2026-10-12 15:13:47 -05:00
james.maesandClaude Opus 5.5 f23e03e417 feat(forms): quiet, metadata-driven validation on record create and edit screens
Fields are checked when the user leaves them (or on save), then live while they
show an error. A refused too-long value (ValueTooLongBehavior ERROR) caps typing;
a counter appears only in the last 20% of a limit; a trimming field notes extra
characters. Whole-number and decimal inputs refuse non-numeric keys; unreadable
dates and numbers become field errors. Messages are plain (Required, Enter a
whole number, Must be 200 characters or fewer (currently 214)). Save moves focus
to the first invalid field and shows a compact summary only when others are out
of sight. The backend's record errors are matched to fields by label and shown
beside them, keeping the user's input. Read-only fields show as values. Error
lines open smoothly, are announced politely and link to the control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 15:13:46 -05:00
james.maesandClaude Opus 5.5 3508c505f5 Merge origin/main (#60 assistant, #61, #78) into feature/search-ux; preview fits the budget
Conflicts: CommandMenu keeps #60's Ask Assistant entry (last, and it
suppresses "No results found") and Cmd+J alongside the search group and
record groups; perf-budget.json keeps main's cssGzipKB 23, its assistant
lazy chunk and $comments, and adds the search lazy chunks.

Budget, measured on the static export against main b32a29d (total JS
1042.7 KB, CSS 22.6 KB): the merged tree measured 1065.5 KB. Bisecting
showed the record preview's imports re-bundled into more screen chunks:
without the preview 1042.5; with the useRecord/useTableMetaData hooks +12.4;
with file thumbnails and chips +9.3. The preview now runs its own two
queries (same query keys, so caches are shared) and renders values as
text, Yes/No and links; thumbnails and chips stay on the record view.

Result: total JS 1042.7 KB (budget 1050, 7.3 KB headroom, equal to main),
CSS 23.0 KB (cap 23, main 22.6); no cap raised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 15:12:13 -05:00
james.maes b32a29d25b Merge pull request #60 from QRun-IO/feature/assistant-panel
feat(assistant): QQQ Assistant panel, phase 1 (read-only Q&A and navigation)
2026-10-12 14:48:48 -05:00
james.maesandClaude Opus 5.5 fec62ec611 feat(forms): record picker for possible-value fields over large tables
A field whose possible value source declares a picker (field.picker, from
QQQ's QPossibleValuePicker) gets an image-aware selection and a picker
dialog built for 100,000+ records.

Inline field (PossibleValueSelect): the typeahead asks for a limited page
(20) of picker options and shows their thumbnails and subtitles. The
selection shows its thumbnail with Change/Browse, Preview (the full-size
lightbox) and View (the record, in a new tab).

Picker dialog (RecordPickerDialog, loaded by import() on Browse/Change):
- debounced server-side search
- filters generated from the picker table's field types
- the declared sorts
- grid and list views with lazy thumbnails
- infinite scroll with a Load more fallback, keyset paging (afterId) for
  the primary-key sort and offset paging otherwise, never a count
- a preview pane with detail fields
- upload-and-select through the picker's upload process (ProcessRun
  gains an onComplete callback)
- single or multiple selection
- filters, sort and view remembered per field in local storage
- arrow-key navigation by row, Enter or double-click to confirm, focus
  trap and Escape (Radix)
- full screen on phones, light and dark tokens only

Bundle: nothing added to any route's first load or to the shared CSS.
Total JS grows 2.5 KB (main 1005.4 KB, branch 1007.9 KB). The dialog is
budgeted as the recordPicker lazy chunk: Turbopack emits one variant per
parent context, 17.0 KB across three, and a session loads one of about
5.9 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:47:57 -05:00
james.maesandClaude Opus 5.5 75c9c5d64d feat(layout): resizable desktop navigation
The left navigation gets a drag handle on its right edge, like a normal
resizable vertical panel:

- Drag (pointer capture, mouse, pen and touch) sets the width; it is
  written to a CSS custom property once per animation frame, so the
  content reflows without a React render per pixel.
- Double-click resets to the default (the theme's --qqq-sidebar-width,
  256 px).
- The handle is a focusable vertical separator with aria-valuenow/min/
  max: Left/Right step 16 px, Shift 64 px, Home/End jump to min/max,
  with a visible focus ring and a grip on hover/focus.
- Width is held to 200 px .. min(480 px, 40% of the viewport), in CSS
  too, so a window resize needs no script.
- The chosen width persists in localStorage (wrapped in try/catch) and
  is applied in a layout effect, before the first paint.
- The phone drawer is unchanged and has no handle.

Bundle: first-load JS +1.0 KB gzip on the app routes, total JS 1005.4
-> 1006.4 KB (cap 1050), shared CSS 21.5 KB unchanged (cap 22).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:41:54 -05:00
james.maes 98409c85af Merge pull request #61 from QRun-IO/feature/record-developer-view
feat(developer): record developer view with record navigation, API access and provider panels
2026-10-12 14:38:32 -05:00
james.maesandClaude Opus 5.5 ddc66c6b63 perf(search): load the results page on demand; lighter preview; lazy chunk budgets
Measured on the static export after merging main (a724fc1), against main:
total JS was 1067.0 KB (main 1005.4), over the 1050 KB cap.

- /app/search loads SearchPage through import(), so the results page is no
  longer in any route's first load (/app/search first load 334.5 KB, main
  335.2).
- The record preview renders values itself (file thumbnails, chips, Yes/No,
  dates, links, text) instead of through FieldValue, whose widget imports
  pulled two re-bundled chunks (+17.1 KB) into the preview. Secrets
  (REVEAL), widgets, code, HTML and binary fields are not previewed.
- The record search response schemas move from the shared schemas module to
  record-search.ts, their only user.
- perf-budget.json budgets the four on-demand search chunks under
  lazyChunks with their measured sizes (search page 13.6, quick search panel
  9.7, palette record groups 7.8, record preview 7.3 KB gzip; caps about 8%
  above), so they leave the total but keep their own caps and must stay out
  of every first load.
- A few one-off utility classes are swapped for ones main already ships.

Result: total JS 1012.7 KB (main +7.3), CSS 21.9 KB (main +0.4), every route
first load within 0.5 KB of main; no cap raised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:10:55 -05:00
james.maesandClaude Opus 5.5 2ed5239642 Merge origin/main (#54, #55, #58, #77, #78) into feature/search-ux
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:54:46 -05:00
james.maesandClaude Opus 5.5 725ef87efd Merge branch 'main' into feature/assistant-panel; CSS budget 23 KB
Main now includes SQL Workbench dark mode (#78, shared CSS 21.5 KB).
With the assistant panel the static export measures 22.3 KB of shared
CSS, over the 22 KB cap, so the cap becomes 23 KB (about 3% headroom).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:46:07 -05:00
james.maes a724fc191c Merge pull request #78 from QRun-IO/feature/sql-workbench-theme
feat(sql-workbench): theme-aware workbench built from the standard controls (light and dark)
2026-10-12 13:45:08 -05:00
james.maesandClaude Opus 5.5 5b4ef76b01 fix(search): keep "nothing selected" when records arrive after ArrowUp
ArrowUp above the first option clears the selection so Enter opens the
results page. When record results arrived after that (the options
changed and the stale flag flipped), the first option was preselected
again, so Enter opened a hit instead. An explicit "nothing selected"
choice now holds until the term changes, like any other choice.

Caught by NAV-025 in WebKit acceptance, where the records answer after
the keypress.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:23:14 -05:00
james.maesandClaude Opus 5.5 c1be9c61b3 Merge branch 'main' into feature/assistant-panel
Keeps the AssistantProvider wrapper around #54's positioned single
scroll shell (relative, data-qqq-id=app-shell).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:13:37 -05:00
james.maesandClaude Opus 5.5 c8b7e9b16a Merge branch 'main' into feature/record-developer-view
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:10:47 -05:00
james.maes 72377ac30b Merge pull request #77 from QRun-IO/chore/dependabot-pr-limit
chore(deps): limit Dependabot to three open PRs per ecosystem
2026-10-12 12:40:58 -05:00
james.maes 4d8b93ed42 Merge pull request #54 from QRun-IO/fix/single-page-scroll-container
fix(layout): one page scroll container; contain absolute descendants
2026-10-12 12:40:54 -05:00
james.maesandClaude Opus 5.5 cf6a326bca Merge main into feature/sql-workbench-theme
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:23:58 -05:00
james.maesandClaude Opus 5.5 c2aa695044 feat(sql-workbench): theme-aware workbench built from the standard controls
Dark mode and the hand-rolled controls made the SQL Workbench look unlike
the rest of Next UI. Every color now comes from a theme token, so both
modes work by construction:

- Editor: CodeMirror theme from the app tokens and the shared --qqq-code-*
  palette (gutter, cursor, selection, active line, autocomplete popup);
  follows the theme toggle live without reconfiguring.
- Standard controls: buttons, selects, inputs, tabs, menus, badges and
  alerts from a new design-system module (components/ui/controls.ts; the
  script panel classes now live there and are re-exported).
- Result grid in the record grid look; Export menu (CSV, Excel, JSON)
  like the record query; lucide icons replace text glyphs.
- Charts use the theme chart palette, popover tooltips, muted axes.
- Write confirmation and notebook delete use the app dialog pattern
  (notebook delete no longer uses window.confirm).
- Layout: one column until the widget is wide enough (container query);
  cell controls in the cell header, not floating over other controls; no
  inner horizontal scroll outside the result grid; plain labels; dates
  via DateTimeText.
- Primary and danger buttons darken on hover, and the danger fill is
  darkened in dark mode, keeping white labels at 4.5:1.

Tests: e2e checks AA contrast in the workbench in light and dark mode,
the live theme switch of the editor, and no sideways scroll on a phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:23:44 -05:00
james.maesandClaude Opus 5.5 6c1e3a67b1 feat(search): detail chips on result cards; phones keep only Preview
Result cards show the backend's secondary field values (details) as
label/value chips instead of a run of text, so a record's key fields
read at a glance; screen readers still hear "Label: value".

On phones (below sm) the card keeps only its Preview button: New tab and
Copy link are in the preview sheet, and the snippet gets the width
instead of breaking mid-word.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:13:32 -05:00
james.maesandClaude Opus 5.5 9d87b6dfee fix(developer): section bar never scrolls sideways and follows the section in view
- Phones get a "Go to" section picker; wider screens get a wrapping row of
  links. Neither scrolls sideways.
- The bar sticks to the top of the main scroll container (-top-6 offsets its
  p-6), so no page content shows between the app header and the bar.
- The highlighted section comes from the scroll position, using the same
  line a jump lands on (scroll-mt-24). At the bottom of the page the
  section last picked stays highlighted while it is in view.
- The API operation rows label their two copy buttons "Copy URL" and
  "Copy example" instead of two identical "Copy" buttons.
- e2e: the bar does not overflow at any width, phone picker, sticky bar and
  highlight, dark mode at 390 and 1440, and screenshots of the SQL
  Workbench and Assistant panels. The unit test waits up to 5 s for the
  lazy script cards, which timed out under full-suite load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:09:03 -05:00
james.maesandClaude Opus 5.5 21ba08bcd5 Merge branch 'main' into feature/assistant-panel
Resolves the perf-budget comment conflict with the widget width ladder
(#55). Both raise the shared CSS cap to 22 KB; the combined tree measures
21.3 KB locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:56:09 -05:00
james.maes 89add474cf Merge pull request #55 from QRun-IO/feature/widget-width-readability
feat(widgets): readable dashboard widths (per-widget min width, ultra-wide caps, table card layout)
2026-10-12 11:55:08 -05:00
james.maes 3f161f6fd5 Merge pull request #58 from QRun-IO/docs/adopting-in-another-app
docs: add "Adopting in another app" section to the README
2026-10-12 11:55:03 -05:00
james.maesandClaude Opus 5.5 c183249a4b chore(deps): limit Dependabot to three open PRs per ecosystem
Twelve version-update PRs each ran the hour-long real-backend acceptance
workflow and starved release PRs of runners. Minor and patch updates are
already grouped per ecosystem; cap open version-update PRs at three for
npm (was 10), GitHub Actions and Maven (default 5). Security updates are
unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:18:00 -05:00
james.maes 9ce3b81b04 Merge remote-tracking branch 'origin/feature/search-ux' into feature/search-ux 2026-10-12 10:33:37 -05:00
james.maesandClaude Opus 5.5 a62b10ae12 test(e2e): target the previewed record's heading in the sheet
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:33:24 -05:00
james.maes 71bcc37c9e Merge remote-tracking branch 'origin/main' into feature/search-ux 2026-10-12 10:30:51 -05:00
james.maesandClaude Opus 5.5 a9975487f6 feat(search): record preview, term completions, relevance and Shift+Enter
Results page: a record preview beside the results on wide screens (>=1280px)
that follows hover, focus and the arrow keys (which now move between
results), and a side sheet elsewhere (Preview button, or a long press on
touch screens). The preview shows the record's first-tier fields as on the
record view (FILE_PREVIEW thumbnails included), the fields that matched,
and open / new tab / copy link / edit (when permitted). It loads on demand.
Cards show a relevance meter when the provider sent scores.

Quick search: completions of the last word from matching labels (never
preselected, so Enter still opens the top hit), and Shift+Enter opens the
results page from anywhere in the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:30:49 -05:00
james.maesandClaude Opus 5.5 95931a7c7e fix(developer): ask a backend without developer panels once; REC-054 follows the new page structure
Real-backend acceptance failed REC-054 on every browser:

- The acceptance backend (QQQ develop checked out before QRun-IO/qqq#1076
  merged) answered GET /table/{t}/{pk}/developer/panels with 404. The view
  already showed no panels, but each 404 is a failed request and a console
  error. A 404/405 now marks the endpoint unsupported for the session, so a
  QQQ before 4.1.0-RC.4 sees one such request, not one per record.
- "a table without associated scripts shows none" assumed the old page's h3
  headings and no tablist at all. Sections are h2s under the record title now
  and API Access has source tabs, so the spec checks the h2 headings, no
  Associated Scripts section, no script-card tabs, and the backend's built-in
  Record Security panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:29:05 -05:00
james.maesandClaude Opus 5.5 38207b3ef5 Merge main into fix/single-page-scroll-container
Resolve docs/releases/unreleased.md against the RC14 reset: keep only
this branch's single-scroll-container entry (the others shipped in RC13).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:28:09 -05:00
james.maesandClaude Opus 5.5 1f4841bf12 Merge main into docs/adopting-in-another-app
Resolve the README status paragraph against the RC14 preparation:
RC14 is published, so the line keeps this branch's published wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:27:48 -05:00
james.maes 5629bcb3c7 Merge branch 'main' into feature/search-ux 2026-10-12 09:27:34 -05:00
james.maes 4fe5ee94f2 Merge branch 'main' into feature/widget-width-readability 2026-10-12 09:27:31 -05:00
james.maes ed1dc64330 Merge branch 'main' into feature/assistant-panel 2026-10-12 09:27:27 -05:00
james.maes ef6f0a9219 Merge branch 'main' into feature/record-developer-view 2026-10-12 09:27:24 -05:00
james.maes 3ae29246c9 Merge pull request #62 from QRun-IO/chore/repo-hygiene-trunk
chore: adopt main-only trunk model and add repo hygiene config
2026-10-12 09:27:01 -05:00
james.maes 6eac444a22 Merge pull request #63 from QRun-IO/fix/nav056-webkit-navigation-race
test(acceptance): finish table reads before deliberate document navigations (NAV-056, NAV-014)
2026-10-12 09:26:56 -05:00
james.maesandClaude Opus 5.5 46158c42cc Merge origin/main (#57 record search tolerance, RC.14 prep) into feature/search-ux
Conflicts in src/lib/api/schemas*.ts and tables*.ts resolved in favor of this
branch: record search moved to src/lib/api/record-search.ts, which keeps #57's
behavior (labels may be missing, null or empty; results without a table name or
record id are skipped with a warning) and its tests (record-search.test.ts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:15:33 -05:00
james.maes 03e7415aa2 Merge pull request #64 from QRun-IO/feature/next-rc14-prep
chore(#712): prepare Next UI 1.0.0-RC.14
2026-10-12 08:44:03 -05:00
james.maesandClaude Opus 5.5 b89d406419 chore(#712): prepare Next UI 1.0.0-RC.14
Urgent fix candidate: record search no longer fails when one result has
no label (#57), plus the Next.js 16.3.8 security update (#52). Source is
main at b699204d (merge of #57). The WebKit NAV-056 diagnostic failure,
which also fails on main, is recorded as an owner-accepted known failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:35:59 -05:00
james.maes b699204d87 Merge pull request #57 from QRun-IO/fix/record-search-tolerant-results
fix(search): one result without a label no longer fails record search
2026-10-12 08:35:12 -05:00
james.maes 51c2db74f8 Merge remote-tracking branch 'origin/main' into fix/nav056-home-app-regression 2026-10-12 07:56:51 -05:00
james.maesandClaude Opus 5.5 f34269bd38 test(acceptance): finish table reads before deliberate document navigations (NAV-056, NAV-014)
The recurring webkit/tablet NAV-056 failure ("appearance preferences explain an
application theme override") and the NAV-014/NAV-016/NAV-023 failures of the same shape are
a race in the specs, not a product regression; they predate #53 (first seen 2026-10-02,
run 36981311218).

Each spec replaced the document (reload, goto, goBack) as soon as an early assertion
passed, while the table page was about to send its initial reads (saved-view init, query,
count, or table metadata). WebKit refuses an XHR the old document sends after a navigation
has started and reports it as a page error "<url> due to access control checks." (Chromium
and Firefox report an ignorable abort). The CI trace of run 38238500908 shows those three
URLs never reached the network from the first document; the errors fire 42 ms after the
reload starts. A probe on Linux WebKit reproduces the exact message for an XHR sent at
reload start, and none for a request already in flight.

Add expectRecordsLoaded(): it waits until the table lists records, by which point the page has
sent all of its initial reads. Use it, and the existing greetInteractive response barrier,
before each hard navigation in these specs. Diagnostics stay strict: any API page error
still fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:56:50 -05:00
james.maesandClaude Opus 5.5 930804d36c chore: adopt main-only trunk model and add repo hygiene config
- Add .github/dependabot.yml: weekly grouped npm, GitHub Actions and Maven updates.
- Add .github/CODEOWNERS (* @KofTwentyTwo).
- Remove the dead CircleCI develop, release/* and hotfix/* workflows (no such branches
  exist); test_only runs on every branch except main, tag-based publish is unchanged.
- Drop the develop push trigger from ci.yml.
- Document the trunk model, required checks and tag-based releases in CONTRIBUTING.md
  and CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:55:17 -05:00
james.maesandClaude Opus 5.5 2f745d578b test(acceptance): reach the one-table view through its filter chip
"Show all" appears only when a table has more matches than the overview lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:43:39 -05:00
james.maes b50513c3b4 Merge pull request #52 from QRun-IO/dependabot/npm_and_yarn/npm_and_yarn-446c67f130
chore(deps): bump the npm_and_yarn group across 1 directory with 2 updates
2026-10-12 07:37:24 -05:00
james.maesandClaude Opus 5.5 60b110a0b1 test(acceptance): search specs follow the new quick search and results page
Records now sit in Top hits and per-table groups, the first result is
preselected (ArrowUp clears it), the dialog has a Close button in its
Tab order, Escape keeps focus in the header combobox, pages rank exact
and prefix matches first, the quick search asks for 6 per table, and the
results page shows per-table sections (8) with Show all (25).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:35:24 -05:00
james.maesandClaude Opus 5.5 148f9d653b fix(assistant): probe /assistant/config only when metadata announces the qbit
The real-backend acceptance suite fails any page with an unexplained 4xx, and the probe answered
404 on every page load of an application without qbit-assistant (also a console error in every
browser). The controller now probes only when instance metadata carries
supplementalInstanceMetaData.assistant (the spec's recommended metadata signal, published by the
qbit; the v1 allow-list in core needs the matching entry). Without it, no assistant request is made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:08:58 -05:00
james.maesandClaude Opus 5.5 ace6579fc0 feat(developer): record developer view with record navigation, API access and provider panels
Rebuild /app/{table}/{id}/dev (owner request 2026-10-12):

- Record screen navigation: back to the record, the shared RecordViewHeader
  (title, copy id, Edit, Actions menu, Audit, share), no view-mode toggle
  (new showViewModeToggle prop), and a sticky section navigation.
- Fields: label, name, type with PVS/adornments/flags, the value as the record
  screen shows it and the raw value; filterable; cards on phones.
- API Access: v1 routes this screen uses and every application API exposing
  the table (operations read from its per-table OpenAPI spec), filtered by
  the user's table permissions and capabilities, with curl, fetch and Java
  examples built from the record's values (placeholder credentials only),
  plus API docs / spec links.
- Developer panels from QQQ's provider contract (QRun-IO/qqq#1076), rendered
  generically from their blocks (key/values, text, code, table, links,
  process actions; safe links only). Older backends (404) show none.
- Relations (references, associations, exposed joins), recent audits with the
  full history dialog, associated scripts, raw JSON with copy, and a
  searchable table metadata explorer (fields with PVS and adornments,
  sections, menus, supplemental metadata, raw JSON).
- The view loads on demand (first load 333.5 KB, budget 360 KB).
- SQL Workbench: ?sql= (and ?backend=) opens the SQL as a new cell, once,
  without running it - the target of developer panel links.

Tests: unit (operations/snippets, panel parsing, page sections, panels
present/absent, workbench URL SQL) and Playwright (navigation parity, panels
present/absent, API sources, 360-3200px without sideways scrolling).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:45:09 -05:00
james.maesandClaude Opus 5.5 66edf51719 fix(search): no stale or hidden picks on Enter; keep text typed past a late URL update
Review follow-ups: Enter on a closed header dropdown searches everything
instead of opening a hidden option; while results belong to the previous
term only a current entry is preselected; a new term drops a pointer or
arrow choice made for the old one; the search page ignores the echo of
its own debounced URL update (keeping text typed meanwhile) and builds
that update from the current filters and sort.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:22:51 -05:00
james.maesandClaude Opus 5.5 3175b47165 feat(assistant): QQQ Assistant panel, phase 1 (read-only Q&A and navigation)
The Next UI side of qbit-assistant (spec docs/specs/2026-10-11-qqq-assistant-qbit-design.md):

- src/lib/api/assistant.ts: typed client for every /qqq/v1/assistant route; turns stream over
  fetch + an incremental SSE parser (sse-parser.ts), deduplicate by seq, resume through
  /turns/{id}/events?after=seq after a dropped connection, and cancel through POST
  /turns/{id}/cancel. The /config probe (assistant-probe.ts) follows the ESB getOrNull pattern:
  403, 404 or a non-config body hides everything.
- src/components/assistant: AssistantProvider (controller created in the dashboard layout),
  header button with unread badge, Ctrl/Cmd+J (also from text fields), a command palette
  "Ask Assistant" item, and the lazily loaded panel: docked and resizable (360-640 px) from
  1024 px with its own scrolling message list, a full-height Radix sheet below that following
  the visual viewport. Conversation rail (listbox), rename/pin/archive/delete, context chip,
  Stop, throttled live announcements, tool steps, record chips with hover previews, records,
  record, count, history and chart widget renders, feedback thumbs.
- Markdown: react-markdown + rehype-sanitize, raw HTML skipped, images never load, links only
  to same-origin /app paths (others plain text with a copy button).
- ?assistant=open&assistantPrompt=... opens a new conversation with the prompt pre-filled
  (not sent) and removes both parameters.

Bundle: the shell adds 1.7 KB first-load JS (/app/_ 335.5 -> 337.2 KB); the panel is a
50.8 KB lazy chunk budgeted under lazyChunks.assistant (55 KB). Its Tailwind utilities add
0.9 KB to the shared CSS (21.3 KB against the 21 KB cap), so the CSS cap is raised to 22 KB.

Tests: vitest for the SSE parser, client (resume, cancel, errors), link safety, reducer and
the panel with MSW streams (gating, streaming, cancel, markdown safety, deep link, axe);
Playwright tests/e2e/assistant.spec.ts at desktop, tablet and phone widths (docked vs sheet,
one scroll container per region, no horizontal page scroll, axe).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:15:49 -05:00
james.maesandClaude Opus 5.5 fabbad15a7 feat(search): search-as-you-type quick search and a URL-addressable results page
Header search, the "/" dialog and the Cmd+K palette now search records as
you type: debounced, superseded requests aborted, grouped by table with
counts and icons, exact/prefix matches promoted to "Top hits", matched
terms highlighted (backend ranges when the extended contract sends them,
else local), matched-field snippets, secondary values and last update.
Keyboard: arrows, Enter (first result preselected), Ctrl/Cmd+Enter opens a
new tab. Recent searches and recently viewed records show before typing.

Failures are honest: the failure class (server error with HTTP status,
timeout, unreachable, permission, unsupported, rejected, invalid response)
with the backend message and a retry, keeping page and recent matches; a
failed search provider is reported above its fallback results.

/app/search is rebuilt around URL state (q, tables, view=pages,
sort=recent): filter chips with counts, per-table sections with "Show
all", 25-per-page paging while the backend reports more, result cards
with snippets and quick actions (open, new tab, copy link).

All extended response fields are optional, so older QQQ backends keep
working; malformed results are skipped instead of failing the search.
The results panels load on demand: first load grows ~1.0 KB per route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:09:42 -05:00
james.maesandClaude Opus 5.5 baf7e0a554 docs: link the QQQ adoption checklist from the README
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:47:01 -05:00
james.maesandClaude Opus 5.5 85da466255 docs: add "Adopting in another app" section to the README
Document how a QQQ backend app switches to or upgrades the Next
dashboard: Maven coordinates and versions, minimum QQQ per feature
(SQL Workbench, FILE_PREVIEW, home app, responsive layouts), the
dashboard selection switches, auth redirect URIs, route changes and
known limitations. Also mark RC13 as published, point at .env.mock
(there is no .env.example) and correct the Next.js major in CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:27:18 -05:00
james.maesandClaude Opus 5.5 a7acea381a fix(search): one result without a label no longer fails record search
QQQ serializes with NON_EMPTY, so a record whose label fields are empty
comes back from POST /search without recordLabel. RecordSearchResponseSchema
required recordLabel on every result, so a single such result rejected the
whole response ("Invalid record search response") and global search showed
"Record search failed. Try again." with no results (prod admin.kof22.com,
term "maes": two page_section rows with no name).

- Results are validated one by one. tableLabel/recordLabel may be missing,
  null or empty; recordId may be a number.
- A result without a label is labeled "{table label} {record id}".
- A result without a table name or record id is skipped with a console
  warning; the rest still show. A non-envelope response still throws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:25:55 -05:00
dependabot[bot] 4d2b9f685c chore(deps): bump the npm_and_yarn group across 1 directory with 2 updates
Bumps the npm_and_yarn group with 2 updates in the / directory: [next](https://github.com/vercel/next.js) and [sharp](https://github.com/lovell/sharp).


Updates `next` from 16.3.6 to 16.3.8
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.6...v16.3.8)

Updates `sharp` from 0.35.4 to 0.35.5
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.35.4...v0.35.5)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 16.3.8
  dependency-type: direct:production
- dependency-name: sharp
  dependency-version: 0.35.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-12 07:54:25 +00:00
james.maes 36382059bc Merge pull request #56 from QRun-IO/feature/next-rc13-prep
chore(#712): prepare Next UI 1.0.0-RC.13
2026-10-12 02:51:25 -05:00
james.maesandClaude Opus 5.5 51e00b4195 chore(#712): prepare Next UI 1.0.0-RC.13
Bump package.json and the Maven revision to 1.0.0-RC.13, point the README,
guides and 1.0 testing page at RC13, add docs/releases/1.0.0-RC.13.md and
reset unreleased.md.

RC13 source: main 84c95e5 (merge of PR53). Merged since RC12: PR47 (SQL
Workbench), PR49 (FILE_PREVIEW thumbnails and lightbox), PR50 (responsive
layouts, readable child lists, compact dates), PR53 (instance home app).
PR54 and PR55 follow in RC14.

Backend: QQQ 4.1.0-RC.1 or later; 4.1.0-RC.2+ for standalone branding
images; 4.1.0-RC.3+ for FILE_PREVIEW thumbnails and the instance home app;
qbit-sql-workbench 1.0.0-RC.1+ for the SQL Workbench page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:50:47 -05:00
james.maes 84c95e524a Merge pull request #53 from QRun-IO/feature/home-app
feat(navigation): instance home app at /app, sidebar logo and breadcrumb root
2026-10-11 22:55:31 -05:00
james.maes 06d5aacd91 Merge branch 'main' into feature/widget-width-readability 2026-10-11 21:37:58 -05:00
james.maesandClaude Opus 5.5 0449ff315a Merge main (SQL Workbench, FILE_PREVIEW, responsive layouts) into fix/single-page-scroll-container
Resolves docs/releases/unreleased.md by keeping every Product fixes entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:37:39 -05:00
james.maesandClaude Opus 5.5 577bd3dee8 Merge main (responsive layouts #50) into feature/home-app
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:37:05 -05:00
james.maes 4586f5b4f2 Merge pull request #50 from QRun-IO/feature/responsive-readable-layout
feat: responsive record/dashboard layouts, readable child lists, compact dates
2026-10-11 21:36:59 -05:00
james.maesandClaude Opus 5.5 effee583b7 test(e2e): card-layout headers are clipped column headers, not hidden
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:26:55 -05:00
james.maesandClaude Opus 5.5 1f1c5f546a fix(widgets): keep card-layout table headers for screen readers; layout-aware acceptance
- Card layout hides the table header visually (clip) instead of display:none, so
  column headers stay in the accessibility tree.
- WID-019 / WID-068 acceptance: on phones and tablets the table widgets now use
  the card layout, so assert no sideways scroll and per-value labels there, and
  keep the sticky-header, fr-width and header-help checks for table layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:26:21 -05:00
james.maesandClaude Opus 5.5 a42887f6e5 chore(perf): CSS budget 22 KB for the widget width ladder and table cards
CI measured shared CSS at 21.1 KB gzip (PR #50: 20.4 KB): the gridMinWidth
container-query classes and the table-widget card layout add 0.7 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:08:02 -05:00
james.maesandClaude Opus 5.5 a163dc53b1 feat(widgets): readable dashboard widths from phone to ultra-wide
- Per-widget minimum width: widgetColumnClasses honors defaultValues.gridMinWidth
  (rem or px; snapped up to 16-64rem tiers) with container-query thresholds that
  give the widget, and a neighbour of the same minimum, at least that width; and
  defaultValues.gridProfile ('widgets' | 'cards' | 'table' | 'fields') to pick a
  ladder. gridCols:sizeClass:* still wins.
- Ultra-wide: table and multi-table widget content stops at 96rem
  (defaultValues.tableMaxWidth overrides, 'none' lifts it); an app can cap the
  dashboard page with supplementalAppMetaData.dashboardLayout.maxContentWidth.
- QqqTableWidget card layout: narrower than 30rem, or than its declared column
  widths need, rows render as stacked label: value cards with no forced min
  width, fixed columns or sideways scroll.
- Unit tests (threshold table regenerated from the formula) and a Playwright
  dashboard spec at 390/768/1024/1440/1920/3200.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:56:15 -05:00
james.maesandClaude Opus 5.5 77eef1c816 chore(perf): give the table developer route /app/_/dev a 360 KB cap
CI's bundle budget on the combined tree (SQL Workbench #47, FILE_PREVIEW
#49 and this PR) measured /app/_/dev at 350.8 KB first-load JS gzip, 0.8 KB
over the 350 KB default (each change fit alone). The record developer
route /app/_/_/dev measured 354.9 KB under its 360 KB cap. Give /app/_/dev
the same 360 KB route cap (about 2.6% headroom); every other route keeps
the 350 KB default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:42:45 -05:00
james.maesandClaude Opus 5.5 9a905890a8 fix(layout): one page scroll container; contain absolute descendants
On long pages the document grew a second vertical scrollbar beside the
<main> scroller, and scrolling it slid the header off-screen. The shell is
a fixed-height column (h-screen overflow-hidden) where only <main> scrolls,
but nothing in it was positioned: an absolutely positioned descendant
(sr-only text such as the company footer's "opens in a new tab") then has
the viewport as its containing block, overflow-hidden cannot clip it, and
its static position at the end of a long page stretches the document.

Make the shell root and <main> `relative` so they contain such elements.
Adds a mocked e2e spec asserting exactly one vertical scroll container
(main) on a tall app home and none on the document for a table page at
390, 1024, 1440, 1920 and 3200px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:40:56 -05:00
james.maesandClaude Opus 5.5 fe0077e86c Merge responsive layouts (#50, with #47 and #49) into feature/home-app
Resolves docs/releases/unreleased.md (the home-app bullet joins the
merged Features section) and perf-budget.json (the shared 360 KB
/app/_/_/dev cap with the combined measurement note from #50).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:29:28 -05:00
james.maesandClaude Opus 5.5 61be012fc2 Merge main (FILE_PREVIEW #49) into feature/responsive-readable-layout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:28:51 -05:00
james.maes bc01d10a27 Merge pull request #49 from QRun-IO/feature/image-preview-lightbox
feat(preview): FILE_PREVIEW thumbnails and preview lightbox (QRun-IO/qqq#1056)
2026-10-11 18:28:38 -05:00
james.maesandClaude Opus 5.5 d3daa4d067 Merge SQL Workbench (#47) and FILE_PREVIEW (#49) into feature/responsive-readable-layout
Combines #49's preview handling with #50's compact columns:
- ChildRecordListWidget keeps #50's measured compact columns, long-text
  clamp and top-aligned cells, and gains #49's FILE_PREVIEW gallery
  (shared cellRecords, joined-field preview references, child table
  variant). The gallery covers only the columns on screen, so lightbox
  navigation never steps into a hidden column. A preview column in the
  first position is not turned into the row link (as in #49).
- ChildFieldMetaData.adornments is typed as FieldAdornment[] so it serves
  both CHIP detection (#50) and FILE_PREVIEW (#49).
- DataCell / FieldValue keep both DateTimeText and the preview imports.
- perf-budget.json: one shared 360 KB /app/_/_/dev cap; the note records
  the SQL Workbench, FILE_PREVIEW and #50/#53 measurements (expected
  about 355 KB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:35:17 -05:00
james.maesandClaude Opus 5.5 49788dd4e8 Merge main (SQL Workbench, #47) into feature/image-preview-lightbox
Resolves perf-budget.json: keep one /app/_/_/dev route cap of 360 KB and
combine the FILE_PREVIEW (+0.6 KB) and SQL Workbench (+3.1 KB) measurements
in the note (expected about 353.7 KB). Also corrects the unreleased note:
inline PDF frames are limited to backend /qqq/ paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:31:11 -05:00
james.maes 1412af5f69 Merge pull request #47 from QRun-IO/feature/sql-workbench
feat(sql-workbench): SQL Workbench notebook UI (SQL_WORKBENCH widget)
2026-10-11 16:30:07 -05:00
james.maesandClaude Opus 5.5 9150fb732f chore(perf): share the developer-route budget note with PR #53
Same 360 KB /app/_/_/dev cap; the note now records both measurements (this
PR 351.1 KB, home app PR #53 350.1 KB, main 350.0 KB) so both PRs carry an
identical perf-budget.json and merge cleanly in either order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:11:14 -05:00
james.maesandClaude Opus 5.5 e540b6fcbd chore(perf): give the record developer route its own 360 KB first-load cap
Measured in CI (static export + scripts/perf-budget.mjs): main is exactly at
the 350 KB default on /app/_/_/dev (350.0 KB, no headroom); this branch's home
app code makes it 350.1 KB. That route gets its own 360 KB cap; every other
route keeps the 350 KB default. The file change is identical to PR #50's, so
the two merge cleanly in either order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:11:11 -05:00
james.maesandClaude Opus 5.5 7f03ba1f4c fix(preview): table variant in gallery URLs; frame only backend PDFs
Thumbnails already carry the stored table variant on backend download
URLs, but the preview galleries did not, so the lightbox Open and
Download links for variant tables omitted it. Pass tableVariant to the
gallery in RecordView, AssociatedRecords (join table), ChildRecordListWidget
(child table from the instance metadata), DataGrid and RecordCardView.

AssociatedRecords now memoizes the shown page of records and its visible
fields so the gallery items are not rebuilt on every render.

The lightbox frames a PDF only when it is a same-origin /qqq/ backend path;
any other same-origin URL (e.g. a dashboard page stored in a field) opens
in a new tab instead of running in a frame with the dashboard's origin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:36:20 -05:00
james.maesandClaude Opus 5.5 68c2567f02 fix(sql-workbench): consume history inserts once; review nits
The history insert request is parent state and NotebookDraft remounts on
save, restore, duplicate and notebook switch, so the last history SQL was
re-appended on every remount (duplicate cells, injected into other
notebooks, spurious dirty state). The draft now applies each request nonce
once and calls onInsertConsumed, which clears it in the widget.

Also: key the result view and quick chart by executionUuid so paging and
chart state reset per run; show an error instead of "No notebooks yet."
when the notebook list fails; use aria-pressed toggle buttons instead of a
partial tab pattern for My notebooks / Shared library; disable the default
backend select and hide cell move/delete controls when the notebook is not
editable. Regression tests cover save remount, notebook switch, the list
error and the read-only controls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:31:33 -05:00
james.maesandClaude Opus 5.5 4e17d720f4 feat(navigation): one landing page - the home app is the single top nav entry
With homeAppName set, the home app is moved to the top of the sidebar in place
of the built-in Dashboard entry, whatever the backend's app order (kof22 labels
its home app "Dashboard"), so there is exactly one landing entry. Without
homeAppName the built-in Dashboard overview entry stays first, as before.

Test: no duplicate landing entry when homeAppName is set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:13:05 -05:00
james.maesandClaude Opus 5.5 984c26b62b Merge origin/main (Next UI 1.0.0-RC.12) into feature/responsive-readable-layout
Resolves docs/releases/unreleased.md: RC.12 moved the earlier product fixes
into its release notes; this branch's three entries are the only unreleased
product fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:10:04 -05:00
james.maesandClaude Opus 5.5 d1eae03894 feat(navigation): instance home app at /app, the sidebar logo and breadcrumb root
When the backend designates a home app (QInstance.homeAppName, QRun-IO/qqq#1059)
that the user may open, it is the instance's home page:

- /app client-side redirects to /app/{homeAppName} (backends such as
  Website-Backend already redirect full page loads of /app/ server-side; this
  covers client-side navigation), rendering a busy placeholder meanwhile
- the sidebar logo and the breadcrumb Home crumb link there
- the sidebar drops its separate Dashboard entry (the home app is in the tree)

Without homeAppName, or with an app missing from the user's metadata, /app is
Next's overview exactly as before. The route map exposes the result as
`homePath` (lib/utils/home-app.ts).

Tests: home-app helper, route map (home path, Dashboard entry), Breadcrumbs
and Sidebar links, /app page redirect vs overview. Docs: developer guide
routes and metadata, unreleased notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:08:27 -05:00
james.maesandClaude Opus 5.5 310c23f9cd fix(layout): apply the 24rem default record field threshold
The previous commit added the test but missed the constant itself: default
record fields now pair from a 24rem grid (tablet REC-004).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:55:24 -05:00
james.maesandClaude Opus 5.5 de387a61f7 fix(layout): pair default record fields from a 24rem grid
On a tablet the selected-section field grid beside the sidebar is ~416px, below
the 30rem form threshold, so two default-width read-only fields stacked where
they used to share a row (acceptance REC-004 on the tablet profile). Read-only
label/value pairs fit at ~180px each, so the default record field layout pairs
them from 24rem; phones still stack and explicit gridColumns keep the form
thresholds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:55:01 -05:00
james.maes e333223470 Merge pull request #48 from QRun-IO/feature/GH-712-next-rc12
chore(#712): prepare Next UI 1.0.0-RC.12
2026-10-11 12:03:09 -05:00
james.maesandClaude Opus 5.5 c064048c6f chore(perf): give the record developer route its own 360 KB first-load cap
Measured with `QQQ_NEXT_OUTPUT=export next build` + scripts/perf-budget.mjs:

- main b0b0d46: /app/_/_/dev 350.0 KB gzip (exactly at the 350 KB default cap)
- this branch:  /app/_/_/dev 351.1 KB (+1.1 KB: compact date formatter,
  child-list column ranking, container-query span tables)
- other routes stay under the unchanged 350 KB default (largest /app/_/dev
  347.1 KB); total JS 966.6 KB of 1050 KB; CSS 20.0 KB of 21 KB

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:49:23 -05:00
james.maesandClaude Opus 5.5 2b4d82309c test(acceptance): assert compact dates and the key-behind-toggle child list
- Record-view DATE and DATE_TIME values show compactly; assertions now check
  the text and the full zoned timestamp in the tooltip (or the <time dateTime>
  calendar day for DATE).
- A value in a declared zone other than the viewer's keeps its time and zone
  even in an older year (`Mar 10, 2024, 3:30 AM CDT`), so fixed- and
  per-record-zone fields still read in their zone (REC-019).
- Audit entries and script log/line timestamps: compact text, full title.
- WID-024: the key column is behind the "more columns" toggle while the name
  identifies the row; the toggle restores Id + Name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:48:52 -05:00
james.maesandClaude Opus 5.5 4fc3dfd7aa docs: note responsive layout, readable child lists and compact dates
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:43:57 -05:00
james.maesandClaude Opus 5.5 8c9619e7f7 test(mocks): CMS app with wide child lists for layout and date checks
Adds a mock CMS app (sites with domain and blog child lists, long descriptions
and meta tags, zoned timestamps including "today") so record views, embedded
child lists, the query grid and an app home can be checked at phone, laptop,
desktop and ultra-wide widths with `NEXT_PUBLIC_MOCK_API=true pnpm dev`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:43:00 -05:00
james.maesandClaude Opus 5.5 d3298cca2b feat(child-records): readable embedded child lists that fit their panel
A child-record list in a record-view panel showed every field of the child
table, so long text wrapped into tall, narrow columns. It now measures its
width and shows the columns that matter most and fit, ranked from metadata and
the rows on screen (compact-columns):

1. the record's name (first short text column, preferring T1 sections)
2. status columns (CHIP / possible values) and booleans
3. one date: the date column with the latest value (modified beats created)
4. other short text, numbers, other dates
5. long text last (TEXT / HTML / heavy, or values that run long in the rows)

The primary key is left out while a name column identifies the row (the row
still links to its record); it leads only when nothing else names the row. An
application that sets the widget's existing `onlyIncludeFieldNames` has chosen
its columns, and that order is their priority, so no new QQQ metadata is needed.

Hidden columns are one click away (`+N more columns` / `Fewer columns`, plus
View All). Cells align to the top, long text is clamped to three lines with the
full text on hover, and headers stay on one line. The CSV export keeps every
column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:42:59 -05:00
james.maesandClaude Opus 5.5 edaa1ffb33 feat(layout): size record, dashboard and form grids by their own width
QQQ sizes cards, widgets and fields in twelfths (gridColumns). Applied at the
viewport's `lg` breakpoint, a half-width card is half of what is left after the
sidebar, so two table panels sat side by side at ~350px on a laptop and stayed
side by side however narrow they got.

Grids are now CSS containers (`@container`) and their items take their declared
span only when the grid itself is wide enough for items of that kind:

- record-view cards (tables, field lists): stacked below 66rem, half from 66rem,
  at least a third from 100rem, the declared span from 120rem; overview cards
  keep their own height instead of stretching to the tallest in the row
- dashboard widgets: 40rem / 60rem / 76rem; table widgets use the card widths
- form and record fields: 30rem / 44rem / 56rem, so a create dialog or a narrow
  panel no longer crams three fields into a row; the default two-column record
  field layout widens to thirds and quarters on ultra-wide screens
- the record header's identity fields flow in a row, each as wide as its value

Material per-breakpoint widget sizes (gridCols:sizeClass:*) keep their
viewport behavior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:42:47 -05:00
james.maesandClaude Opus 5.5 c564a29ce8 feat(dates): one compact date/time presentation for every table, record view and widget
A shared formatter (compactDateTime / compactDate / relativeDateTime) and a
DateTimeText component replace the long `yyyy-MM-dd hh:mm:ss AM TZ` text that
wrapped over three lines in narrow cells:

- today: `6:33 PM`; this year: `Jul 5, 6:33 PM`; before: `Jul 5, 2025`
- DATE values never show a time and are never shifted by the viewer zone
  (DataCell read `2024-06-15` as UTC midnight and showed the 14th in the US)
- one line (nowrap, tabular figures), the full zoned timestamp as the tooltip,
  a machine-readable <time dateTime>
- the viewer's zone and locale; a backend display value zoned by a field
  behavior in another zone keeps that zone (`Jul 5, 4:33 PM PDT`)
- relative time (`3h ago`) for activity lists (ESB messages and counters)
- grid date columns default to widths that fit the compact form

Exports are unchanged: server exports and the child-list CSV still write the
raw or backend display values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:42:35 -05:00
james.maesandClaude Opus 5.5 3ab0ed88fa fix(preview): stack the record-view file name under its thumbnail (QRun-IO/qqq#1056)
In narrow record-view columns the name beside a 96/160 px thumbnail wrapped
character by character; it now sits under the thumbnail, truncated to its
width (full name in the title). Found in a local smoke against a real backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:39:20 -05:00
james.maesandClaude Opus 5.5 12ef8571f1 feat(preview): FILE_PREVIEW thumbnails and preview lightbox (QRun-IO/qqq#1056)
Fields with the backend FILE_PREVIEW adornment render as fixed-size, lazily
loaded thumbnails (file-type icons for non-images) wherever values are shown:
query grid and card view, record view (larger, with file name), associated
records, child-record list and record-grid widgets. Clicking or pressing Enter
opens an accessible preview dialog (Radix: focus trap, Escape, focus restore)
with zoom/fit, open-original and download, file details, arrow-key
navigation across the screen's previews, same-origin PDF, video and audio
players, and a file card fallback. Only explicitly adorned fields are
previewed; URLs are limited to http(s), same-origin paths and inline images.
Join columns resolve their prefixed values.

The lightbox is a lazy chunk (~3.5 KB gzip). Main measured /app/_/_/dev at
350.0 KB (the default cap); the thumbnail resolver, thumbnail and gallery
context add 0.6 KB to record routes (350.6 KB), so that route gets an explicit
360 KB cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:28:13 -05:00
james.maesandClaude Opus 5.5 a4c8eaf8c9 chore(#712): prepare Next UI 1.0.0-RC.12
Version 1.0.0-RC.12 (package.json, pom revision), RC12 preparation notes
collecting everything merged since RC11 (PR25-PR46, incl. the #45
always-available record Actions menu), reset unreleased notes, and updated
version references in README, guides and the testing policy. Backend
requirement moves to published QQQ 4.1 candidates (RC.1+, RC.2+ for
standalone branding images, QQQ #1009).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:52:03 -05:00
james.maesandClaude Opus 5.5 26ab79f752 feat(sql-workbench): SQL Workbench notebook UI for the SQL_WORKBENCH widget
Renders the qbit-sql-workbench QBit's SQL_WORKBENCH widget (its
sqlWorkbench app page) as a super-user SQL notebook over the
permission-checked /qqq/v1/sql-workbench endpoints, following the ESB
widget pattern (app presence advertises the feature):

- Notebook library (mine / shared), recent-run history, notebook
  editor with SQL and markdown cells, :parameters with types, save
  (versioned), publish, duplicate, delete, version restore, pinned
  result snapshots.
- CodeMirror 6 SQL editor: dialect per backend (PostgreSQL, MySQL,
  SQLite, standard), schema autocomplete plus QQQ table/field names
  that insert SQL names, Ctrl/Cmd+Enter to run; Tab is unbound so
  focus is never trapped. sql-formatter Format (four dialects only).
- Typed result grid (NULL, JSON, binary, numbers) with server-side
  paging and sorting over the cached result, copy as TSV, CSV/XLSX/
  JSON export links; Recharts quick charts; EXPLAIN plan tree with
  cost bars and raw view.
- Read-only by default; write mode (write permission only) previews
  a rolled-back dry run and commits only after WriteConfirmDialog
  (type the backend name, acknowledge the row guard).
- Run/cancel with a client-chosen execution id.
- CodeMirror and the formatter load only on that page; perf budget
  gains sqlWorkbench (127.2 KB) and sqlFormatter (60.2 KB) lazyChunks
  entries, and /app/_/_/dev's cap moves to 355 KB: it measured 353.1
  KB with the lazy import (main 350.0 KB at its cap; 350.1 KB without
  the WidgetRenderer hook-in).
- Tests: API client and helpers at 100%, component tests (widget
  flows, grid, plan, markdown, chart, real CodeMirror), MSW handlers,
  and a mocked Playwright spec (read-only run, write confirmation,
  axe).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:14:22 -05:00
james.maes b0b0d469fe Merge pull request #46 from QRun-IO/fix/45-record-actions-menu
fix(record-view): always show the Actions menu when it has entries (#45)
2026-10-11 10:04:06 -05:00
james.maesandClaude Opus 5.5 3ceed91214 test(acceptance): PRC-050 record menu also offers Audit (#45)
The default record Actions menu ends with Audit for users who can read
audits, so assert Developer Mode and Audit are present rather than
Developer Mode being last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:58:42 -05:00
james.maesandClaude Opus 5.5 4e12a5d524 fix(record-view): always show the Actions menu when it has entries (#45)
RecordViewHeader passed an empty entry list to the desktop RecordActions
unless the table had processes or a custom VIEW_SCREEN_ACTIONS menu, so
plain tables lost Developer Mode and Audit. The Actions controls (desktop
and phone) were also gated on write permissions or processes, hiding them
from read-only tables that still offer Developer Mode and Audit.

Pass the resolved default/custom menu entries through unchanged and show
the Actions controls whenever that menu has items. RecordActions keeps
de-duplicating Copy/Delete via menuOffersAction, so those now live in the
menu instead of also appearing as buttons, matching Material.

Update the component tests and the PRC-050 / REC-046 acceptance specs
that asserted the old missing-menu behavior.

Closes #45

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:53:46 -05:00
james.maes 18d1fd6e37 Merge pull request #44 from QRun-IO/feature/GH-660-header-off-bulk-load
fix: omit null header names from header-off bulk loads (QRun-IO/qqq#660)
2026-10-09 18:19:25 -05:00
james.maes eee644a280 Merge pull request #43 from QRun-IO/feature/GH-723-record-view-process-order
fix: sort record view processes by label (QRun-IO/qqq#723)
2026-10-07 07:15:16 -05:00
james.maes 7682aa1ff0 fix: omit null header names from header-off bulk loads (QRun-IO/qqq#660) 2026-10-06 21:00:27 -05:00
james.maes 8e334cd944 fix: sort record view processes by label (QRun-IO/qqq#723)
Matches Material RecordView: the table's own processes sort by label, all-screens processes keep their configured order, and the query page is unchanged.
2026-10-06 18:08:48 -05:00
james.maes 255554a184 Merge pull request #35 from QRun-IO/feature/GH-1001-dashboard-readiness
test(navigation): await dashboard dependencies before departure
2026-10-06 17:58:55 -05:00
james.maes e1d60b9634 Merge pull request #32 from QRun-IO/feature/widget-date-defaults
fix(widgets): apply backend defaults to untouched date pickers
2026-10-06 17:58:44 -05:00
james.maes d81b7ed5ae chore(#1002): integrate current main for date-default verification 2026-10-06 07:25:30 -05:00
james.maes b8313b6819 chore(#1001): integrate current main for readiness verification 2026-10-06 07:25:21 -05:00
james.maes 22c9e02f20 test: consolidate browser capture and security review follow-ups (#42)
* test(#1003): await complete chart label geometry

Keep the original strict overlap check on the same completed measurement sample. Refs QRun-IO/qqq#1003; historical rendering cause remains unproved.

(cherry picked from commit 4aa95cc363)

* test(acceptance): capture selected navigation request ownership

(cherry picked from commit bd7716fe8d)

* test(acceptance): integrate session capture with selective navigation

(cherry picked from commit 6c5d6d92e14536ed472b2c8357a00c652718198b)

* docs(#696): reconcile merged security qualifications

(cherry picked from commit b6106062bb09381407e92bdb55352175062941d2)
2026-10-04 18:48:11 -05:00
james.maes 138c3c6db5 fix: derive standalone CSP from application metadata (QRun-IO/qqq#1017) (#40)
* fix: derive standalone CSP from application metadata (#1017)

Consume optional bounded CSP source metadata through the existing auth cache. Remove fixture-derived policy masking and qualify the actual analytics hook.

* docs(qqq#696): reconcile current security integration evidence

* test(qqq#1022): close owned analytics fixture connections
2026-10-04 17:07:09 -05:00
james.maes 70f26e9c9a Merge pull request #38 from QRun-IO/feature/GH-1009-standalone-branding
fix: complete standalone security and branding acceptance
2026-10-04 14:19:35 -05:00
james.maes 2bc75d779e Merge qualified Auth0 fixture correction into branding acceptance 2026-10-04 12:55:09 -05:00
james.maes 17e22edaee Merge reviewed NAV-018 readiness correction into branding acceptance 2026-10-04 12:54:47 -05:00
james.maes 66d02c7b6a test: align owned Auth0 issuer with trusted provider contract (#1016) 2026-10-04 12:53:20 -05:00
james.maes 20fe7b7b9b test(GH-1014): await process response completion before navigation 2026-10-04 11:55:45 -05:00
james.maes 4190ee01ba test: restrict acceptance javac to owned fixture sources (QQQ #1013) 2026-10-04 10:35:06 -05:00
james.maes 62d1e77847 fix: route declared standalone branding images through fixed backend roles 2026-10-04 10:21:43 -05:00
james.maes a23fd997db test: qualify standalone security variants against real Node origins 2026-10-04 10:06:27 -05:00
james.maes f367756b8c Merge pull request #36 from QRun-IO/feature/GH-1006-next-query-cleanup
fix(qqq#1006): clear private query state between users
2026-10-04 09:06:18 -05:00
james.maes 4a055463bd fix(GH-1006): clear private query state between users
Refs QRun-IO/qqq#1006 and QRun-IO/qqq#696.
2026-10-02 09:41:42 -05:00
james.maes c1a4efd7d7 test(navigation): integrate current main readiness (#1001) 2026-10-02 07:58:11 -05:00
james.maes 0222f0dcad test(widgets): await resized chart label geometry (#31) 2026-10-02 06:58:12 -05:00
james.maes 4fea636f24 Merge reviewed main before widget acceptance qualification (#31) 2026-10-02 06:56:43 -05:00
james.maes 5ca3e0b17a Merge pull request #30 from QRun-IO/feature/GH-995-cell-focus-investigation
fix(qqq#995): preserve grid body-cell focus and state
2026-10-02 05:40:58 -05:00
james.maes e0fb2598b0 test(navigation): separate unit and browser discovery (#1001) 2026-10-02 05:33:32 -05:00
james.maes 61286717e5 test(navigation): await dashboard dependencies before leaving (#1001) 2026-10-02 05:13:33 -05:00
james.maes a32ef74a9d chore(GH-995): integrate reviewed main for cell focus 2026-10-02 04:21:31 -05:00
james.maes d292c76530 Merge pull request #28: preserve report-copy keyboard focus
Fixes QRun-IO/qqq#994. Source-specific required checks verified.
2026-10-02 04:18:17 -05:00
james.maes e3fc4b2f52 chore(qqq#994): integrate current main for verification 2026-10-02 03:00:53 -05:00
james.maes b5f8be7645 Merge pull request #27 from QRun-IO/feature/GH-716-menu-viewport
fix(query): keep column menus reachable after scrolling
2026-10-02 02:57:12 -05:00
james.maes 8f552e2e29 Merge current acceptance evidence baseline for date fix (#31) 2026-10-02 02:21:54 -05:00
james.maes 1f18575abe test(widgets): await default selection request (#31) 2026-10-02 02:15:08 -05:00
james.maes 6a5c1bb237 fix(widgets): preserve date request normalization (#31) 2026-10-02 02:09:21 -05:00
james.maes 58a896345f Merge pull request #29 from QRun-IO/feature/GH-904-document-capture
test(qqq#904): capture document and XHR boundaries
2026-10-02 02:00:41 -05:00
james.maes bd904fe4ac fix(widgets): apply untouched date defaults (#31) 2026-10-02 01:41:59 -05:00
james.maes 09c271384c test(qqq#996): wait for report setup before pivot input 2026-10-02 01:39:17 -05:00
james.maes 05763450ff fix(qqq#995): preserve grid body-cell focus and state
Invoke locally owned pure cell renderers as content so selection and refresh retain focused children and current callbacks.
2026-10-02 00:32:46 -05:00
james.maes aa0129f024 test(qqq#904): capture document and XHR boundaries
Add opt-in bounded evidence for QRun-IO/qqq#904 and #960. Preserve native outcomes and mark ambiguous request reuse incomplete.
2026-10-02 00:31:09 -05:00
james.maes 2bdc048440 fix(query): preserve report column copy focus (#994)
Keep current header callbacks and child identity across preview updates.
2026-10-02 00:02:07 -05:00
james.maes 7bd157dd81 fix(query): reveal column anchor before opening menu (#716)
Restore keyboard menu reachability after scrolling a focused header away.
2026-10-01 23:24:21 -05:00
james.maes 2535e98c7e Merge pull request #25 from QRun-IO/feature/GH-904-transport-capture
test(browser): retain transport and query retry evidence
2026-10-01 22:44:07 -05:00
james.maes 2853b9b148 Merge pull request #26 from QRun-IO/feature/GH-712-next-rc11
chore(#712): prepare Next UI 1.0.0-RC.11
2026-10-01 22:19:50 -05:00
james.maes 29cb5584fc chore(#712): prepare Next UI 1.0.0-RC.11 2026-10-01 22:14:30 -05:00
james.maes f36d8709c9 test(GH-900): cover production query retry lifecycle 2026-10-01 22:05:22 -05:00
james.maes f7b2aa52a8 test(GH-904): capture acceptance response write boundaries 2026-10-01 22:05:08 -05:00
james.maes a1d3e48419 Merge pull request #24 from QRun-IO/feature/GH-970-browser-input-races
fix(browser): stabilize selector and script completion input races
2026-10-01 21:56:06 -05:00
james.maes ea73118fca docs(GH-985): record browser input race corrections
Refs QRun-IO/qqq#970 and QRun-IO/qqq#954.
2026-10-01 21:09:34 -05:00
james.maes c629eb0b6c fix(GH-954): preserve suggestions after deferred scroll events
Dismiss completions only when the textarea moves from the position used to place them. Cover deferred reset and both real scroll axes.

Refs QRun-IO/qqq#954
2026-10-01 21:09:31 -05:00
james.maes 571ee7bc88 fix(GH-970): invalidate stale possible values when search changes
Close the debounce interval in which an old option can disappear during a pointer selection. Cover loaded and in-flight prior searches with deterministic regressions.

Refs QRun-IO/qqq#970
2026-10-01 20:59:51 -05:00
james.maes 12cf7745f6 Merge pull request #23 from QRun-IO/feature/GH-990-esb-touch-coverage
test(GH-990): include all ESB touch acceptance scenarios
2026-10-01 20:52:56 -05:00
james.maes 0f6581b6d7 chore(GH-990): reconcile touch coverage with RC10 preparation 2026-10-01 20:43:46 -05:00
james.maes 46a1d5d7ec Merge pull request #22 from QRun-IO/feature/GH-712-next-rc10
chore(release): prepare Next UI 1.0.0-RC.10
2026-10-01 20:36:14 -05:00
james.maes 226efa841f Merge pull request #21 from QRun-IO/feature/QQQ-989-pr15-reconciliation
fix(records): preserve immediate URL state (QQQ#989)
2026-10-01 20:35:01 -05:00
james.maes dd130238be test(GH-990): include ESB in touch acceptance
Closes QRun-IO/qqq#990. Preserve real-backend assertions and record the original coverage failure.
2026-10-01 20:18:27 -05:00
james.maes d8c803ef5f chore(release): prepare Next UI 1.0.0-RC.10
Record verified candidate scope and disclosed coverage limits. Refs QRun-IO/qqq#712.
2026-10-01 20:15:37 -05:00
james.maes 5e3738b73f fix(records): preserve immediate record view URL state
Closes QRun-IO/qqq#989
2026-10-01 20:00:16 -05:00
james.maes 289eca19d4 Merge pull request #18 from QRun-IO/feature/browser-stability
fix(browser): stabilize Firefox and WebKit interactions and acceptance
2026-10-01 19:44:38 -05:00
james.maes 7ff90f1a63 chore(GH-985): integrate ESB acceptance with browser fixes 2026-10-01 18:46:40 -05:00
james.maes 50a935a4a5 Merge pull request #19 from QRun-IO/feature/GH-10-esb-acceptance
fix(esb): finish management and real-broker acceptance (#10)
2026-10-01 18:30:11 -05:00
james.maes 40e987d4d0 Merge pull request #20 from QRun-IO/feature/GH-985-next-tracking
docs(tracking): manage Next UI through the QQQ project
2026-10-01 18:09:28 -05:00
james.maes 71f5a39b09 docs(tracking): centralize Next UI delivery and unreleased fixes 2026-10-01 18:04:34 -05:00
james.maes 45f99f27ce fix(auth): finish logout before allowing another sign-in
Keep authentication loading through cookie expiry and provider departure (QRun-IO/qqq#984).
2026-10-01 17:40:38 -05:00
james.maes d17e880e79 test(widgets): recognize proven missing script responses 2026-10-01 17:25:44 -05:00
james.maes f8fdb8e997 fix(esb): finish management and real-broker acceptance (#10) 2026-10-01 17:23:24 -05:00
james.maes ad154b5b56 test(acceptance): observe browser framing refusal (#981) 2026-10-01 17:22:43 -05:00
james.maes c6a58933a6 test(#979): account for Linux WebKit download filenames
Verify original application filenames and preserve downloaded data checks while matching the pinned libsoup space sanitization.
2026-10-01 17:16:51 -05:00
james.maes 26e3a0d004 test(acceptance): await initial document fonts before redirect
Complete the rendered initial document before NAV016 tears it down (QRun-IO/qqq#951).
2026-10-01 17:08:48 -05:00
james.maes c9d7fcd393 fix(forms): preserve option pointer targets in WebKit 2026-10-01 17:06:35 -05:00
james.maes b3e1ddda8b test(browser): adopt fixed WebKit network library and retain crash diagnostics
Refs QRun-IO/qqq#904, QRun-IO/qqq#960, QRun-IO/qqq#973. Preserve final RC9 tag outcomes separately from current verification.
2026-10-01 17:06:23 -05:00
james.maes 8d4668cdab test(acceptance): await breadcrumb destination iframe
Require the owned chart heading before leaving Greetings App so navigation does not cancel its pending document. Refs QRun-IO/qqq#972.
2026-10-01 16:53:35 -05:00
james.maes 254a0a2050 test(reports): await summary metadata before navigation
Refs QRun-IO/qqq#961.
2026-10-01 16:39:39 -05:00
james.maes 52cc038fa6 test(acceptance): observe transient Firefox feedback reliably
Capture toast visibility before actions and verify navigation-cancelled legacy fonts in the final document. Refs QRun-IO/qqq#951, QRun-IO/qqq#953, QRun-IO/qqq#967.
2026-10-01 16:39:23 -05:00
james.maes 6090f2f9b4 fix(search): preserve focus after early keyboard interaction
Refs QRun-IO/qqq#950.
2026-10-01 16:32:48 -05:00
james.maes 32a9560970 Merge pull request #17 from QRun-IO/feature/rc9-publication-notes
docs: record verified RC9 public delivery
2026-10-01 14:29:27 -05:00
james.maes 96085c72e2 docs(release): record verified RC9 public delivery
Refs QRun-IO/qqq#712. Preserve immutable source and distinguish active tag acceptance from completed checks.
2026-10-01 14:28:27 -05:00
james.maes c029ad2a70 Merge pull request #14 from QRun-IO/feature/next-1.0
Next UI 1.0 RC9 integration: compatibility, verification and issue tracking
2026-10-01 14:12:28 -05:00
james.maes 32a3b281ff chore(release): prepare Next UI 1.0.0-RC.9
Refs QRun-IO/qqq#712 and QRun-IO/qqq#713. Preserve known issue disclosures and the final release hold.
2026-10-01 14:11:48 -05:00
james.maes a0a18cfa76 Merge pull request #16 from QRun-IO/feature/next-issue-tracking
docs: make GitHub the Next UI source of truth
2026-10-01 14:06:09 -05:00
james.maes 354ee2709d docs(tracking): make GitHub the Next UI source of truth
Reconcile the roadmap and issue evidence under QRun-IO/qqq#713; point guides and ledgers to the live tracker.
2026-10-01 07:21:51 -05:00
james.maes 3c7c0e88b7 docs: announce verified RC8 artifacts and known issues 2026-10-01 06:41:25 -05:00
james.maes 0679672aec docs: preserve RC8 secondary browser outcomes 2026-09-30 21:26:58 -05:00
james.maes 6d0862284a docs: record RC8 acceptance and public preview evidence 2026-09-30 21:08:56 -05:00
james.maes cd3d4731d1 docs: record RC8 corrections and process visual comparison (#14) 2026-09-30 19:39:44 -05:00
james.maes 2eb075610e fix: fit API specification labels to application fonts (#14) 2026-09-30 19:38:27 -05:00
james.maes a317f0292d fix: keep search dialog keyboard focus contained (#14) 2026-09-30 19:38:27 -05:00
james.maes 14d69667dc docs: record RC8 sleep interruption and final RC7 results (#14) 2026-09-30 19:01:54 -05:00
james.maes f0f0eae98e chore: prepare RC8 stability candidate with open gate evidence (#14) 2026-09-30 16:21:06 -05:00
james.maes 66ccd34642 fix: validate bounded long values without rounding or throwing (#14)
Gate range checks behind integer validation and preserve exact integer-string bounds. Refs QRun-IO/qqq#728.
2026-09-30 16:13:51 -05:00
james.maes c954005332 test: scope process choice label lookup to its view field (#14) 2026-09-30 16:13:51 -05:00
james.maes 33cfb0b322 fix: keep API specification controls visible in narrow panels (#14)
Scope responsive button rules to the overview while preserving desktop layout. Refs QRun-IO/qqq#724.
2026-09-30 16:13:51 -05:00
james.maes b9b1f3da84 docs: record verified RC7 publication and final RC6 results (#14) 2026-09-30 16:11:44 -05:00
james.maes f65cf6bfbd chore: prepare verified local RC7 candidate (#14) 2026-09-30 15:16:17 -05:00
james.maes d5f275f85e test: check accessible sidebar labels after icons load (#728) 2026-09-30 15:12:20 -05:00
james.maes d4795c80e6 fix: retain second precision in time input blocks (#728) 2026-09-30 15:12:20 -05:00
james.maes b8b361d8da fix: improve API playground theme contrast (#724) 2026-09-30 15:12:20 -05:00
james.maes 12c21a7560 docs: record terminal RC6 candidate browser results (#14) 2026-09-30 15:11:00 -05:00
james.maes d4e3289c61 docs: record verified RC6 publication and visual review (#14) 2026-09-30 15:08:06 -05:00
james.maes 7ddf45fa4d chore: prepare verified local RC6 candidate (#14) 2026-09-30 14:13:40 -05:00
james.maes df33189ab4 fix: render legacy metadata icon names locally (#728) 2026-09-30 14:11:09 -05:00
james.maes bad28d9e21 docs: record final RC5 candidate results (#14) 2026-09-30 13:54:29 -05:00
james.maes 8900d47e28 docs: retain RC5 secondary browser results (#14) 2026-09-30 13:48:46 -05:00
james.maes fcf08769d6 docs: record verified RC5 publication (#14) 2026-09-30 13:23:00 -05:00
james.maes ac22946975 docs: record complete RC5 local verification (#14) 2026-09-30 12:25:19 -05:00
james.maes fbe9d148ec fix: persist widget field clears through record forms (#14) 2026-09-30 12:11:13 -05:00
james.maes 90a456ddcf docs: record RC5 candidate verification (#14) 2026-09-30 11:57:41 -05:00
james.maes 263f92f7bd chore: prepare Next UI 1.0.0-RC.5 candidate (#14) 2026-09-30 11:36:25 -05:00
james.maes 977c1dfda3 fix: keep column menu actions reachable in short windows (#716) 2026-09-30 11:35:38 -05:00
james.maes a8e4d3b210 fix: retain report editing preview when summary is hidden (#722) 2026-09-30 11:17:58 -05:00
james.maes 9fb613d04c fix: restore original record overview spacing (#716) 2026-09-30 10:55:38 -05:00
james.maes b1c0cedbe0 fix: restore original Next table row backgrounds (#716) 2026-09-30 10:46:37 -05:00
james.maes 0316c7a980 docs: publish verified RC4 installation and evidence (#14) 2026-09-30 09:42:17 -05:00
james.maes 3c409f9186 docs: record integrated RC4 candidate verification (#14) 2026-09-29 22:32:54 -05:00
james.maes f2cdf03bf0 fix: keep density menu open after touch focus (#694) 2026-09-29 22:11:31 -05:00
james.maes 3676fe9980 fix: preserve numeric block values while scrolling (#728) 2026-09-29 21:52:51 -05:00
james.maes 1dbe1d91ef chore: prepare 1.0.0-RC.4 candidate (#14) 2026-09-29 21:42:17 -05:00
james.maes 5be45cd0e1 fix: preserve omitted joined fields in report editors (#722) 2026-09-29 21:34:14 -05:00
james.maes b1495c4a44 docs: finalize RC3 browser verification (#14) 2026-09-29 21:28:48 -05:00
james.maes 1283bac07e docs: record RC3 tag Firefox failures (#14) 2026-09-29 21:12:26 -05:00
james.maes b971340c39 fix: honor omitted joins in report previews (#722) 2026-09-29 21:11:52 -05:00
james.maes 5ae0c8c339 docs: retain completed RC3 browser results (#14) 2026-09-29 21:07:31 -05:00
james.maes 10094ee885 docs: distinguish post-RC3 password coverage (#728) 2026-09-29 20:37:52 -05:00
james.maes f7ae67f13d fix: honor explicit password case behaviors (#728) 2026-09-29 20:36:22 -05:00
james.maes cb51aa614e docs: record published RC3 artifacts and validation (#14) 2026-09-29 20:36:21 -05:00
james.maes f1a6a3a168 docs: retain completed pre-RC3 browser results (#14) 2026-09-29 19:49:10 -05:00
james.maes ca9b5374f4 docs: record corrected RC3 acceptance gate (#14) 2026-09-29 19:46:49 -05:00
james.maes 1bd7bdcb94 fix: preserve caret across Unicode case expansion (#728) 2026-09-29 19:41:21 -05:00
james.maes dc640530f9 docs: record RC3 dependency audit (#14) 2026-09-29 19:20:19 -05:00
james.maes ef1f407993 chore: prepare verified RC3 package (#14) 2026-09-29 19:09:35 -05:00
james.maes e71ed784bd fix: honor case behavior in multiline fields (#728) 2026-09-29 19:06:30 -05:00
james.maes 73adda133c docs: record published RC2 verification (#14) 2026-09-29 19:02:07 -05:00
james.maes 9e98626d9d test: verify input block case behavior (#14)
Cover QRun-IO/qqq#728 case conversion, caret preservation, and persisted process submissions.
2026-09-29 18:40:31 -05:00
james.maes 0905cfb0cd fix: preserve parent widget CSV export (#14) 2026-09-29 17:32:16 -05:00
james.maes 542ac47593 fix: restore child export header and recovery (#14) 2026-09-29 17:02:23 -05:00
james.maes 222199916e fix: honor report preview query capabilities (#14) 2026-09-29 16:27:44 -05:00
james.maes defb3abc82 chore: prepare verified RC2 candidate (#14) 2026-09-29 16:14:20 -05:00
james.maes e87fe90d91 fix: restore original Next record layout defaults (#14) 2026-09-29 15:54:25 -05:00
james.maes d346d9bffd fix: expose pinned selection touch targets (#14) 2026-09-29 15:54:22 -05:00
james.maes 201a73901e docs: hold final release for RC1 application testing (#14) 2026-09-29 15:53:28 -05:00
james.maes d937507a2e feat: add native script editor completions (#14) 2026-09-29 15:26:02 -05:00
james.maes bf8fa4e1c2 docs: record merged backend fixes and Chromium gate (#14) 2026-09-29 15:18:35 -05:00
james.maes 95847c25ab fix: expose active filters in report preview headers (#14) 2026-09-29 14:58:03 -05:00
james.maes 303ba61fe1 fix: scope report previews and statistics to backend variants (#14) 2026-09-29 14:33:09 -05:00
james.maes 44c36fc87d fix: restore report saved-view reset behavior (#14) 2026-09-29 14:08:04 -05:00
james.maes eeab664e34 docs: record merged Google Drive backend prerequisite (#14) 2026-09-29 13:45:08 -05:00
james.maes 0fcd28344d feat: integrate configured Google Drive folder selection (#14) 2026-09-29 13:41:05 -05:00
james.maes b9a49ad29d test: deepen report statistics and CSV verification (#14) 2026-09-29 13:00:09 -05:00
james.maes 69766f1902 docs: record verified RC1 publication and browser evidence (#14) 2026-09-29 12:47:32 -05:00
james.maes e901df9101 fix: keep themed process error details readable (#14) 2026-09-29 11:55:48 -05:00
james.maes 49e51158aa fix: keep Maven publication aligned with the RC tag (#14) 2026-09-29 11:46:38 -05:00
james.maes 381cce27e6 chore: prepare RC1 with a separate Chromium gate (#14) 2026-09-29 11:41:37 -05:00
james.maes 1395a8b7d7 fix: keep report columns valid when changing tables (#14) 2026-09-29 11:25:17 -05:00
james.maes 093b8d758c fix(#719): expose persistent appearance preferences 2026-09-29 10:59:36 -05:00
james.maes 654aa28a9f fix(#728): fit widget editors across native fonts 2026-09-29 10:44:05 -05:00
james.maes 9732c56c99 fix(#649): keep query and report errors readable 2026-09-29 10:32:18 -05:00
james.maes 113785600d fix(#649): preserve column editor keyboard focus 2026-09-29 09:52:50 -05:00
james.maes a0d63ae981 fix(#649): restore compact query filter controls 2026-09-29 09:35:37 -05:00
james.maes a03a0ab7da fix(#679): retain timestamps in copies and child edits 2026-09-29 09:21:56 -05:00
james.maes 30db3098ad fix(#728): preserve input timestamp instants and seconds 2026-09-29 09:10:29 -05:00
james.maes e56ef0d7c4 fix(#728): preserve block input focus and Enter values 2026-09-29 08:55:46 -05:00
james.maes 5f47ffc630 fix(#649): make record section tabs keyboard accessible 2026-09-29 08:37:26 -05:00
james.maes 8484779d51 fix(#649): restore compact report input presentation 2026-09-29 08:28:22 -05:00
james.maes 121468b049 fix(#717): synchronize query URLs before immediate reload 2026-09-29 08:14:23 -05:00
james.maes 16dd6a7ba9 fix(#728): bind composite metadata editors without replacing plain inputs 2026-09-29 07:58:18 -05:00
james.maes d969630e50 fix(#649): complete report tab navigation and validation contrast 2026-09-29 07:13:28 -05:00
james.maes 06cf1b0c91 fix(#649): restore inline bulk profile naming 2026-09-29 06:44:43 -05:00
james.maes d1db0cd002 fix(#649): repair menu focus and preferences layering 2026-09-29 06:32:18 -05:00
james.maes 096c55adbe fix(#649): restore bulk profile grouping and status 2026-09-29 06:03:00 -05:00
james.maes 4748b6c594 fix(#649): restore compact filter and chart presentation 2026-09-29 05:40:44 -05:00
james.maes 5077c78afd fix(#649): recover Next visuals and stabilize query controls 2026-09-29 05:20:31 -05:00
james.maes c44261376a feat(#722): complete embedded report query controls 2026-09-29 04:33:43 -05:00
james.maes 6439944150 fix(#711): fit query headings and relative date menus 2026-09-28 21:17:16 -05:00
james.maes 6027e65065 fix(#711): contain query menus and restore keyboard navigation 2026-09-28 20:50:40 -05:00
james.maes 93b7b5a433 fix(#711): align native controls with the selected theme 2026-09-28 20:24:37 -05:00
james.maes 0f96c81dc8 fix(#711): recover filter actions and browser acceptance 2026-09-28 20:14:48 -05:00
james.maes 9d29663fda fix(#711): add interactive read-only report previews 2026-09-28 20:14:29 -05:00
james.maes 0e71bb03c6 fix(#711): contain record tabs and number widget context 2026-09-28 19:32:10 -05:00
james.maes 083db18e40 fix(#711): compact filter and process widget spacing 2026-09-28 19:29:00 -05:00
james.maes 384c360c03 test(#711): wait for query input in shortcut checks 2026-09-28 19:15:11 -05:00
james.maes 5cb2f5a922 fix(#711): keep query menus visible and keyboard accessible 2026-09-28 19:12:22 -05:00
james.maes 75044c1781 docs(#711): reconcile Next guides with restored defaults 2026-09-28 19:00:40 -05:00
james.maes 1af7254bc4 fix(#722): verify versioned reports and editable preview grids 2026-09-28 19:00:14 -05:00
james.maes 214cb9c88e fix(#711): restore phone titles and compact process actions 2026-09-28 18:59:31 -05:00
james.maes 7524057454 fix(#711): restore Next query, record and widget presentation 2026-09-28 18:43:35 -05:00
james.maes bf97d71e22 Lazy load full copy editor to meet bundle budget 2026-09-27 03:48:22 -05:00
james.maes 97dd622fe1 Verify report filter variables and block unresolved queries 2026-09-27 03:34:03 -05:00
james.maes c89da89ce5 Verify cron editor parity across five browser profiles 2026-09-27 03:22:24 -05:00
james.maes 38c75a49eb feat(forms): render WIDGET-adorned filter fields (#722) 2026-09-26 19:42:56 -05:00
james.maes 609d016c50 feat(widgets): add setup entry points and ordered sorts (#722) 2026-09-26 19:28:58 -05:00
james.maes 1694580521 feat(records): render qualified fields from readable joins (#722) 2026-09-26 19:17:34 -05:00
james.maes 658116dd83 fix(records): carry selected variant through record pages and files (#722) 2026-09-26 19:00:25 -05:00
james.maes c14e5aedb5 feat(records): honor record view layout metadata (#723) 2026-09-26 18:47:34 -05:00
james.maes 0aa65e2a26 feat(records): restore audit and T1 parity (#723) 2026-09-26 18:18:06 -05:00
james.maes f4faeeab56 docs(parity): reconcile script developer tools (#724) 2026-09-26 18:05:52 -05:00
james.maes 5d7e76e6d2 docs(parity): reconcile report process screens (#727) 2026-09-26 18:03:03 -05:00
james.maes ca400d5cee docs(parity): reconcile bulk load fidelity (#726) 2026-09-26 18:01:32 -05:00
james.maes c7835b8968 docs(parity): reconcile integrated process widgets (#725) 2026-09-26 17:58:46 -05:00
james.maes 7b2386dc75 docs(parity): reconcile child and widget controls (#728) 2026-09-26 17:54:58 -05:00
james.maes 6407176134 docs(parity): reconcile completed widget behaviors (#728) 2026-09-26 17:52:27 -05:00
james.maes 37bb68e659 fix(query): hide joins through unreadable tables (#696)
Reconcile deletion feedback parity (#717) and stabilize the REC-060 tablet navigation.
2026-09-26 17:50:46 -05:00
james.maes ddda45d7de docs(parity): verify unreachable Material paths (#732) 2026-09-26 17:40:18 -05:00
james.maes 04df7cc7a4 fix(auth): preserve Material Auth0 callback and errors (#732) 2026-09-26 17:35:34 -05:00
james.maes 3358f53db8 docs(parity): reconcile shell, auth and help coverage (#732) 2026-09-26 17:20:48 -05:00
james.maes 7736a6e815 feat(forms): honor inline possible values end to end (#721) 2026-09-26 17:15:22 -05:00
james.maes 642a350e2a docs(parity): reconcile mounted release coverage (#722, #729, #730) 2026-09-26 16:54:30 -05:00
james.maes 6ff2272fa1 feat(records): persist hosted dynamic form values (#722) 2026-09-26 16:54:10 -05:00
james.maes bac4c4e287 docs(parity): reconcile records and widget coverage (#723, #728) 2026-09-26 16:23:58 -05:00
james.maes 2b123fd1b3 fix(query): contain compact grid header actions (#728) 2026-09-26 16:23:43 -05:00
james.maes ed95420e66 test: prove form adjusters against versioned QQQ metadata (#720) 2026-09-26 15:58:52 -05:00
james.maes 15a9918f8f test: synchronize record menu keyboard navigation (#649) 2026-09-26 15:35:27 -05:00
james.maes 84c4846a16 test: cover Material theme against real QQQ metadata (#719) 2026-09-26 15:25:23 -05:00
james.maes 3e4d7c9520 perf: align CSS budget with release CI measurement (#710) 2026-09-26 15:00:10 -05:00
james.maes 3b20c412c5 test: cover release utilities and filter confirmation (#649) 2026-09-26 15:00:02 -05:00
james.maes 3e5bed9c9c chore: prepare Next UI 1.0.0 release metadata (#649) 2026-09-26 14:40:52 -05:00
james.maes f1e92f3be8 test: align release acceptance with Material parity (#649) 2026-09-26 14:40:52 -05:00
james.maes b55bc57f6a fix: show touch section icons and render Material link glyphs (#649) 2026-09-26 14:40:52 -05:00
james.maes 560379501c perf: split release screens and budget bundles 2026-09-26 14:40:52 -05:00
james.maes d11741f2e0 feat: edit saved report filters and columns (#722) 2026-09-26 14:40:52 -05:00
james.maes c46dfc02dc fix: verify HttpOnly session and process permissions 2026-09-26 14:40:52 -05:00
james.maes 7f299e2d03 docs: refresh Next UI guides for integrated release 2026-09-26 14:40:52 -05:00
james.maes a5322e553a feat(widgets): complete row builder editor parity (#727) 2026-09-26 14:40:51 -05:00
james.maes 1a31c75808 feat(widgets): complete pivot table editor parity (#726) 2026-09-26 14:40:51 -05:00
james.maes 803a7ecd77 feat(widgets): complete filter and column setup parity (#725) 2026-09-26 14:40:51 -05:00
james.maes 368d702581 feat(ui): complete metadata shell and widget parity (#715 #723) 2026-09-26 14:40:51 -05:00
james.maes e09ba6f79e fix(security): support HttpOnly sessions and dashboard headers (#733 #734) 2026-09-26 14:39:23 -05:00
james.maes 3437e6d8b2 test(theme): align CSS hook acceptance with form layout (#731) 2026-09-26 12:58:42 -05:00
james.maes d61f12f28f feat(theme): preserve Material CSS hooks (#731) 2026-09-26 12:51:59 -05:00
james.maes 10f0744b86 feat(theme): support Material branding and analytics (#719 #730) 2026-09-26 12:43:59 -05:00
james.maes 0dfc4d9ed2 feat(process): complete bulk load workflow (#734) 2026-09-26 12:41:53 -05:00
james.maes dbf600e894 feat(process): complete process and report workflows (#725 #736) 2026-09-26 12:40:37 -05:00
james.maes 72f5a07ea6 fix(records): honor metadata grid width and current headings (#730) 2026-09-26 12:39:28 -05:00
james.maes 9b4142c1b0 feat(developer): complete scripts and data bag tools (#723) 2026-09-26 12:29:46 -05:00
james.maes 49b3e1720b feat(forms): finish Material form layout and save feedback (#724) 2026-09-26 12:27:48 -05:00
james.maes 4e51b5ddd8 test(records): expect static export process route
The release route helper includes the trailing slash required by the Javalin-served Next export. Focused record action tests pass.
2026-09-26 12:18:58 -05:00
james.maes 2336e39573 feat(records): render metadata driven record actions (#723)
Resolve custom and default menus, run named processes, deliver record files, and preserve phone action behavior. TypeScript, ESLint, license, and 1262 unit tests pass.
2026-09-26 12:17:10 -05:00
james.maes 962d48869c test(acceptance): give the scripts fixture the API columns of the sample's API (#738)
With qqq-middleware-api on the sample's classpath, the scripts model adds
apiName and apiVersion to scriptRevision, so the owned script_revision
table needs api_name and api_version.
2026-09-26 12:17:01 -05:00
james.maes de94de58e4 feat(forms): complete adjusters and editable form sections (#720 #721 #722)
Handle on-load and field adjusters, metadata rules, inline choices, child editors, and editable widget sections. Verified by TypeScript, ESLint, and 1277 unit tests.
2026-09-26 12:08:17 -05:00
james.maes f4b15f4616 feat(forms): form context for editable widgets and widget header controls (#720, #721, #722)
Widgets receive a formContext when they edit inside a record create/edit form
or a process screen (live values, value and association writes, and
validators run before save), and share the header link button and switch
that Material's setup widgets use. Field metadata types carry inline
possible values and supplemental metadata.
2026-09-26 12:08:05 -05:00
james.maes 20a5befbdb fix(forms): accept a null optional BOOLEAN on record edit (#761)
An optional BOOLEAN stored as null, or cycled back to unset, failed client
validation with 'Expected boolean, received null'. The metadata schema now
accepts null for optional booleans and requires true or false, with a
readable message, for required ones, as Material does.
2026-09-26 12:07:57 -05:00
james.maes e9676e97af docs(query): record integrated Material parity and acceptance
Updates the Query parity ledger after the five-profile 475/475 real-server pass. Adjusts the saved-view assertion for the detailed legacy-column diff. Refs #716 #717.
2026-09-26 12:03:30 -05:00
james.maes 4c7918f785 feat(query): complete grid and saved-view parity (#716 #717) 2026-09-26 11:46:35 -05:00
james.maes 19353e6562 feat(query): complete Material filter workflows
Refs QRun-IO/qqq#715 and QRun-IO/qqq#718.
2026-09-26 10:19:20 -05:00
james.maes 510fc6e6dc test: accept additive v1 query settings in process checks (#727) 2026-09-26 09:13:15 -05:00
james.maes 74042c056e perf: allow measured Linux record-view bundle (#710) 2026-09-26 08:51:51 -05:00
james.maes ec04caf2f7 docs: add Next UI developer and agent guides 2026-09-26 08:34:38 -05:00
james.maes 3c4b24758c perf: budget combined ESB and Next UI 1.0 bundles (#710, #713)
The ESB process management merge increased measured static-export JS from 706.6 to 736.2 KB total and from 461.7 to 470.2 KB on /app/_. Set caps to 750 and 475 KB, leaving narrow headroom while retaining the route and largest-chunk gates.
2026-09-26 08:20:07 -05:00
james.maes 39bc6ae4f0 Merge remote-tracking branch 'origin/main' into feature/next-1.0
# Conflicts:
#	src/app/(dashboard)/app/[slug]/dev/page.test.tsx
#	src/app/(dashboard)/app/[slug]/dev/page.tsx
2026-09-26 08:15:29 -05:00
james.maes 89b9708bb2 fix(navigation): preserve export slashes for dotted process routes (#713) 2026-09-26 08:11:27 -05:00
james.maes 6d53c14bff feat(esb): process developer view, ESB overview, management actions (#13)
Closes #9. Part of QRun-IO/qqq#739.
2026-09-26 01:59:50 -05:00
james.maes 9b19cd184a Merge main (ESB developer section) into feature/next-1.0 2026-09-26 01:32:17 -05:00
james.maes f543bfe1f0 docs(acceptance): record the Next UI 1.0 batch 4 gate (#713)
Five projects (chromium, firefox, webkit, mobile, tablet), 2,197 tests,
347 required rows passing, phone coverage 347/347, 0 failures.
2026-09-26 01:30:28 -05:00
james.maes 037d0a86f8 Merge main (ESB developer section) into feature/next-1.0
Keeps the ESB section and the API docs on the table developer view, splits their tests, runs tablet in CI, and expects the sample's ESB 404.
2026-09-26 01:30:19 -05:00
james.maes cf3c3ad6e9 feat(esb): ESB section in table developer view (#11)
Closes #8. Part of QRun-IO/qqq#739.
2026-09-26 01:13:27 -05:00
james.maes e3bea6e6a7 test(acceptance): let the bulk load run start before QRY-034 goes back (#708)
On the iPad (WebKit) QRY-034 went back while the bulk load page was still
loading its table metadata and process init, and WebKit reports API calls
cut off by a navigation as access-control errors, which the diagnostics
fixture rightly counts for API routes. The test now waits for the process
screen, as a user would see it, before returning to the query.
2026-09-26 00:43:36 -05:00
james.maes e42ad2b2bc test(acceptance): run SEC-032 on phones and tablets (#708)
The unknown-authentication-type report is layout-neutral (the sign-in
error), so the row's test now runs in the touch projects too; the mobile
stream's @mobile copy covered the TABLE_BASED form this line supports.
2026-09-25 23:58:05 -05:00
james.maes 622e273dc1 test(acceptance): open the WID-065 phone record without expanding its sections (#708)
The phone coverage made openHost expand every accordion section so that
section widgets render on phones. WID-065's phone test checks the opposite:
only the open section mounts and requests its widget until the user opens
another. It now lands on the record as a user does.
2026-09-25 23:20:13 -05:00
james.maes 3e9960d3a4 Merge the processes, widgets, security and performance touch coverage into the integration (#708) 2026-09-25 23:12:34 -05:00
james.maes c421e37e18 test(acceptance): phone and tablet coverage for the v1, security and performance rows (#708)
Tags the tests for PRC-048, PRC-049, PRC-051, RPT-019, RPT-020, WID-064,
WID-066, WID-067, SEC-038, SEC-040, SEC-042, SEC-043, SEC-044 and
PRF-001 to PRF-005 @mobile, with touch-readiness checks on each screen:

- RPT-019 runs Render Report from the record actions (the phone action sheet);
  SEC-042 and the WID-064 schedule view open every record section (the phone
  accordion).
- WID-064 checks that the schedule editor popovers stay on screen, have no
  overlapping choices and have touch-sized targets.
- PRF-001 times the phone's default card list for the first page, then checks
  all 40 columns in its Table view; PRF-002 and PRF-003 page, sort and filter
  in the Table view on a phone and in the grid on the tablet. The heavy
  dashboard and the long process run unchanged, with no sideways scroll.
2026-09-25 23:02:28 -05:00
james.maes a7601087c7 fix(forms): keep schedule editor popovers on screen on phones and tablets (#708)
With a touch pointer each of the 60 minute choices is a 44 px target, so the
Minutes popover is taller than a phone or tablet screen. Radix placed it
partly off screen (above the top on the iPad, below the bottom on the phone),
where the choices could not be reached. The popover is now capped at the
height Radix measures beside its trigger and scrolls inside.
2026-09-25 23:02:26 -05:00
james.maes fe4297eeff Merge the records touch coverage and phone fixes into the integration (#708) 2026-09-25 23:01:45 -05:00
james.maes a16f4cd519 test(records): phone and tablet coverage for REC-049 to REC-057 (#708)
Tags the layout-neutral record tests @mobile and adds touch checks: label
breadcrumbs, v1 CRUD and possible values, dependent possible values, the
table and record developer views, record view shortcuts with the help opened
by tap, hash links (section anchors open the accordion item on a phone) and
create presets with the create-child dialog.
2026-09-25 23:00:55 -05:00
james.maes 2774be8c26 fix(records): scroll phone section anchors once the section opens, keep test log lines in their card (#708)
On a phone a #section link opens the section's accordion item, whose content
renders only afterwards, so the scroll now waits for the section to appear.
The associated-script Test Log Lines table scrolls inside the output card
instead of widening the page on a tablet with the sidebar.
2026-09-25 23:00:54 -05:00
james.maes cbfa7a9299 fix(records): fit the keyboard help and create-child dialogs on phones, return help focus on iPad (#708)
The keyboard help and the #/createChild= dialog spanned the full width of a
phone; they now keep the 1rem screen margin the other record dialogs use.
Closing the help returns focus to the header help button when the tap did
not focus it (Safari, iPad).
2026-09-25 23:00:52 -05:00
james.maes dbff3b49f7 Merge the navigation and query touch coverage into the integration (#708) 2026-09-25 22:59:35 -05:00
james.maes c7ab4c6699 fix(feedback): keep toasts below the header when a banner appears above it (#708)
The site banner renders once branding loads and pushes the header down
without resizing it, so the toast offset stayed at the header's first
position and toasts overlapped it by the banner's height. The offset now
follows every ancestor of the header, which the banner does resize.
2026-09-25 22:57:39 -05:00
james.maes b79446c65f fix(acceptance): build every acceptance run from a clean .next (#713)
A Turbopack production build that reused .next after the mobile merge
emitted CSS without the new touch-target rules of globals.css, so NAV-038
measured 36 px header buttons that the source makes 44 px. A clean build
has the rules. Acceptance runs now remove .next before building, so a
gate always tests the source as it is.
2026-09-25 22:57:12 -05:00
james.maes f1b58f50e5 test(query): phone and tablet coverage for the parity rows (#708)
Tag QRY-065 to QRY-071 @mobile. Go To and the n/r/f shortcuts use layout-neutral
record locators (cards on a phone), QRY-068 also checks that the phone cards
list the default columns in section order before switching to the grid, and the
Go To dialog is checked for touch readiness.
2026-09-25 22:56:32 -05:00
james.maes 144a340204 test(navigation): phone and tablet coverage for the record search and Material URL rows (#708)
Tag the NAV-031, NAV-032, NAV-034, NAV-035 and NAV-036 scenarios @mobile so the
mobile and tablet projects run them. NAV-032 opens the search dialog from the
phone header button where there is no header box, NAV-035 launches the record
process from the phone action sheet and selects the first card, and the quiet
app home and the phone search dialog are checked for touch readiness.
2026-09-25 22:56:31 -05:00
james.maes 1276df8a4b fix(feedback): start top-center toasts below the dashboard header (#708)
Toasts moved to the top center so they never cover Save, Next or Submit,
but there they could slide over the header's breadcrumbs, search, menu and
help controls. The header now keeps a --qqq-toast-top variable at its bottom
edge plus 8 px, following banners above it and window resizes. The toaster
offsets its desktop and phone positions by that variable and falls back to
sonner's defaults on pages without a header. REC-006 checks the created
toast sits below the header in every project.
2026-09-25 22:52:15 -05:00
james.maes 710ec015c5 Merge the mobile WebKit tooltip fix (ce670a1) into the integration (#708) 2026-09-25 22:49:51 -05:00
james.maes e54650291e test(acceptance): expect the locale-formatted last-page range in PRF-002 (#714)
The pagination range is locale formatted since the Material parity merge
(QRY-069), so the last 250 rows of 10,000 read 'Showing 9,751–10,000 of
10,000'. PRF-002 waited for the unformatted text until it timed out, which
looked like a budget failure. Its timing budgets are unchanged.
2026-09-25 22:37:04 -05:00
james.maes 6bb90cf76b test(acceptance): expect the locale-formatted last-page range in PRF-002 (#714)
The pagination range is locale formatted since the Material parity merge
(QRY-069), so the last 250 rows of 10,000 read 'Showing 9,751–10,000 of
10,000'. PRF-002 waited for the unformatted text until it timed out, which
looked like a budget failure. Its timing budgets are unchanged.
2026-09-25 22:30:33 -05:00
james.maes 0f4ae0a48d Merge branch 'feature/next-1.0-mobile' into feature/next-1.0
Phone and tablet coverage (#708) on the v1-only line:
- mobile's phone-layout test changes keep the integrated v1 routes; the
  mobile-added phone tests that waited on unversioned routes now wait on
  the v1 ones (query-phone bulk edit init, adornment download links)
- the TABLE_BASED "unsupported authentication" test (SEC-032) stays
  removed: TABLE_BASED sign-in is supported on this line (#700)
- saved-report tests keep the create-child dialog flow with mobile's
  openRecord/recordAction helpers and @mobile tags
- processRecords accepts an omitted records list only for a run with no
  records (mobile's rule) and keeps the v1 empty-list handling
- DataGrid tests keep both the row-rendering and the checkbox-label suites
- matrix rows renumbered clear of the integrated line: NAV-030->NAV-037,
  NAV-031->NAV-038, REC-049->REC-058, REC-050->REC-059 (matrix, test
  titles, browser-matrix.md)
- docs/acceptance/browser-matrix.md takes the phone and tablet scope
  draft, with the renumbered rows and the integrated TABLE_BASED, login
  branding and record search rows
2026-09-25 22:28:41 -05:00
james.maes ce670a1499 fix(widgets): hide a tapped widget tooltip on a tap elsewhere in WebKit (#708)
WebKit neither focuses the trigger on a tap nor blurs anything on a tap
on plain content, so with hover, focus and tap tracked separately the
tapped state never cleared. Close a tapped or focused tooltip on a
pointerdown outside it.
2026-09-25 22:26:06 -05:00
james.maes 4b202c4d8f test(acceptance): run the parity links and variant checks against the integrated UI (#714)
REC-056 waited for the unversioned process init, which the v1-only UI never
sends. REC-055 looked for the header search box under its pre-record-search
label. QRY-071 compared the variant id as a number, but the v1 variants
route (and so the stored variant) names ids as strings. Each check still
requires the same thing: the v1 init with the record, typing in the header
search box, and the South store variant on the records read, init and
every step.
2026-09-25 22:20:20 -05:00
james.maes 336ec87be0 Merge the batch 3 gate record into the parity integration 2026-09-25 22:14:00 -05:00
james.maes 8450428445 docs(acceptance): record the Next UI 1.0 batch 3 gate (#713) 2026-09-25 22:13:46 -05:00
james.maes 301c5f6a51 fix(feedback): show toasts at the top center, clear of primary actions (#708)
Toasts appeared at the bottom right, where every viewport keeps its
primary actions: form Save, process Next and Submit, the phone's sticky
action bar and action sheets. A failed save's error toast slid in over
Save, so a tap started on Save ended on the toast and never submitted;
a toast under a resting pointer paused its timer and blocked Next for
good. Move the toaster to the top center on all viewports.
2026-09-25 22:01:44 -05:00
james.maes 35eaaa59e8 fix(widgets): keep a widget tooltip open while its trigger is focused (#708)
The tooltip had one open flag that hover, focus and tap all wrote, so a
mouseleave closed it even while the trigger kept focus. After a tap the
pointer rests on the trigger; when a widget above finishes loading and
pushes the label down, the browser sends a synthetic mouseleave and the
tooltip closed under a still-focused trigger. Track hover, focus and tap
separately and show the tooltip while any of them holds.
2026-09-25 22:00:57 -05:00
james.maes a186853397 fix(processes): read the empty record page of a run with no input records (#708)
The backend omits the null records list when a process run has no input
records ({"totalRecords":0}). processRecords rejected that page as
malformed, so the review step's preview query failed and the global
handler raised a spurious "Something went wrong" toast over the step.
2026-09-25 22:00:21 -05:00
james.maes 6087831f26 test(acceptance): describe the parity rows against the v1 routes (#714) 2026-09-25 21:51:32 -05:00
james.maes 4ba3254468 Merge the batch 3 gate fixes into the parity integration
Brings e9fc79b and e5dba6c (PRC-003, WID-065 and the Firefox image-decode
classification) under the parity merge; processRecords keeps the stricter
object check and both zero-record unit tests.
2026-09-25 21:50:59 -05:00
james.maes 05a1076979 fix(processes): poll and cancel a variant-table run with its table variant (#714)
The v1 process routes now serve a run's state only under the backend
variant the run started with. The step and record reads already sent it,
but the async status polls and the cancel did not, so a long step or a
cancel on a variant table would have been refused.
2026-09-25 21:50:20 -05:00
james.maes e5dba6c896 test(acceptance): tell Firefox's interrupted image decode from a broken image (#713)
The batch 3 gate failed SEC-033 in Firefox on two console errors, 'Image
corrupt or truncated.' for samples-logo.png. The logo had been served
intact (200, then 304; the PNG's chunks and zlib stream check out). The
logout navigation cut off its decode, and Firefox reports that interrupted
decode as corruption. The diagnostics fixture now holds these reports until
the test ends, then has the same browser decode the image on the page's
origin. The report is recorded as an interrupted load only if that decode
succeeds. A really corrupt or truncated image, or one that cannot be checked,
still fails the test. Both outcomes were verified in Firefox.
2026-09-25 21:48:41 -05:00
james.maes e05f3ece06 build(perf): budget the lazily loaded RapiDoc chunk on its own (#714)
RapiDoc, which the table developer view loads on demand for the API docs,
is a 204.5 KB gzip chunk. Counting it in the application's total and
largest-chunk budgets would hide every other regression behind one raise.
perf-budget.json now lists it under lazyChunks: the script finds the chunk
by its custom-element definition, fails if any route loads it on first
load, holds it to its own 215 KB budget (about 5% above the measured
size), and leaves it out of the total (702.3 of 710 KB) and largest-chunk
(93.8 of 100 KB) budgets, which are unchanged.
2026-09-25 21:18:49 -05:00
james.maes 9593bbf2d8 Merge branch 'feature/next-1.0-parity' into feature/next-1.0
Material parity (#714) on the v1-only line (#699):
- possible-value searches send form values as the v1 JSON `values` map
- process init, steps and record reads send the table variant as a v1
  form field or query parameter
- loadMetaData reads the Material dashboard's all-screens processes from
  the v1 metadata (no unversioned /metaData request)
- the developer views use the v1 /table/{table}/{pk}/developer routes;
  only the API middleware catalog stays beside /qqq/v1
- lockfile regenerated with rapidoc; the removed auth SDKs stay removed
- the scheduled-report editor tests follow the create-child dialog
- parity matrix rows that collided with the integrated line were
  renumbered: PRC-048->PRC-050, PRC-049->PRC-051, QRY-065->QRY-070,
  QRY-066->QRY-071, REC-050->REC-056, REC-051->REC-057, WID-066->WID-067
  (matrix, test titles, material-parity.md)
- AGENTS.md, committed by accident on the parity branch, is left out
2026-09-25 21:16:19 -05:00
james.maes e9fc79b5cb test(acceptance): prove zero-record runs and single widget loads against the v1 routes (#713)
The batch 3 gate ran PRC-003 and WID-065 against assumptions from before
the v1 migration (#699): the v1 records route answers a zero-record run
with an empty list rather than omitting it, and widget data now loads from
/qqq/v1/widget/, so the request counter saw no requests at all. PRC-003 now
proves a zero total with no records in either serialization, and WID-065
counts the v1 widget route, so its once-only and not-yet-requested checks
are no longer vacuous. A unit test covers the v1 zero-record shape.
2026-09-25 20:57:41 -05:00
james.maes f46ae2aa09 test(e2e): mock the v1 record, process and saved-view routes the dashboard now calls (#699)
The mocked e2e suite (#709) still served the legacy /data record routes, the registered-route process metadata and one-field-per-value process forms. The dashboard reads and writes records, loads process metadata and runs processes only through /qqq/v1, so the mocks answer those routes with the v1 shapes ({ record }, the process itself, the JSON values field, process UUIDs in step and status paths).
2026-09-25 20:45:37 -05:00
james.maes 3b84e3782d Merge branch 'feature/next-1.0-v1' into feature/next-1.0
The acceptance fixture combines the security stream's Content-Security-Policy violation check with the v1 stream's guard against unversioned API routes. The v1 stream's rows REC-049, RPT-019 and WID-064 collided with rows batch 1 added under the same ids; they are renumbered REC-051, RPT-020 and WID-066 in the matrix and the test titles. CronUIWidget keeps the batch 1 view-only widget, processRecords takes the v1 route (which also reads an omitted empty list as an empty page), and PRC-003 checks the v1 records response.
2026-09-25 20:37:34 -05:00
james.maes 33227986ea docs(acceptance): regenerate the feature matrix from the full four-project gate (#714)
309 rows, 306 required rows passing in chromium, firefox, webkit and
mobile (1145 tests, retries 0); PRC-039, WID-033 and SEC-030 stay excluded.
2026-09-25 20:36:18 -05:00
james.maes 7d34d0b6c9 docs(acceptance): record the Next UI 1.0 batch 2 gate (#713)
Quality (#698, #709, #710) and security (#695, #696) integrated on feature/next-1.0 against the combined sample backend (develop with #737, the security headers and cookie hardening, and the TABLE_BASED hardening). Four-browser gate, first attempts only: 313 required rows passing, 0 failed, 0 missing, 3 excluded; chromium, firefox and webkit 362 passed each, phone 22 passed, no flaky tests.
2026-09-25 20:36:14 -05:00
james.maes df02feff26 fix(records): #/launchProcess= names no table for a hidden table process (#714)
A process missing from the record screen's list is one of the table's
hidden processes (processes added to every screen are always listed), so
the hash launch no longer adds tableName for it (REC-050). REC-051 counts
only the insert POST now that possible-value searches also POST.
2026-09-25 20:06:34 -05:00
james.maes df0712ee05 test(acceptance): the tag-records metadata customizer tolerates a session without a user (#714)
QInstance builds its table paths through MetaDataAction in a temporary
context whose session has no user; TagRecordsAudience threw a
NullPointerException there, so every child record list widget failed to
render (WID-024/026/027/029-032/060, RPT-009..018, SEC-011).
2026-09-25 20:06:33 -05:00
james.maes 7af15dea7f test(acceptance): hover the widget label again if the dashboard was still settling (#666)
In Firefox a layout shift while other widgets finish loading can move the label from under the pointer right after the hover; WID-043 still requires the tooltip to open on hover and close when the pointer leaves.
2026-09-25 20:05:25 -05:00
james.maes 289bc4126a test(acceptance): age the TABLE_BASED last-request record when expiring sessions (#696)
The TABLE_BASED hardening keeps the last request of each session in memory and counts it as activity, so SEC-036 must move that record back as well as the table access time to make a session idle past the timeout.
2026-09-25 20:05:24 -05:00
james.maes a4e51003b7 fix(processes): read a run with no records as an empty preview (#659)
The backend serializer omits empty lists, so the records route answers a run without records with {"totalRecords":0}. processRecords rejected that as malformed and the review step raised a 'Something went wrong' notification, which on WebKit covered the Next button. PRC-003 now asserts the response shape and that the run shows no error notification.
2026-09-25 20:05:23 -05:00
james.maes 555a90444f merge: navigation, query and records phone and tablet coverage (#708) 2026-09-25 19:55:02 -05:00
james.maes 3d3bb6acf5 test(navigation): phone and tablet coverage for every navigation row (#708)
Every navigation test is layout-neutral and now runs on the phone and
tablet projects too (@mobile). New rows: NAV-030, the drawer used from the
keyboard (focus moves in, Tab stays inside, Escape, the close button and a
chosen link return focus to the menu button) at a pinned 412 px touch
viewport; NAV-031, the shell's skip link, header, navigation and user menu
as touch targets. NAV-026 now runs at 412 px with touch and checks the
backdrop tap. Breadcrumbs, sidebar or drawer entries, app homes, the
landing page, search results and not-found links assert 44 px targets and
no sideways page scroll; a table outside the app tree is named by label.
The banner check reads the page banners before opening the modal drawer,
and appNavigation never re-clicks the menu behind an open drawer.
2026-09-25 19:36:36 -05:00
james.maes 01e35477ce fix(navigation): name tables, processes and reports outside the app tree by their label (#708)
A table that opens by direct link but is not in the app tree (for example
qryItem) showed its name in the breadcrumb, which is the phone header's
only title. Its label from the instance metadata now names it there and
in the document title; app-tree labels still win, and such objects stay
out of navigation.
2026-09-25 19:31:52 -05:00
james.maes 979bb06049 fix(navigation): 44 px touch targets for breadcrumbs, sidebar entries, the skip link and not-found links (#708)
Under a coarse pointer, breadcrumb links (20 px), the tablet sidebar entries
and the focused skip link (36 px) and the not-found links (36 px) now take a
44 px target. The trail no longer wraps in the fixed-height header on a
touch screen: it scrolls sideways on its own, starting at the current page,
and the header controls keep their place. Mouse layouts are unchanged.
2026-09-25 19:31:51 -05:00
james.maes 3498f7046f fix(navigation): the phone navigation drawer is a modal dialog that keeps keyboard focus (#708)
Opening the drawer from the keyboard left focus on the menu button, so Tab
walked the page behind it, and Firefox could not reach the drawer at all.
The drawer is now a Radix dialog: focus moves in on open, Tab stays inside,
and Escape, the close button, a backdrop tap or a chosen link close it and
return focus to the menu button. An open user menu takes Escape first. The
menu button reports aria-expanded; the hooks and names are unchanged.
2026-09-25 19:31:43 -05:00
james.maes 1127e2f659 docs(acceptance): mark the integrated parity rows done in the Material parity inventory (#714)
Go To (QRY-067), processes added to every screen (PRC-048), dependent
possible values (REC-052, PRC-049), table API docs (REC-053; the sample has
no qqq-middleware-api, #738) and record associated scripts (REC-054) are
Done. The table-scoped report URL is Partial: the backend report metadata
has no tableName (#732). The in-progress note is gone; row IDs are checked
against the matrix files.
2026-09-25 19:24:56 -05:00
james.maes b7f90cb27a fix(processes): accept a zero-record records response without a records list (#714)
The backend omits empty lists, so a run with no records answers
{"totalRecords":0}. processRecords rejected that and the validation review
showed a "Something went wrong" toast (PRC-003 flake). A missing records
list is now empty; a non-object body, a non-number total or a non-array
records value is still rejected.
2026-09-25 19:21:03 -05:00
james.maes 2a29872394 test(acceptance): renumber parity rows clear of the integrated 1.0 line (#714)
NAV-030/031/032 become NAV-034/035/036 (the 1.0 line uses NAV-030..033
for search and login branding), REC-049 becomes REC-055 and WID-064
becomes WID-066 (the 1.0 line uses REC-049 and WID-064/065).
2026-09-25 19:20:23 -05:00
james.maes 33f7884035 feat(records): developer views with API docs and associated scripts (#714)
- table developer view: API Docs & Playground with API and version selects and
  an embedded RapiDoc reference; "This table is not available in any APIs."
  when the instance has no application APIs (no /apis.json)
- record developer view: per associated script, versions with CURRENT, code of
  the selected version, Edit / Edit and Activate (storeScriptRevision), Test
  (testScript), run logs and script type docs; Create Script for an empty
  field; single-file scripts show their one file as Script.js whatever name it
  was stored under
- actions are offered only when the session has the processes or table edit
  permission
- acceptance: specs/records/developer.spec.ts (REC-053, REC-054)
2026-09-25 19:18:46 -05:00
james.maes 4f833aac39 test(acceptance): script tables on RDBMS and developer view fixtures (#714)
- the standard scripts tables live in the owned H2 database (snake_case,
  identities from 1000), so backend.sql reads what the script processes store;
  WID-032 rows are unchanged
- Script Lab (Records Lab) has an associated greeting script with a Java
  tester, two revisions, run logs and a record without a script
- personas get storeScriptRevision/testScript like other processes (viewer and
  noProcesses do not)
- matrix rows REC-053 (table API docs) and REC-054 (associated scripts)
2026-09-25 19:18:46 -05:00
james.maes a2b3d93856 feat(forms): send form values with possible-value searches (#714)
Material posts the current form values (a form-encoded values JSON) with
every possible-value search, so a field whose possibleValueSourceFilter
uses ${input.otherField} offers only the choices matching the form. Next
sent only the search term, so dependent fields could not narrow.

- possible-values API: new formValues request field, posted as the
  form-encoded values body the legacy routes read (GET otherwise)
- PossibleValueSelect: watches the form, sends its values with searches
  and id lookups, reloads an open list when another field changes
- usePossibleValues: formValues in the request and the query key
- process view fields send the screen values with label lookups
- acceptance: REC-052 (Lab Pick item filtered by category) and PRC-049
  (Specimen Pick filtered by category) with SQL and backend checks;
  REC-045 now selects the record insert among POSTs
2026-09-25 19:18:03 -05:00
james.maes 0caa294929 feat(processes): Material processes added to every query and record screen (#714)
- loadMetaData merges supplementalInstanceMetaData from the legacy /metaData
  route (V1 omits it); fetched on its own only when needed, and its failure
  alone is not fatal
- getProcessesForTable appends the processes named by
  materialDashboard.processNamesToAddToAllQueryAndViewScreens (or, without
  Material instance metadata, runRecordScript), present in metadata and
  permitted, deduplicated, after the table's own processes
- The query Actions menu lists them after a divider in configured order; the
  record actions menu and phone action sheet list them even when hidden from
  navigation, as Material does
- Launches name the launching table (tableName) for processes that are not
  the table's own, and the process page passes it to a table-less process
- Acceptance: PRC-048 with the table-less prcTagRecords fixture process, listed
  only for the sample user casey so other areas keep their exact menus
2026-09-25 19:17:18 -05:00
james.maes 993d79d88c feat(query): Material Go To record dialog (#714)
- Tables whose supplemental Material metadata sets gotoFieldNames get a
  "Go To..." button on the query toolbar and the record view header
- The dialog offers the primary key, then each configured unique key, with
  inputs labelled by field label; Enter submits an option; one match opens
  /app/{table}/{id}, none or several show Material's messages
- A table with GET but no QUERY capability opens Go To automatically, and it
  cannot be dismissed (Material's RecordQuery behavior)
- QTableMetaData gains supplementalMetaData (the v1 key); the legacy
  supplementalTableMetaData key is still read
- Acceptance: QRY-067 with qryBin (code; aisle + shelf) and qryLocker (GET only)
2026-09-25 19:16:59 -05:00
james.maes 9691312b13 test(acceptance): cover Material shortcuts, hash links and table-scoped URLs (#714)
- NAV-030/031: /app/{table}/{process} and /app/{table}/{id}/{process}
  open the run (history replaced, not-found for unknown segments); runs
  launched from a record or a filtered query return there
- REC-049/050/051: record view n/e/c/d/a shortcuts and help text,
  #audit, #/launchProcess=, section anchors, #/createChild= dialog and
  create-page #/defaultValues=/disabledFields= presets, proven by SQL
- QRY-065: query screen n/r/f shortcuts; r re-queries the backend
- RPT-012: Add new opens the create-child dialog over the saved report
  with Saved Report preset and locked
- QRY-034: query launches carry returnTo
2026-09-25 19:16:25 -05:00
james.maes 167e2a3a35 fix(parity): create-child dialog renders widget form sections (#714)
The #/createChild= dialog built its EntityForm without the instance
widgets, so widget-driven sections (the scheduled report cron schedule)
were missing and a schedule could not be created from the saved
report's Add new link. Pass the widgets as the create page does.
2026-09-25 19:14:27 -05:00
james.maes 8fa7d6a046 Merge branch 'feature/next-1.0-security' into feature/next-1.0
The security stream's acceptance rows SEC-034 to SEC-040 collided with the TABLE_BASED rows batch 1 added under the same ids; they are renumbered SEC-038 to SEC-044 in the matrix, the test titles, the acceptance README and the security review. The lockfile keeps the quality stream's upgrades and drops the auth SDK and cookie packages the security stream removed.
2026-09-25 19:10:55 -05:00
james.maes fcf240d800 Merge branch 'feature/next-1.0-quality' into feature/next-1.0 2026-09-25 19:08:00 -05:00
james.maes f108367db6 fix(mobile): 44 px native selects in WebKit (#708)
Safari ignores min-height on a select that has an author border but honors
height, so the coarse-pointer select rule also sets height: 44px. The rule is
wrapped in :where(), so a class height still wins and min-height still lifts
it to 44 px; mouse layouts are unchanged.
2026-09-25 19:04:37 -05:00
james.maes 455c937845 merge: processes, widgets and security phone and tablet coverage (#708) 2026-09-25 19:01:28 -05:00
james.maes b5aac3c9ff test(mobile): security and interaction rows run on phone and tablet (#708)
The list helper matches a card by an element with the exact value (card
text runs together), the navigation helper reuses an open drawer, the stale
state test waits for the record page before reading its heading, dialogs
and sign-in pages assert touch readiness, and a phone keyboard test opens a
card, the record actions sheet and the edit form and saves. The desktop
grid keyboard and grid loading variants stay desktop tests.
2026-09-25 19:00:31 -05:00
james.maes 792f635941 test(mobile): widgets and reports rows run on phone and tablet (#708)
Dashboards assert one full-width widget per row below the large breakpoint
and twelfths above it, wide tables scroll inside their card, and controls,
report run pages, saved report views and the share dialog are touch ready.
Record widgets expand the phone section accordion, record actions use the
phone actions sheet, reports use the navigation drawer, and new phone tests
cover tapping the widget label and help tooltips and the touch-sized
dropdown, date, reload and export controls.
2026-09-25 19:00:30 -05:00
james.maes f1bcddb8f2 test(mobile): every process row runs on phone and tablet (#708)
All process specs run in the mobile and tablet projects. Each screen type
(forms, validation review and summary, record lists, bulk edit, delete and
load mapping, progress, errors, completion, scanner, widgets, the cancel
dialog) asserts no sideways page scroll and 44 px touch targets on touch
screens, and the table-launched run uses the layout-neutral list helper.
2026-09-25 19:00:26 -05:00
james.maes 3cafb4d09a test(records): phone and tablet coverage for every records row (#708)
Every REC test runs on the phone and the tablet (@mobile). Record values are
read from the visible layout only (the phone accordion and the desktop tabs
are both in the page); phone tests open the accordion section a value lives
in, record actions go through the phone action sheet, and lists are read as
grid or cards. The REC-004 tab test stays desktop-only beside a new
layout-neutral section test.

Pinned phone/tablet tests: REC-049 (new row) record header and action sheet;
REC-050 (new row) forms with every editor type; tap equivalents for the
TOOLTIP adornment and field help (REC-036, REC-039); audit history, delete
and unsaved-changes dialogs that fit a phone and move focus in and back
(REC-042, REC-012, REC-008). REC-046 also asserts that a read-only user gets
no phone Actions trigger.
2026-09-25 18:50:28 -05:00
james.maes 2d051dc2ea fix(forms): unsaved-changes dialog takes focus and returns it; dialogs fit a phone (#708)
The unsaved-changes dialog left focus on the page: Radix AlertDialog focuses
its Cancel part on open and the dialog had none, so Stay now is that part.
Staying returns focus to the form's Cancel button, also in Safari, which
does not focus a clicked button. The unsaved-changes and delete dialogs keep
a 16 px margin on a phone and scroll inside on short screens.
2026-09-25 18:50:21 -05:00
james.maes f1589c3421 fix(records): record header, sections and action sheet fit phones and tablets (#708)
- The header wraps its controls (view toggle, Audit, actions) onto their own
  row when they do not fit beside the title; the title was squeezed to a few
  letters under the toggle on a phone and on the iPad with the sidebar, and
  the actions ran past the content edge.
- Long values (URLs, tokens) wrap inside their card; the list view stacks
  label over value on phones. The tab bar scrolls inside itself when many
  sections do not fit.
- The phone action sheet caps its height and scrolls, keeps Tab inside, and
  is not offered to a user without any record action.
- Closing the audit history returns focus to the Audit button, also in
  Safari, which does not focus a clicked button.
2026-09-25 18:50:15 -05:00
james.maes 375901fc34 fix(records): 44 px touch targets for value links, the back link and current-file links (#708)
Record, URL, email and file links on the record view, the Back to <table>
link and the current-file link of upload fields were text-sized (17-20 px
tall). Under a coarse pointer they now take a 44 px tall target; mouse
layouts are unchanged.
2026-09-25 18:50:07 -05:00
james.maes e8675bd7e3 fix(records): tooltips and field help open by tap on touch screens (#708)
Touch screens have no hover and Radix closes a tooltip on every click of
its trigger, so the TOOLTIP adornment, field-label help and form help
icons could not be read on a phone or tablet (a tap on the help icon in
Safari neither focuses nor opens it). A touch or pen tap now toggles the
tooltip; mouse clicks, hover and keyboard focus behave as before.
2026-09-25 18:50:01 -05:00
james.maes 9fedda9ac8 test(acceptance): fail any page request to an unversioned API route (#699)
The diagnostics fixture records page requests to the legacy QQQ routes
(/data, /processes, /widget, /possibleValues, /download, /reports,
/metaData, /manageSession, /logout outside /qqq/v1) on the QQQ servers
and fails the test on any; allow() cannot waive it. This keeps the UI
on the v1 API across every acceptance test.
2026-09-25 18:43:56 -05:00
james.maes 9ca9aa8e3b feat(api): list variants and manage OAuth2 sessions through the v1 API (#699)
Variant options come from GET /qqq/v1/table/{table}/variants ({variants},
string ids), and the OAuth2 code exchange and session resume use the v1
POST /manageSession (QRun-IO/qqq#406). Next makes no request to an
unversioned API route any more.

Acceptance: QRY-066 (the picker lists the stores from the v1 route;
v1 lists every option, none for a table without variants, 403 for an
unknown table); QRY-060 expects the v1 string variant id.
2026-09-25 18:43:48 -05:00
james.maes abe9bd7664 feat(api): run processes and read process metadata through the v1 API (#699)
Process init, step (including isStepBack), status, records and cancel
(now POST) use /qqq/v1/processes/..., and process metadata comes from
/qqq/v1/metaData/process/{name}. Init/step send the process values in
the v1 'values' JSON field (the same text the legacy form fields
carried), the record selection as recordsParam/recordIds/filterJSON,
uploads as file fields (QRun-IO/qqq#543) and stepTimeoutMillis. v1
widget-block process values are read in the block-data shape. Saved
views, sharing, saved bulk load profiles, column stats, reports and
audits run through the same functions. Backend parity (uploads, back
step, min/max input records, block type names, ownership of runs) is
in QRun-IO/qqq#406.

Acceptance: PRC-049 (a bulk load uploads, maps, steps back and inserts
on the v1 routes with SQL proof; unsafe upload 400, unknown run
refused); process and query specs now read the v1 request shapes.
2026-09-25 18:43:41 -05:00
james.maes 262f5a5bc8 feat(query): Material default column order by sections and locale-formatted pagination range (#714)
- default grid columns list each section's fields in section order (Material
  setupGridColumns bySection) instead of the metadata field map, QRY-068
- the shown range is locale formatted like the total (1,001-1,234 of 1,234), QRY-069
- docs/acceptance/material-parity.md: the Material parity inventory
2026-09-25 18:41:47 -05:00
james.maes 010bb4e020 docs(acceptance): record the Next UI 1.0 batch 1 gate (#713)
Search (#701), polish (#702, #706, #707) and auth (#700, #703)
integrated on feature/next-1.0 against the combined sample backend.
Four-browser gate, first attempts only: 301 required rows passing,
0 failed, 0 missing, 3 excluded; chromium, firefox and webkit 346
passed each, phone 20 passed, no flaky tests.
2026-09-25 18:41:39 -05:00
james.maes 423cb3e48e docs(security): record the Next UI security review against ASVS L2 (#696)
docs/security/next-ui-review.md: scope, method, fourteen findings with ASVS
references, severities and dispositions (ten fixed with regression tests,
two follow-ups QRun-IO/qqq#733 and #734, two accepted), what was verified
without findings, and the evidence.
2026-09-25 18:30:14 -05:00
james.maes 465fa77855 fix(mobile): tap tooltips and touch-sized widget, report and share controls (#708)
Widget label and help tooltips open on a tap, stay inside the viewport and
have a 44 px trigger on touch screens. Widget, block and stepper links,
the report download link and the widget, report format and share dialog
selects are 44 px targets under a coarse pointer, and dashboard grid items
may shrink so wide content scrolls inside its card.
2026-09-25 18:15:23 -05:00
james.maes 4b54e026ac fix(mobile): touch-sized process switches, selects and links (#708)
On a touch screen the bulk edit switches get a 44 px label hit area,
bulk load, profile and record-list selects are 44 px tall (WebKit ignores
min-height on bordered selects), and the download, summary record and
result links are 44 px targets. Mouse layouts are unchanged.
2026-09-25 18:15:16 -05:00
james.maes ddec18d8ad test(acceptance): performance area with large-table, dashboard and long-process budgets (#710)
- fixture/PerformanceFixtures.java (define + prime, wired into
  AcceptanceSampleServer):
  - prfWide: a read-only table of 10,000 rows and 40 columns of mixed
    types, generated by one INSERT ... SELECT FROM SYSTEM_RANGE. Every
    value is a function of id and column. prime() keeps an intact copy
    (row count, a checksum and the last name) instead of re-inserting
    400,000 values before every test of every area. It rebuilds
    anything else.
  - prfDashboard: 24 widgets (6 statistics, 6 bar, 4 line, 4 pie, 4
    tables of 100 rows) with deterministic data.
  - prfLongRun: an async job that walks all rows in 40 pages (about
    10 s), reporting progress. It logs rows, total and start/end epoch
    millis to prf_run_log.
- matrix/performance.json: PRF-001..005 (required). specs/performance
  measure wall time from the action to the exact expected content,
  record it as a timing annotation, and check it against
  specs/performance/budgets.ts. Each spec also checks structure: one
  page in the DOM (25 or 250 rows x 40 cells), skip/limit 250 on the
  query, each widget fetched exactly once, status polls never overlap
  and are spaced at least 1.5 s apart after each answer.
- Budgets are about 8x the idle times. A full four-project run on a
  busy machine measured up to 4x, and the first, tighter budgets failed
  that way twice in Firefox (next page 4,191 ms, dashboard 8,841 ms).
- docs/acceptance/performance.md: bundle and runtime budgets,
  measurements, fixes and open observations. README lists the new
  commands.

Full run (chromium, firefox, webkit, mobile) with the fixture: 1004
passed, 2 failed (those two Firefox budget breaches, before the budgets
were raised). Performance area rerun afterwards: 15/15 in chromium,
firefox and webkit.
2026-09-25 18:09:51 -05:00
james.maes 4e100a34f3 test(query): wait for the saved view route before leaving it (#708)
Firefox aborted the next page.goto while the client navigation to the new saved
view was still settling; the test now waits for the view's label first.
2026-09-25 18:08:35 -05:00
james.maes 16fa3cf0c6 feat(api): load widget metadata and widget data through the v1 API (#699)
Widget data is POST /qqq/v1/widget/{name} with the inputs as query
parameters, and the instance metadata no longer falls back to the
legacy /metaData for widget permissions and reports: v1 /metaData now
carries full widget metadata and the reports map (QRun-IO/qqq#406).

Acceptance: WID-064 (v1 metadata lists permitted widgets and reports,
the dashboard renders from POST /qqq/v1/widget, a denied widget is
absent, never requested and refused 403 without rendering); WID-054 now
checks the v1 widget path. Full chromium gate 336/336.
2026-09-25 18:04:48 -05:00
james.maes 58a4b8cbe3 Merge branch 'feature/next-1.0-auth' into feature/next-1.0
Conflicts: the search stream and the auth stream each added a NAV-030
row. Search keeps NAV-030 to NAV-032 (record search); the auth login
branding row becomes NAV-033 in matrix/navigation.json, its test title
and the phone rows of docs/acceptance/browser-matrix.md.
2026-09-25 17:57:36 -05:00
james.maes 50c6c97565 test(query): phone and tablet coverage for every query and relationship row (#708)
Every QRY/REL test runs on the phone and the tablet (@mobile). Column values are
read from grid cells or card values; tests that need grid-only columns, sort
headers or row checkboxes switch a phone to the Table view, and the phone filter
sheet is closed before the list is used. New rows with pinned 412 px touch
tests: QRY-008 card list, QRY-009 filter sheet, QRY-024 column configuration by
touch (phone and tablet), QRY-036 bulk actions from a card selection, QRY-055
saved views and export menus.
2026-09-25 17:56:16 -05:00
james.maes 09b2ebad71 fix(query): card values use the grid's cell formatting (#708)
Cards showed raw values (ISO date-times, unformatted numbers); they now render
each value with DataCell, like the grid, including possible-value links.
2026-09-25 17:56:08 -05:00
james.maes 5b0480dac2 fix(records): open deep-linked accordion sections only in the phone layout (#708)
The desktop tabs and the phone accordion are both in the DOM; opening the
accordion at every width duplicated the tab content. The section now opens
only when the phone media query matches.
2026-09-25 17:56:07 -05:00
james.maes 90615a34f9 Merge branch 'feature/next-1.0-polish' into feature/next-1.0 2026-09-25 17:55:40 -05:00
james.maes 4627ca7198 Merge branch 'feature/next-1.0-search' into feature/next-1.0 2026-09-25 17:55:36 -05:00
james.maes ca0e2cb7e7 feat(api): export and download reports through the v1 API (#699)
Table export posts {format, filename, fieldNames, filter, tableVariant}
to /qqq/v1/table/{table}/export; process and report files download from
/qqq/v1/download/{file}; reports without a process stream from
/qqq/v1/reports/{name}?format=... (the legacy /data/.../export,
/download and /reports routes are no longer used). The v1 routes enforce
the export capability, the process download registry and report
permission (QRun-IO/qqq#406).

Acceptance: QRY-065 (export request, rows, 403 without the capability),
RPT-019 (v1 download and report links with SQL-checked rows; 403 for
unregistered files and a restricted report, 404 unknown, 400 without a
format); RPT-011 now requires the v1 report link. Full chromium gate
333/334 with the one RPT-019 test fixed and rerun green (2/2).
2026-09-25 17:55:01 -05:00
james.maes aeba4f7060 fix(mobile): touch-sized native selects in WebKit (#708)
WebKit ignores min-height on a native menulist select, so on the iPad selects
stayed about 23 px tall. Under a coarse pointer a select now draws its own
arrow (appearance: none) and takes the 44 px box. Mouse layouts are unchanged.
2026-09-25 17:52:39 -05:00
james.maes 23c3073831 test(acceptance): prove the dashboard security headers and review fixes (#695, #696)
The diagnostics fixture now fails a test on any Content-Security-Policy
violation: every frame forwards securitypolicyviolation events (cspViolations
in diagnostics.json). The acceptance application allows its widgets loopback
service through the new withNextDashboardSecurityHeadersCustomizer hook.

New rows: SEC-034 (strict policy on every document, hashes equal the served
inline scripts, framing refused, injected script and handler refused and
reported), SEC-035 (OAUTH2 and AUTH_0 provider origins, other origins
refused), SEC-036 (override hook and metadata-derived origins), SEC-037
(login error codes), SEC-038 (hostile stored HTML), SEC-039 (recent records
bound to the identity), SEC-040 (SameSite/Secure session cookies). SEC-024
gains the normalized-path open-redirect cases.
2026-09-25 17:50:50 -05:00
james.maes a178f66272 chore(security): remove unused auth SDK and cookie dependencies (#696)
@auth0/auth0-react, oidc-client-ts, universal-cookie and ts-md5 were declared
but never imported; the sign-in flows live in src/lib/auth/oidc.ts. They looked
like a second auth stack to reviewers and scanners. A unit test keeps them out.
2026-09-25 17:50:40 -05:00
james.maes d19d522b6d fix(security): bind recently viewed records to the signed-in identity (#696)
Logout cleared per-user browser data, but a session that expired or was
revoked left the recently viewed records and stored identity in localStorage,
so the next person to sign in on the browser saw them. claimClientData() now
clears that data when a different identity signs in (session creation, resume
and the OAuth callback).
2026-09-25 17:50:32 -05:00
james.maes 0d272edd4f fix(security): sanitize all rendered HTML with one hardened profile (#696)
Every HTML sink used DOMPurify's default profile, which keeps style elements,
forms, form controls and position:fixed. Stored HTML could therefore cover the
page with a fake sign-in form or restyle the application. sanitizeHtml()
(src/lib/utils/sanitize-html.ts) keeps the defaults and also removes style,
form, input, button, textarea, select, option, optgroup, datalist and dialog,
and inline positioning other than static or relative. FieldValue, DataCell,
HelpContent, SafeHtml, BlockWidget, HtmlComponent, ProcessStepScreen, Banner
and RichTextField use it.
2026-09-25 17:50:24 -05:00
james.maes 16fe7ed518 fix(security): show only known sign-in error codes on the login page (#696)
/login?error=<text> displayed any text from the link inside the sign-in card
(content spoofing). Known provider and callback codes get their message,
other well-formed OAuth codes are shown as a code, and anything else shows a
generic "Sign-in failed."
2026-09-25 17:50:14 -05:00
james.maes 835acb5c79 fix(security): reject returnTo paths that normalize to another origin (#696)
safeReturnTo checked for // and /\ before parsing, then returned the parsed
pathname. Dot segments (/.//host, /app/..//host, /app/../\host) normalize to
//host, which a navigation treats as another origin. The normalized path is
now checked as well.
2026-09-25 17:50:05 -05:00
james.maes 3b2536b91b fix(reports): drop stray No fields; label pages outside the app tree (#707)
An empty dynamic form without a message renders nothing; tables, processes and reports outside the app tree title pages with their labels. Rows RPT-019 and REC-049.
2026-09-25 17:49:52 -05:00
james.maes be49ac3299 fix(records): mount only the active record view layout (#706)
Section widgets mounted in both the tab and accordion layouts; acceptance row WID-065 proves one mount and one request per widget.
2026-09-25 17:49:42 -05:00
james.maes e5e5b8ea72 feat(widgets): cron schedule editor with live description (#702)
Record forms edit cronUI sections in Basic or Advanced mode with a live description from an in-repo port of the backend CronDescriber; acceptance row WID-064.
2026-09-25 17:49:35 -05:00
james.maes ed98aec576 feat(api): read and write records through the v1 API (#699)
Record get, insert, update and delete use /qqq/v1/table/{table}[/{pk}]
(GET, POST multipart, PATCH multipart, DELETE) instead of the legacy
/data routes, reading the v1 envelopes ({record}, {deletedRecordCount,
errors}). Field downloads the backend writes as /data/{t}/{pk}/{f}/{file}
are served from the v1 record field download route (apiUrl helper).
Backend parity (associations, record-v1, permissions, null values,
empty association lists) is in QRun-IO/qqq#406.

Acceptance: REC-050 (create/view/edit/delete on v1 with SQL proof,
record-v1 associations, required-field 400, viewer 403 on writes); the
record specs now assert the v1 routes. Full chromium gate 332/332
(the two rows added for the next group were not yet collected).
2026-09-25 17:47:06 -05:00
james.maes a1c68fdbc7 fix(records): a deep-linked tab opens its accordion section on a phone (#708) 2026-09-25 17:41:49 -05:00
james.maes e48ad3173d fix(query): Escape closes the selection, saved views and export menus and returns focus (#708) 2026-09-25 17:41:47 -05:00
james.maes f3b036b375 fix(query): cards list the grid's columns in the grid's order (#708)
Cards took the first fields of the raw metadata map, so a phone showed other
values than the grid. They now use the grid's query columns, order and
visibility.
2026-09-25 17:41:46 -05:00
james.maes 92851bf8b0 feat(search): search records across tables from the global search (#701)
The header box, the '/' dialog and the search page now list the records
the backend record search (POST /qqq/v1/search) finds, after the matching
pages: record label with the table label, opened by keyboard or click.

- searchableTables() reads the capability from metadata: readable, visible
  tables that list searchFields. With none (older backends, or no
  declarations) no search request is made and the search stays the local
  jump-to from #653, with the same labels.
- useRecordSearch debounces (250 ms), needs two characters, and uses
  TanStack Query; searchRecords() validates the v1 response and replaces
  the unused globalSearch() that swallowed 404s.
- Recent records that were found are not listed twice; searching and
  failures are announced in a polite status line outside the listbox.
- Acceptance: NAV-030 (results, keyboard, search page, @mobile), NAV-031
  (noPets sees no pets, backend refuses them; saved view found by its
  owner and shared reader, not by casey), NAV-032 (security variant
  without search fields makes no /search call). NAV-025 keeps its local
  scenarios; its negative now asserts no search request fails.
2026-09-25 17:38:43 -05:00
james.maes db0c6d86aa test(acceptance): wait for the saved view's URL sync in QRY-051 (#709)
After "Save View As" the app navigates to /savedView/<id> and then
router.replace()s the URL with pageSize and filter. The test matched
the first URL and immediately page.goto()'d away, which in Firefox
could abort the second navigation's RSC fetch ("Failed to fetch RSC
payload", NS_BINDING_ABORTED) and fail the diagnostics check (seen once
in a chromium/firefox/webkit run of the query area). The assertion now
waits for the synced URL. It is stricter than before, not looser.
2026-09-25 17:34:12 -05:00
james.maes e57201ef55 perf(query): skip re-rendering unchanged grid rows on large pages (#710)
A 250-row page of the 40-column performance table is 10,000 cells, and
the grid re-rendered all of them on every query state change: once
for the page number before its request was even sent, again for the
URL sync and the fetching flags.

- DataGrid: body rows are a memoized GridRow. Rows re-render when their
  data, the column definitions, or their selection changes, and are
  skipped otherwise. RecordQuery passes a stable column-stats handler so
  tables with column statistics keep stable column definitions.
- DataCell: date and date-time cells built an Intl.DateTimeFormat per
  cell (toLocaleDateString and the time-zone name); each formatter is
  now built once. Output is unchanged.

Page turn at 250 rows x 40 columns, click to rendered rows (chromium,
M3 Max): 659-669 ms -> 518-561 ms; the time before the request is sent
dropped from about 200 ms to 110-150 ms. DataGrid.test.tsx checks that
unchanged rows skip rendering and that selection, record and column
changes still re-render.
2026-09-25 17:32:17 -05:00
james.maes 0ee169b798 perf(bundle): budget first-load JS per route and keep Recharts off /app/[slug] (#710)
- scripts/perf-budget.mjs (pnpm perf:budget): measures the static
  export (out/) per route: every script and stylesheet the route's HTML
  loads before hydration, gzip -9, plus total JS and the largest chunk
  (Next 16 no longer prints these). Compares them with perf-budget.json,
  writes test-results/performance/bundle.json and exits 1 on a breach.
- perf-budget.json: budgets about 5% above the measured sizes.
- CI: a "Bundle budget (static export)" job runs build:export and the
  check on every PR.
- Worst regression found: /app/[slug] (app home, record query, process,
  report) imported AppHome from the widgets barrel, which re-exports the
  chart widgets, so Recharts + d3 (a 94 KB gzip chunk plus 7 KB) was in
  the first load of every table and process page although WidgetRenderer
  loads charts lazily. The slug and saved-view pages import their page
  components by module path now. /app/[slug] first-load JS:
  562.9 -> 448.2 KB gzip (-20%); total JS 689.2 -> 675.0 KB, 54 -> 51
  chunks. Charts still load on demand on dashboards.
2026-09-25 17:32:06 -05:00
james.maes 5e9f013376 fix(acceptance): report the security variant ready only after startup priming (#700)
The first reset after a variant start could race the startup priming and
fail with a 500 (TABLE_BASED priming hashes passwords, widening the window).
Priming now runs under the reset lock and /acceptance/ready waits for it.
2026-09-25 17:31:09 -05:00
james.maes 40efee5f8b test(acceptance): process launches from the query carry returnTo; quiet app fixture is read-only (#714) 2026-09-25 17:29:36 -05:00
james.maes d3fad8cbda fix(query): phone filter sheet as a modal dialog, on-screen column panel, remembered view mode (#708)
The phone filter sheet is a Radix dialog: focus moves in, Escape and Close
return focus to the Filter button, and its body scrolls inside a sheet that
fits the viewport. The column configuration panel is clamped inside a phone
viewport. A chosen card/table view is remembered per table, so returning from a
record keeps it; the page reports the applied mode in data-view-mode.
2026-09-25 17:23:19 -05:00
james.maes 709edfbf15 fix(records): touch-sized View All and View links in relationship panels (#708) 2026-09-25 17:23:09 -05:00
james.maes d7b2ee8cd7 fix(query): 44 px label targets for grid and card checkboxes, cards follow query selections (#708)
Grid header/row checkboxes and card checkboxes sit inside a label, which the
coarse-pointer rule sizes to 44 x 44 px; tapping it selects without opening the
record. Cards now show all/first-N selections as checked like the grid, name
each value by field (card-field-<name>) and wrap long values instead of
cutting them off.
2026-09-25 17:23:07 -05:00
james.maes 58b952445a feat(parity): table variants in process runs, Material app-home settings, leaf block widgets (#714)
- process init, every step and the run's record requests carry the table's
  stored variant (Material's qqq.tableVariant.<table>), QRY-066
- MaterialDashboardAppMetaData showAppLabelOnHomeScreen and
  includeTableCountsOnHomeScreen are honored on the app home, NAV-032
- a widget payload that is a single leaf block renders that block, WID-064
2026-09-25 17:15:02 -05:00
james.maes 1a71b4988d docs(acceptance): list the TABLE_BASED and login branding phone rows (#700, #703) 2026-09-25 17:14:38 -05:00
james.maes 4bb147ea4e feat(auth): TABLE_BASED sign-in and pre-sign-in login branding (#700, #703)
TABLE_BASED authentication (#700):
- The login page asks for a username and password (also after an expired
  session and after logout) and signs in through POST /qqq/v1/manageSession
  with an Authorization: Basic header; the password is read from the form
  on submit, sent once, cleared after every attempt and never stored.
- Sessions resume from the sessionUUID cookie after a reload; stale session
  cookies are expired before a sign-in so they cannot shadow the new one; a
  different user signing in drops the previous user's cached data.
- Refused credentials are shown in the form; empty fields and usernames with
  a colon are named before anything is sent.
- Acceptance on the TABLE_BASED security variant (SEC-034..SEC-037: sign-in,
  failure, idle expiry, logout) with owned users and sessions in the H2
  database; SEC-032 now proves an unknown type from an owned UNSUPPORTED
  variant, since TABLE_BASED is supported.

Login branding (#703):
- The login card shows the logo, app name, accent color, favicon and title
  from the unauthenticated GET /qqq/v1/metaData/authentication branding;
  logo and icon sources are limited to paths, http(s) and inline images.
- The dashboard shell applies branding through the same helper.
- NAV-030 proves the branding before sign-in and that banners stay hidden.
2026-09-25 17:11:49 -05:00
james.maes 8147a29e3b test(mobile): touch target and horizontal scroll helpers for phone specs (#708) 2026-09-25 17:05:59 -05:00
james.maes d0818d1a61 feat(mobile): tablet project, 44px touch targets and a phone coverage gate (#708)
Adds an iPad (WebKit, touch) acceptance project that runs the @mobile and
@tablet tests, sizes controls to 44 x 44 CSS px on coarse pointers, and makes
the gate require a passing touch-project test for every required row unless
the row records why it is desktop-only.
2026-09-25 17:05:03 -05:00
james.maes 53bc5d377c feat(parity): Material page shortcuts, hash links and table-scoped process URLs (#714)
- n/r/f on the table query and n/e/c/d/a on the record view, suppressed in text
  entry, with modifiers and while a dialog or menu is open
- /app/{table}/{process}, /app/{table}/{id}/{process} and /app/{table}/{report}
  open the process or report; runs launched from a record or query return there
- record view #audit, #/launchProcess=, #/createChild= and section anchors;
  create page #/defaultValues= and #/disabledFields= presets
- child record list Add new presets and locks the join fields, as Material does
2026-09-25 17:02:03 -05:00
james.maes 06c47ee3be feat(api): load possible values through the v1 API (#699)
Table, process and standalone possible values now POST a JSON search
({searchTerm, ids, labels, useCase}) to /qqq/v1/.../possibleValues/...
instead of the legacy GET routes. The v1 routes also enforce table and
process permission (QRun-IO/qqq#406).

Acceptance: REC-049 (table field, persistence, 403 for a source the
persona cannot read, 404 for an unknown field) and PRC-048 (process
field); full chromium gate 329/329.
2026-09-25 17:00:18 -05:00
james.maes 604c44c7b9 ci: run the mocked e2e suite, src/lib coverage and Storybook on every PR (#709)
- .github/workflows/ci.yml: three parallel jobs on pull_request and
  pushes to main/develop, none needing a backend:
  unit (typecheck, lint, test:coverage), e2e (pnpm build, then the
  mocked Playwright suite against the standalone server, chromium) and
  storybook (build-storybook).
- Mocked e2e reliability:
  - saved-views.spec mocked /qqq/v1/processes/<name>/init with a JSON
    `values` field, but the app posts saved-view processes to the
    registered route (/processes/<name>/init) with one multipart field
    per value, so three tests saw no views. The mocks now match the
    real request.
  - playwright.config: QQQ_E2E_SERVER=production serves the `pnpm build`
    standalone output (scripts/e2e-server.mjs) instead of compiling
    routes on first hit; QQQ_E2E_PORT picks the port; retries 0 so a
    flaky test fails instead of passing on retry; trace kept on failure.
  - 104/104 pass; 520/520 with --repeat-each=5 --workers=4.
- vitest.config: coverage thresholds for src/lib just under the measured
  82.8/75.65/89.87/84.42 (statements/branches/functions/lines), and
  higher per-folder floors for src/lib/api and src/lib/utils.
- Storybook: silence Rollup's per-module "use client" directive and
  sourcemap-location warnings (100 lines per build).
- playwright-report/index.html and test-results/.last-run.json were
  committed despite being ignored; untracked.
2026-09-25 16:55:07 -05:00
james.maes caeadfb078 build(deps): Vitest 4.1.11 and the Vite Storybook framework (#698)
- vitest, @vitest/coverage-v8 and @vitest/ui 3.2.7 -> 4.1.11: fixes
  GHSA-82fw-gwwq-j7x9 in @vitest/mocker (patched in 4.1.11). All 93
  files / 923 unit tests pass unchanged.
- @storybook/nextjs -> @storybook/nextjs-vite (same 10.6.0 release).
  The webpack framework pulled in node-polyfill-webpack-plugin ->
  crypto-browserify -> browserify-sign/create-ecdh -> elliptic 6.6.1
  (GHSA-848j-6mx2-7j84, no patched release). None of the four stories
  uses a Node core module, so the Vite builder removes the path instead
  of shipping polyfills; 328 fewer packages resolve. The '@' alias moves
  from webpackFinal to viteFinal.
- storybook-static/ is ignored.

pnpm audit: No known vulnerabilities found.
2026-09-25 16:40:43 -05:00
1338 changed files with 255544 additions and 15247 deletions

No files matched your search

+10 -93
View File
@@ -1,97 +1,14 @@
version: 2.1
setup: true
orbs:
qqq-orb: kingsrook/qqq-orb@0.6
# ─── Jobs ────────────────────────────────────────────────────────────────────
# The dashboard jar (static export under next-dashboard/) is published to Maven
# Central on release tags, with the same orb commands and credentials as the
# Material Dashboard. QApplicationJavalinServer serves it at / when it is present.
commands:
configure-package-install:
steps:
- run:
name: Keep package installation unattended
command: |
# These build machines are discarded after the job; avoid the interactive needrestart prompt.
sudo sed -i 's|http://security.ubuntu.com/ubuntu|https://archive.ubuntu.com/ubuntu|g' /etc/apt/sources.list
sudo install -d /etc/needrestart/conf.d
printf '%s\n' '$nrconf{restart} = "l";' | sudo tee /etc/needrestart/conf.d/99-qqq-ci.conf >/dev/null
jobs:
publish_dashboard_jar:
executor: qqq-orb/java
steps:
- configure-package-install
- checkout
- qqq-orb/install_java:
install_node: true
node_version: "22"
- run:
name: Build the static export
command: |
corepack enable
pnpm install --frozen-lockfile
pnpm typecheck
pnpm lint
pnpm test
pnpm build:export
- qqq-orb/mvn_manage_version
- qqq-orb/mvn_jar_deploy
# ─── Workflows ───────────────────────────────────────────────────────────────
# Branch strategy mirrors the QRun-IO orb convention:
# test_only → all feature / non-protected branches
# publish_snapshot → develop
# publish_release_candidate → release/*
# publish_release → main / version tags
# publish_hotfix_release → hotfix/*
qqq-orb: kingsrook/qqq-orb@0.8
workflows:
test_only:
setup:
jobs:
- qqq-orb/node_app_test_only:
filters:
branches:
ignore: /(develop|main|release\/.*|hotfix\/.*|integration.*)/
tags:
only: []
publish_snapshot:
jobs:
- qqq-orb/node_app_build:
filters:
branches:
only: [ develop ]
publish_release_candidate:
jobs:
- qqq-orb/node_app_build:
filters:
branches:
only: [ /release\/.*/ ]
publish_release:
jobs:
- qqq-orb/node_app_build:
filters:
branches:
only: [ main ]
tags:
only: [ /v.*/ ]
- publish_dashboard_jar:
context: [qqq-maven-registry-credentials]
filters:
branches:
ignore: /.*/
tags:
only: /^v[0-9]+\.[0-9]+\.[0-9]+(-RC\.[0-9]+)?$/
publish_hotfix_release:
jobs:
- qqq-orb/node_app_build:
filters:
branches:
only: [ /hotfix\/.*/ ]
- qqq-orb/generate_pipeline:
orb_version: '0.8'
filters:
branches:
ignore: /.*/
tags:
only: /.*/
+2
View File
@@ -6,3 +6,5 @@ playwright-report
coverage
.env*
*.log
.pnpm-store
+1
View File
@@ -0,0 +1 @@
dynamic-components/** -text
+13
View File
@@ -0,0 +1,13 @@
# Copyright 2026 QRun.IO, Inc.
# Licensed under the Apache License, Version 2.0.
# https://www.apache.org/licenses/LICENSE-2.0
name: build
on:
push:
jobs:
maven:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
secrets: inherit
with:
node-version: '22.20.0'
prebuild: corepack enable && pnpm install --frozen-lockfile && pnpm typecheck && pnpm lint && pnpm test && pnpm build:export
+5
View File
@@ -0,0 +1,5 @@
# Default reviewers for every change.
* @KofTwentyTwo
# The UI API contract (QRun-IO/qqq-frontend-next#107): the vendored documents and the off-spec list
/src/lib/api/openapi/ @KofTwentyTwo
+32
View File
@@ -0,0 +1,32 @@
# Weekly, grouped dependency updates. Minor and patch updates arrive as one PR per ecosystem;
# major updates stay separate so each gets its own review. At most three version-update PRs per
# ecosystem are open at once, so their real-backend acceptance runs do not starve release PRs of runners. Security updates are enabled in the
# repository settings and are not limited by this schedule.
version: 2
updates:
- package-ecosystem: npm # pnpm-lock.yaml
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 3
groups:
npm-minor-and-patch:
update-types: [minor, patch]
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 3
groups:
github-actions:
patterns: ['*']
- package-ecosystem: maven # pom.xml (the dashboard jar)
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 3
groups:
maven-minor-and-patch:
update-types: [minor, patch]
-81
View File
@@ -1,81 +0,0 @@
name: Real-backend acceptance
# Full QRun-IO/qqq#649 acceptance: the QRun-owned sample application built from QQQ source,
# serving this repository's production static export, driven by Playwright in each browser.
# The matrix gate fails on any required row without a first-attempt pass.
on:
pull_request:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
inputs:
qqq_ref:
description: QRun-IO/qqq branch or tag to build the sample backend from
required: false
permissions:
contents: read
concurrency:
group: acceptance-${{ github.ref }}
cancel-in-progress: true
env:
# The released QQQ version this dashboard is accepted against; override per run.
QQQ_REF: ${{ inputs.qqq_ref || vars.QQQ_ACCEPTANCE_REF || 'develop' }}
NEXT_TELEMETRY_DISABLED: '1'
jobs:
acceptance:
strategy:
fail-fast: false
matrix:
# The phone project runs only @mobile tests, so it shares the chromium job's gate.
browser: ['chromium,mobile', firefox, webkit]
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: QRun-IO/qqq
ref: ${{ env.QQQ_REF }}
path: qqq-backend
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: '21'
cache: maven
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
- name: Install dependencies
run: |
corepack enable
pnpm install --frozen-lockfile
pnpm exec playwright install --with-deps chromium firefox webkit
- name: Build QQQ from source
run: mvn -B -q -f qqq-backend/pom.xml install -DskipTests -Dspotbugs.skip=true -Dpmd.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true
- name: Build and install the dashboard jar (static export)
run: |
pnpm build:jar
mvn -B -q install
echo "NEXT_JAR_VERSION=$(mvn -B -q help:evaluate -Dexpression=project.version -DforceStdout)" >> "$GITHUB_ENV"
- name: Build the sample application jar
run: |
# Run from the QQQ checkout: its checkstyle/pmd configuration paths are relative to it.
(cd qqq-backend && mvn -B -q -f qqq-sample-project/pom.xml package -DskipTests -Djacoco.skip=true \
-Dqqq.frontend.next.version="$NEXT_JAR_VERSION")
echo "QQQ_SAMPLE_JAR=$(ls "$PWD"/qqq-backend/qqq-sample-project/target/*-jar-with-dependencies.jar)" >> "$GITHUB_ENV"
- name: Acceptance (${{ matrix.browser }})
env:
QQQ_ACCEPTANCE_BROWSERS: ${{ matrix.browser }}
run: node scripts/acceptance.mjs --skip-build
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: acceptance-${{ matrix.browser == 'chromium,mobile' && 'chromium-mobile' || matrix.browser }}
path: test-results/acceptance/
retention-days: 30
+41
View File
@@ -0,0 +1,41 @@
name: Next centralized CI
'on':
pull_request:
types:
- opened
- synchronize
- reopened
- labeled
- unlabeled
push:
branches:
- main
tags:
- v*
workflow_dispatch:
inputs:
qqq_ref:
description: QQQ source ref for acceptance
type: string
required: false
permissions:
contents: read
jobs:
ci:
if: (github.event_name == 'pull_request' && !contains(fromJSON('["labeled","unlabeled"]'),
github.event.action)) || (github.event_name == 'push' && github.ref == 'refs/heads/main')
uses: QRun-IO/qqq-orb/.github/workflows/next-ci.yml@0f33ddadf88b9a1b940b8883782fe2000ee1d2bd
acceptance:
if: github.event_name != 'pull_request' || !contains(fromJSON('["labeled","unlabeled"]'),
github.event.action) || github.event.label.name == 'full-acceptance'
uses: QRun-IO/qqq-orb/.github/workflows/next-acceptance.yml@0f33ddadf88b9a1b940b8883782fe2000ee1d2bd
with:
qqq_ref: ${{ inputs.qqq_ref || '' }}
dashboard-image:
if: (github.event_name == 'pull_request' && !contains(fromJSON('["labeled","unlabeled"]'),
github.event.action)) || (github.event_name == 'push' && startsWith(github.ref,
'refs/tags/v'))
permissions:
contents: read
packages: write
uses: QRun-IO/qqq-orb/.github/workflows/next-dashboard-image.yml@0f33ddadf88b9a1b940b8883782fe2000ee1d2bd
-75
View File
@@ -1,75 +0,0 @@
name: Dashboard image
on:
pull_request:
push:
tags: ['v*']
permissions:
contents: read
env:
IMAGE: ghcr.io/qrun-io/qqq-frontend-next
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
if: github.event_name == 'push'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
with:
context: .
file: docker/quickstart/Dockerfile
platforms: linux/${{ matrix.arch }}
push: ${{ github.event_name == 'push' }}
load: ${{ github.event_name == 'pull_request' }}
tags: ${{ env.IMAGE }}:${{ github.sha }}-${{ matrix.arch }}
cache-from: type=gha,scope=dashboard-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=dashboard-${{ matrix.arch }}
- name: Verify runnable dashboard
if: github.event_name == 'pull_request'
env:
TAG: ${{ env.IMAGE }}:${{ github.sha }}-${{ matrix.arch }}
run: |
container=$(docker run -d --rm -p 127.0.0.1:3000:3000 "$TAG")
trap 'docker rm -f "$container"' EXIT
curl -fsS --max-time 3 --retry 30 --retry-delay 1 --retry-all-errors \
--retry-max-time 90 http://127.0.0.1:3000/app/person >/dev/null \
|| { docker logs "$container"; exit 1; }
publish:
if: github.event_name == 'push'
needs: build
runs-on: ubuntu-24.04
permissions:
packages: write
steps:
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish versioned multi-architecture image
run: |
docker buildx imagetools create \
--tag "$IMAGE:${GITHUB_REF_NAME#v}" \
"$IMAGE:$GITHUB_SHA-amd64" "$IMAGE:$GITHUB_SHA-arm64"
+6
View File
@@ -10,6 +10,8 @@
# next.js
/.next/
/.next-collaboration/
/.next-presence-live/
/out/
# production
@@ -40,3 +42,7 @@ target/
/playwright-report/
/log/
/.playwright-mcp/
/storybook-static/
/public/scripting-generated/
/public/pdf-generated/
/public/dynamic-qfmd-components/
+5
View File
@@ -0,0 +1,5 @@
# Reviewed findings committed before the Gitea migration; exact historical fingerprints only.
0969231c95f876bb0a41529e8c6c4621a0257aee:src/lib/qa-capture/sanitizers.test.ts:jwt:33
23a855297de0f84b564b20d782e01180a68b5257:tests/acceptance/specs/security/restrictions.spec.ts:generic-api-key:248
+3
View File
@@ -0,0 +1,3 @@
profile: frontend-next
orb_version: '0.8'
default_branch: main
+27 -8
View File
@@ -1,21 +1,40 @@
import type { StorybookConfig } from '@storybook/nextjs'
import type { StorybookConfig } from '@storybook/nextjs-vite'
import { fileURLToPath } from 'node:url'
// The Vite builder: the webpack-based @storybook/nextjs framework bundled Node core
// polyfills (crypto-browserify -> elliptic, GHSA-848j-6mx2-7j84) that no story needs.
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
staticDirs: ['../public'],
addons: [
'@storybook/addon-docs',
],
framework: {
name: '@storybook/nextjs',
name: '@storybook/nextjs-vite',
options: {},
},
webpackFinal: async (config) => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
'@': fileURLToPath(new URL('../src', import.meta.url)),
}
viteFinal: async (config) => {
const src = fileURLToPath(new URL('../src', import.meta.url))
const alias = config.resolve?.alias
config.resolve = {
...config.resolve,
alias: Array.isArray(alias)
? [...alias, { find: '@', replacement: src }]
: { ...alias, '@': src },
}
// Rollup drops the Next.js "use client" directives (meaningless in Storybook) and warns
// once per client module; keep the build log to the warnings that matter.
const onwarn = config.build?.rollupOptions?.onwarn
config.build = {
...config.build,
rollupOptions: {
...config.build?.rollupOptions,
onwarn(warning, warn) {
if (warning.code === 'MODULE_LEVEL_DIRECTIVE' || warning.code === 'SOURCEMAP_ERROR') return
if (onwarn) onwarn(warning, warn)
else warn(warning)
},
},
}
return config
},
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Preview } from '@storybook/react'
import type { Preview } from '@storybook/nextjs-vite'
import '../src/styles/globals.css'
const preview: Preview = {
+9
View File
@@ -0,0 +1,9 @@
<!-- BEGIN:nextjs-agent-rules -->
## This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
<!-- END:nextjs-agent-rules -->
+18 -1
View File
@@ -8,7 +8,7 @@ QQQ is a metadata-driven, low-code application framework. The admin UI renders e
## Tech Stack
- **Framework:** Next.js 15 (App Router) with React 19
- **Framework:** Next.js 16 (App Router) with React 19
- **Language:** TypeScript 5.x (`strict: true` — no `any` in component props)
- **Styling:** Tailwind CSS 4.x with CSS custom properties for QQQ theme tokens
- **Components:** shadcn/ui (Radix primitives + Tailwind)
@@ -143,6 +143,13 @@ pnpm lint # ESLint
pnpm format # Prettier
```
## Branching and Releases
Trunk-based: `main` is the only long-lived branch (no `develop`, `release/*` or `hotfix/*`).
Branch from `main`, keep the branch current by merging `main` in (no rebase or force-push under
review), sign every commit, and open the PR into `main`. Releases are signed `v*` tags on `main`.
Required checks and the release flow are in [CONTRIBUTING.md](CONTRIBUTING.md).
## Implementation Plans
Full implementation plans are in `docs/implementation-plans/`. The project is built in 6 work packages:
@@ -182,3 +189,13 @@ the second-brain vault (`R:/Git.Local/KofTwentyTwo/second-brain`):
Consult the dossier before deep-diving the code — it covers the API contract this
frontend targets (including versioned-middleware endpoint gaps), maturity assessment,
known parity gaps, and its relationship to `qqq-frontend-material-dashboard`.
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
<!-- END:nextjs-agent-rules -->
+51
View File
@@ -0,0 +1,51 @@
# Contributing
## Branching: trunk-based on `main`
`main` is the only long-lived branch. There is no `develop`, `release/*` or `hotfix/*` branch.
1. Branch from the current `main` (`feature/...`, `fix/...`, `docs/...`, `chore/...`).
2. Keep the branch current by merging `main` into it; do not rebase or force-push a branch
that is under review.
3. Open a pull request into `main`. Every commit must be signed.
4. A pull request merges when the required checks pass and the code owner approves.
Head branches are deleted automatically after the merge.
### Required checks
| Check | Source |
|-------|--------|
| `Typecheck, lint, unit tests and coverage` | `.github/workflows/ci.yml` |
| `Mocked e2e (Playwright, chromium)` | `.github/workflows/ci.yml` |
| `Bundle budget (static export)` | `.github/workflows/ci.yml` |
| `Storybook build` | `.github/workflows/ci.yml` |
| `ci/circleci: qqq-orb/node_app_test_only` | `.circleci/config.yml` |
The real-backend acceptance matrix (`.github/workflows/acceptance.yml`) runs on every pull request
and on `main`. Treat a red row as a release blocker and investigate it; never re-run it until it passes.
## Releases
A release is a signed `v<version>` tag on `main` (for example `v1.0.0-RC.14`), where `package.json`
and `pom.xml` carry that version. The tag publishes the dashboard jar to Maven Central (CircleCI
`publish_dashboard_jar`) and the dashboard container image (`.github/workflows/dashboard-image.yml`).
Release notes live in `docs/releases/`.
## Dependencies
Dependabot opens grouped weekly updates for npm, GitHub Actions and Maven (`.github/dependabot.yml`),
plus security updates as they are published.
## UI API calls (merge order)
Next calls the backend only through operations described in the vendored UI API documents:
`src/lib/api/openapi/qqq-v1.json`, plus one JSON file per extension in `extensions/`.
`ui-api-contract.test.ts` fails on any other call.
A feature that needs new backend calls lands in this order:
1. The qbit or module ships the operations as a UI API extension, with spec classes and its
committed `openapi/qqq-v1/<name>.yaml`.
2. That document is vendored here, as `extensions/<name>.json` or a re-vendored `qqq-v1.json`.
3. The UI PR that calls them.
`OFF_SPEC_CALLS` in `ui-api-calls.ts` is a shrinking list of calls that predate the extension point.
`OFF_SPEC_CEILING` caps it: lower the ceiling as entries move onto specs, and never raise it.
+108 -4
View File
@@ -1,7 +1,14 @@
# QQQ Frontend Next
> **QBit integration branch (2026-11-04).** File delivery/preview, TypeScript editing and generic process controls use the QQQ UIUX APIs. See [current verification and dependency setup](docs/QBIT-RELEASE-STATUS.md) and [remaining release requirements](docs/QBIT-KNOWN-ISSUES.md). These changes are source/local snapshots, not part of the published RC.36 baseline described below.
A metadata-driven admin UI for the QQQ low-code application framework, rewritten from React + Material UI to Next.js 16 + Tailwind CSS + shadcn/ui. The UI renders entirely from backend metadata — no table names, field lists, or navigation items are hardcoded in the frontend.
**Published baseline: Next UI 1.0.0-RC.36**, which adds accessibility fixes: readable link text, 24px clear targets, h2 widget titles and skip-link focus to RC35. Confirm availability in the [RC36 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.36) before installing; see the [RC36 notes](docs/releases/1.0.0-RC.36.md) for the preparation checkpoint and verification scope. [RC35](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.35) was the previously verified public candidate at that checkpoint and remains immutable. Final 1.0 remains held for application soak and explicit owner approval.
**Current roadmap and known issues:** [Next UI 1.0 tracker](https://github.com/QRun-IO/qqq/issues/713). GitHub issues are the source of truth for status and remaining work; see [issue tracking](docs/ISSUE-TRACKING.md).
## Use it in a QQQ application
The dashboard is the default admin UI for QQQ 4.1+. Add the jar (managed by `qqq-bom-pom`) and `QApplicationJavalinServer` serves it at `/`, on the same port and origin as the API:
@@ -10,12 +17,75 @@ The dashboard is the default admin UI for QQQ 4.1+. Add the jar (managed by `qqq
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.36</version>
</dependency>
```
The explicit version overrides an older BOM-managed frontend. RC36 requires a published QQQ 4.1 candidate: 4.1.0-RC.1 or later; 4.1.0-RC.2 or later for standalone branding images (QQQ #1009); and **4.1.0-RC.3 or later** for FILE_PREVIEW thumbnails (QQQ #1056) and the instance home app (QQQ #1059); **4.1.0-RC.4 or later** for record developer panels (QQQ #1076); **4.1.0-RC.5 or later** for the record picker (QQQ #1099), form validation limits (QQQ #1100) and date filter presets (QQQ #1115). The Alerts bell appears only with qbit-alerts (0.1.0-RC.1 or later); the notification center needs qbit-alerts 0.1.0-RC.2 or later. The assistant panel appears only with the qbit-assistant backend (0.1.0-RC.3 or later). The SQL Workbench needs the qbit-sql-workbench QBit (1.0.0-RC.1 or later); QA capture appears only with qbit-qa-capture (the `qa-capture` UI API extension); see the release notes for the verified backend. Select Next explicitly with `-Dqqq.javalin.frontend=next`. This does not release QQQ 4.1.
The jar holds the static export (`pnpm build:export`) under `next-dashboard/`. Deep links such as `/app/person/1` are served from placeholder pages and read their route from the browser path, so no Node.js server is involved. The Material Dashboard remains available: select it with `withServeFrontendMaterialDashboard(true)` or `-Dqqq.javalin.frontend=material`. Material routes are `/<app>/<table>/<id>`; Next routes are `/app/<table>/<id>`.
Feature coverage is certified by the real-backend acceptance matrix in [`docs/acceptance/feature-matrix.md`](docs/acceptance/feature-matrix.md) ([QRun-IO/qqq#649](https://github.com/QRun-IO/qqq/issues/649)). Run it with `pnpm test:acceptance` (see [`tests/acceptance/README.md`](tests/acceptance/README.md)).
Feature coverage is recorded in the real-backend acceptance matrix in [`docs/acceptance/feature-matrix.md`](docs/acceptance/feature-matrix.md) ([QRun-IO/qqq#649](https://github.com/QRun-IO/qqq/issues/649)). Run it with `pnpm test:acceptance` (see [`tests/acceptance/README.md`](tests/acceptance/README.md)).
## Adopting in another app
How an existing QQQ backend application (for example one still on QQQ 0.x with the Material Dashboard) switches to, or upgrades, the Next dashboard. The frontend is distributed only as a Maven jar; there is no npm package (`package.json` is `private`).
### Coordinates and compatible versions
| Piece | Coordinates | Version |
|---|---|---|
| Next dashboard | `com.kingsrook.qqq:qqq-frontend-next` | `1.0.0-RC.13` (latest; [all versions](https://repo1.maven.org/maven2/com/kingsrook/qqq/qqq-frontend-next/)) |
| QQQ BOM | `com.kingsrook.qqq:qqq-bom-pom` (import scope) | `4.1.0-RC.3` (manages `qqq-frontend-next` at `1.0.0-RC.11`, so pin `1.0.0-RC.13` explicitly) |
| SQL Workbench QBit (optional) | `com.kingsrook.qbits:qbit-sql-workbench` | `1.0.0-RC.1` (built against QQQ `4.1.0-RC.2`) |
| Container image (optional, separate Node server) | `ghcr.io/qrun-io/qqq-frontend-next` | `1.0.0-RC.13` |
| Next feature | Minimum QQQ | Backend work |
|---|---|---|
| Dashboard at `/` | 4.1.0-RC.1 | Add the jar; `QApplicationJavalinServer` (`qqq-middleware-javalin`) serves it. |
| Standalone branding images | 4.1.0-RC.2 | Branding metadata ([qqq#1009](https://github.com/QRun-IO/qqq/issues/1009)). |
| FILE_PREVIEW thumbnails and lightbox | 4.1.0-RC.3 | Adorn each field with `AdornmentType.FilePreviewValues.newFieldAdornment()` ([qqq#1056](https://github.com/QRun-IO/qqq/issues/1056)). The field value, or `thumbnailUrlField` / `fullSizeUrlField`, must be an http(s) URL or a same-origin path; a BLOB field needs a `FILE_DOWNLOAD` adornment as well. Plain URL fields are never auto-detected. |
| Instance home app | 4.1.0-RC.3 | `qInstance.setHomeAppName("<app>")` ([qqq#1059](https://github.com/QRun-IO/qqq/issues/1059)). Users who may not open that app get Next's `/app` overview. |
| SQL Workbench | 4.1.0-RC.2 + QBit | Produce the QBit (`new SqlWorkbenchQBitProducer().withQBitConfig(...).produce(qInstance)`); it adds the `sqlWorkbench` app with the `SQL_WORKBENCH` widget, five tables and `/qqq/v1/sql-workbench/*` routes. Grant `sqlWorkbench.hasAccess` (and `sqlWorkbench.write` only for write mode) to super-users; configure `readOnlyCredentials` in production. See the [QBit README](https://github.com/QRun-IO/qbit-sql-workbench#readme). |
| Responsive layouts, readable child lists, compact dates | 4.1.0-RC.1 | None; frontend only. Child-record widgets honour `onlyIncludeFieldNames` order. |
### Switching from the Material Dashboard
1. Move the application to QQQ 4.1 (`qqq-bom-pom` `4.1.0-RC.3`). This is a major upgrade from QQQ 0.x; follow the QQQ 4.1 upgrade notes for the backend itself.
2. Add the `qqq-frontend-next` dependency above. When the jar is on the classpath, QQQ serves Next at `/` unless the Material Dashboard is requested explicitly.
3. Choose the dashboard in code with `withServeFrontendNext(true)` / `withServeFrontendMaterialDashboard(true)`, or, when neither is set in code, with `-Dqqq.javalin.frontend=next|material|none`. An app that sets `withServeFrontendMaterialDashboard(false)` and serves Material through its own route provider gets Next at `/` as soon as the jar is added; remove that custom route, or set `withServeFrontendNext(false)` to keep Material.
4. Authentication: keep the existing identity-provider client. OAUTH2 redirects to `{origin}/token` and AUTH_0 to `{origin}/`, as Material did; both use PKCE.
5. URLs change from `/<app>/<table>/<id>` (Material) to `/app/<table>/<id>` (Next). Map old bookmarks and links with `QInstance.redirects` (exact or wildcard paths).
6. Optional: set `homeAppName`, add `FILE_PREVIEW` adornments, add the SQL Workbench QBit (table above). No database migrations are needed for the dashboard itself; the SQL Workbench QBit needs its five tables (DDL in its README).
For a full per-feature checklist (backend upgrade, QBits, migrations, permissions) see the QQQ adoption checklist: https://github.com/QRun-IO/qqq/blob/develop/docs/migration/adoption-checklist.adoc
To upgrade between RCs, change only the `qqq-frontend-next` version and check the minimum QQQ version in the table above and in the [release notes](docs/releases/).
### Configuration
Served from the jar there is nothing to configure in the frontend: it calls the API on the same origin at `/qqq/v1`, and the Javalin server sends the security headers and Content-Security-Policy. External embeds (for example QuickSight frames) need an allowed CSP source, not a disabled policy. The build-time variables under [Environment Variables](#environment-variables) apply only to frontend development and the standalone container image.
### App home navigation grids
App homes show automatic section ("Actions", "Reports", "Data") and child-app ("Apps")
card grids. When the app declares one or more widgets, the "Apps" grid is left out (even if
a declared widget is unavailable to the current user), so widget-based navigation is not
duplicated; the section grids stay.
Set `supplementalAppMetaData.nextUi.showChildGrid` on the app to override this default:
`true` shows both grids alongside widgets; `false` hides both grids, for a home whose
widgets already cover its sections and child apps.
Only boolean values override the default. Existing visibility and permission rules still apply.
### Known limitations
- Release candidates only; final 1.0 is held for application soak and owner approval ([#712](https://github.com/QRun-IO/qqq/issues/712), tracker [#713](https://github.com/QRun-IO/qqq/issues/713)).
- Served at `/` only: sub-path hosting is not supported ([#732](https://github.com/QRun-IO/qqq/issues/732)).
- Material parity is not complete ([#714](https://github.com/QRun-IO/qqq/issues/714)); check the parity ledger for the screens your app relies on.
- Real Auth0, Google Drive and QuickSight integrations are not yet verified end to end ([#713](https://github.com/QRun-IO/qqq/issues/713)).
- The container image fixes the backend origin at build time (`host.docker.internal:8000`); use the jar, or rebuild the image, for another origin.
- WebKit and tablet acceptance runs show intermittent navigation diagnostics ([#985](https://github.com/QRun-IO/qqq/issues/985), [#904](https://github.com/QRun-IO/qqq/issues/904)).
## Run the local QQQ sample
@@ -48,7 +118,7 @@ The dev server starts at [http://localhost:3000](http://localhost:3000).
| `QQQ_NEXT_OUTPUT` | _(unset)_ | `export` produces the static export for QQQ hosting (`pnpm build:export`) |
| `NEXT_PUBLIC_MOCK_API` | _(unset)_ | Set to `true` to activate MSW browser mocks for dev/demo without a live backend |
Copy `.env.example` to `.env.local` and adjust as needed.
Copy `.env.mock` to `.env.local` to run against MSW mocks, or set `NEXT_PUBLIC_MOCK_API=false` and point `NEXT_PUBLIC_API_BASE_URL` at a real backend.
## Available Commands
@@ -62,12 +132,14 @@ Copy `.env.example` to `.env.local` and adjust as needed.
| `pnpm start` | Serve the production build |
| `pnpm test` | Run Vitest unit tests (single pass) |
| `pnpm test:watch` | Run Vitest in watch mode |
| `pnpm test:coverage` | Run Vitest with V8 coverage report |
| `pnpm test:e2e` | Run Playwright end-to-end tests |
| `pnpm test:coverage` | Run Vitest with V8 coverage; fails below the `src/lib` thresholds in `vitest.config.ts` |
| `pnpm test:e2e` | Run the mocked Playwright e2e suite (`tests/e2e`, no backend); `QQQ_E2E_SERVER=production` serves the `pnpm build` output, `QQQ_E2E_PORT` picks the port |
| `pnpm perf:budget` | Check the static export (`out/`) against the bundle budget in `perf-budget.json` ([`docs/acceptance/performance.md`](docs/acceptance/performance.md)) |
| `pnpm typecheck` | Run `tsc --noEmit` (type check without emitting files) |
| `pnpm lint` | Run ESLint + Apache license header check |
| `pnpm format` | Run Prettier over all source files |
| `pnpm storybook` | Start Storybook component explorer on port 6006 |
| `pnpm build-storybook` | Build the static Storybook (`storybook-static/`) |
## Directory Structure
@@ -121,3 +193,35 @@ src/
- **Charts:** Recharts
- **Testing:** Vitest (unit), Playwright (E2E), axe-core (a11y)
- **Package Manager:** pnpm
## Default query columns
A new query view shows at most six fields: the visible primary key first, then
eligible fields from T1 sections followed by T2 sections, preserving section and
field order within each tier. Tables without sections use metadata field order.
Hidden fields, heavy fields, TEXT/HTML/BLOB/JSON-like types, and CODE_EDITOR or
RENDER_HTML adornments are excluded from the automatic selection (the primary key
is retained unless hidden). Fields outside T1/T2 are initially unchecked.
Other available columns remain selectable in the column picker.
Applications can override this selection with a string-array hint:
```json
{
"supplementalMetaData": {
"nextUi": {
"defaultColumns": ["id", "title", "status"]
}
}
}
```
The legacy `supplementalTableMetaData.nextUi.defaultColumns` path is also accepted;
the modern `supplementalMetaData.nextUi` object takes precedence. A valid hint
selects exactly the named available columns, without the six-field cap or automatic
primary-key inclusion. Unknown or unavailable fields are ignored; an empty array
selects no columns, and malformed hints fall back to the automatic selection.
Saved views and legacy stored visibility (including `{}`, meaning all base columns)
are preserved. New View and Reset All Changes on an unsaved view restore these
metadata defaults. Quick search searches only visible string fields. Responsive
card/grid behavior is unchanged.
+10 -3
View File
@@ -1,4 +1,4 @@
FROM registry-1.docker.io/library/node:22-bookworm-slim AS frontend
FROM registry-1.docker.io/library/node:22-alpine@sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914 AS frontend
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
RUN corepack enable && corepack prepare pnpm@9.15.9 --activate
@@ -9,14 +9,21 @@ ENV QQQ_BACKEND_URL=http://host.docker.internal:8000
ENV NEXT_PUBLIC_MOCK_API=false
RUN pnpm build
FROM registry-1.docker.io/library/node:22-bookworm-slim AS runtime
FROM registry-1.docker.io/library/node:22-alpine@sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914 AS runtime
LABEL org.opencontainers.image.source="https://github.com/QRun-IO/qqq-frontend-next"
# Standalone production starts node directly; package managers are build-only.
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack /opt/yarn-* \
&& rm -f /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack \
/usr/local/bin/pnpm /usr/local/bin/pnpx /usr/local/bin/yarn /usr/local/bin/yarnpkg
WORKDIR /app
ENV NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 HOSTNAME=0.0.0.0 PORT=3000
COPY --from=frontend --chown=node:node /app/.next/standalone ./
COPY --from=frontend --chown=node:node /app/.next/static ./.next/static
COPY --from=frontend --chown=node:node /app/public ./public
# server.js wrapped with the dashboard security headers (QRun-IO/qqq#734); add origins the
# dashboard must reach (QuickSight, custom component bundles) with QQQ_DASHBOARD_CSP_SOURCES
COPY --from=frontend --chown=node:node /app/standalone/qqq-server.mjs /app/standalone/security-headers.mjs /app/standalone/branding-assets.mjs /app/standalone/backend-prefixes.json ./
COPY LICENSE ./LICENSE
USER node
EXPOSE 3000
CMD ["node", "server.js"]
CMD ["node", "qqq-server.mjs"]
+37 -49
View File
@@ -23,10 +23,12 @@ Axios interceptor
▼
TanStack Query cache (onError)
│ 401 → silently ignored (already handled)
│ 403 → toast "Permission denied" / "You do not have permission…"
│ 403 → toast the server's message, or "Permission denied"
│ 404 → toast "Resource not found"
│ 5xx → toast "Server error — retrying…" / "Server error — please try again"
│ network → toast "Something went wrong"
│ other status → toast the server's message, or "Request failed (HTTP 409)"
│ no response → toast "No response from the server" / "The server took too long to respond"
│ every toast names the request, and a repeat of the same failure updates it
▼
Component (optional per-query onError)
│ Only needed when generic toast is wrong for the specific UX
@@ -66,52 +68,38 @@ The `unauthorizedCallback` is wired to the auth provider's logout/redirect funct
**Location:** `src/lib/query-client.ts` — `handleQueryError` function
```typescript
function handleQueryError(error: unknown, context: 'query' | 'mutation'): void {
const status = getErrorStatusCode(error)
It is registered on both `QueryCache.onError` and `MutationCache.onError`. A query or mutation whose
component renders its own error opts out with `meta: HANDLES_OWN_ERRORS`.
if (status === 401) return // handled by axios interceptor
| Failure | Banner text |
|---|---|
| Cancelled request (an axios cancel or an aborted `fetch`), 401 | none |
| 403 | the server's message (of a JSON body), or `Permission denied` |
| 404 | `Resource not found` |
| 5xx | `Server error — retrying…` (query), `Server error — please try again` (mutation) |
| Any other status (400, 409, 422, …) | the server's message (`userFacingError`, `error` or `message` of a JSON body), or `Request failed (HTTP 409)` |
| No response (connection failed) | `No response from the server` |
| No response (the 30 s timeout) | `The server took too long to respond` |
| An error the application raised (`throw new Error('Invalid audit response')`) | that sentence |
| A runtime error (`TypeError`, a schema error, …) | `The page hit an unexpected error (TypeError)` |
if (status === 403) {
toast.error(
context === 'mutation'
? 'You do not have permission to perform this action'
: 'Permission denied'
)
return
}
Under the text, every banner names what failed: the request as `POST /qqq/v1/table/order/query (HTTP 409)`,
or `While loading records` (the query key's namespace in plain words: `possibleValues` reads
`possible values`) for a query that failed outside a request. The
request is written without its query string, and every path segment that is not a plain word (a record
id, a UUID, an encoded value) is shown as `{id}`. Nothing else of the request or response is shown.
if (status === 404) {
toast.error('Resource not found')
return
}
**One banner per failure.** The banner's sonner `id` is its text plus the request, so a failure that
repeats (a polling query that fails on every attempt) updates the banner already on screen instead of
adding one. A different failure gets its own banner. A lost connection is one cause whichever request
met it, so it has a single banner, which names the request that failed most recently. Without the id
every failed poll added a banner, and sonner pauses a toast's timer while the tab is hidden or the
pointer rests on the stack, so the banners could pile up.
if (status !== undefined && status >= 500) {
toast.error(
context === 'mutation'
? 'Server error — please try again'
: 'Server error — retrying…'
)
return
}
// Network failure or unexpected status
toast.error('Something went wrong')
}
```
This handler is registered on both `QueryCache` and `MutationCache`:
```typescript
export const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error) => handleQueryError(error, 'query'),
}),
mutationCache: new MutationCache({
onError: (error) => handleQueryError(error, 'mutation'),
}),
})
```
**Console.** Each banner is also logged once with `console.warn('[API] Request failed:', { context,
status, code, name, request, area })`: the HTTP status, the axios error code (`ERR_NETWORK`,
`ECONNABORTED`, `ERR_BAD_REQUEST`), the error's name, the request as above and the query namespace.
It never logs a message, body, header or query string, because those can carry record data.
**Implication for components:** For most queries and mutations, you do not need `onError` at all. The global handler covers toasting. Add per-query handling only when you need to do something the global handler cannot — such as mapping a 400 error's field messages back to form fields.
@@ -139,7 +127,7 @@ Returns `undefined` for anything else (non-object errors, null, network failures
### 400 — Validation Error
The global handler does NOT toast on 400 because 400s typically carry field-level validation messages that must be mapped back to individual form fields. Handle 400 explicitly wherever you submit a form.
The global handler toasts the server's message for a 400 (or `Request failed (HTTP 400)` when it sent none). 400s typically carry field-level validation messages that belong beside individual form fields, so a form handles 400 explicitly, and sets `meta: HANDLES_OWN_ERRORS` on the mutation when the toast would repeat what the form shows.
```typescript
import { getErrorStatusCode } from '@/lib/utils/error-utils'
@@ -207,7 +195,7 @@ No additional per-component handling is needed for 5xx unless you want a custom
### Network Failure (no response)
Treated as non-4xx by the retry logic — queries retry up to 3 times. The global handler toasts "Something went wrong" after all retries are exhausted. No per-component handling needed for the common case.
Treated as non-4xx by the retry logic — queries retry up to 3 times. After all retries are exhausted the global handler toasts "No response from the server" (or "The server took too long to respond" for the 30 s timeout) with the request that failed. No per-component handling needed for the common case.
---
@@ -274,8 +262,8 @@ export function EditOrderForm({ orderId, schema, defaultValues }: Props) {
onError: (error) => {
if (getErrorStatusCode(error) === 400) {
// Map field errors from the server response body to form fields.
// The global handler does NOT toast on 400, so we must do it here or
// rely on inline field error messages in the form UI.
// The global handler toasts the server's message for a 400; the form
// places each field's message beside it.
const body = (error as AxiosError<{ errors: Record<string, string> }>).response?.data
if (body?.errors) {
for (const [field, message] of Object.entries(body.errors)) {
@@ -305,6 +293,6 @@ export function EditOrderForm({ orderId, schema, defaultValues }: Props) {
| Layer | File | Handles |
|---|---|---|
| Axios interceptor | `src/lib/api/client.ts` | 401 → redirect to login |
| Global query handler | `src/lib/query-client.ts` | 403, 404, 5xx, network → toast |
| Global query handler | `src/lib/query-client.ts` | Every status but 401, no response, page errors → one toast per failure, naming the request |
| Per-component `onError` | Your component | 400 field errors, custom empty states, navigation |
| `getErrorStatusCode` | `src/lib/utils/error-utils.ts` | Normalize `AxiosError` and plain-object errors to HTTP status code |
+37
View File
@@ -0,0 +1,37 @@
# Material CSS hooks in Next UI
QQQ application themes can provide `supplementalInstanceMetaData.materialDashboardTheme.customCss`. Next UI keeps the main Material Dashboard `data-qqq-id` hooks so application CSS can target metadata-driven controls. The source of truth for ID generation is [`src/lib/utils/qqq-id.ts`](../src/lib/utils/qqq-id.ts); [NAV-055](acceptance/feature-matrix.md) exercises representative selectors against the real sample server.
## ID rules
The ID suffix is lowercased, each run of characters outside `a-z` and `0-9` becomes one dash, leading and trailing dashes are removed, and the result is cut at 50 characters. Camel case is not split: `firstName` becomes `firstname`. Use the metadata label for navigation entries and the metadata name for fields, tables and sections.
| Control | Pattern | Example |
|---|---|---|
| Button | `button-{id or text}` | `button-save`, `button-query-mode-advanced` |
| Editable text field | `input-{field}` | `input-firstname` |
| Possible-value field | `select-{field}` | `select-speciesid` |
| Boolean field | `switch-{field}` | `switch-isemployed` |
| Navigation item | `sidenav-{label}` | `sidenav-people-app` |
| Menu item | `menu-item-{text}` | `menu-item-bulk-edit` |
| Tab | `tab-{label}` | `tab-related` |
| Table header | `table-header-{field}` | `table-header-firstname` |
| Link | `link-{text}` | `link-back-to-person` |
The ID may be on a layout-neutral wrapper around a native button or menu item. Select the wrapper when applying layout or visibility rules, and select its button descendant for button-specific styles. Inspect the rendered element before changing an application's CSS.
## Stable named hooks
- Navigation: `sidenav-root`, `sidenav-logo-area`, `sidenav-menu-list`, `sidenav-logout-button`; `.qqq-sidebar-active` marks the active item. `data-qqq-sidenav-item-type` identifies `user-profile` and `top-level-parent-app` entries.
- App home and chrome: `app-card-{appName}-icon`, `.banner.info`/`.banner.warning`/`.banner.error`, and `.qqq-branded-header-bar`.
- Query: `button-filter-builder`, `button-views`, `quick-views-container`, `button-query-mode-basic`, `button-query-mode-advanced`, `table-header-{field}`, and `menu-item-{action}`.
- Record view: `.recordView`, `record-sidebar`, `record-view-header-{table}`, `record-view-title-{table}`, `record-view-button-bar-{table}`, `record-view-actions-menu`, and `delete-confirmation-*` with `button-delete-yes`/`button-delete-no`.
- File previews: a `FILE_PREVIEW` value keeps its cell or value ID (`grid-cell-{field}`, `field-value-{field}`) on the thumbnail button with `data-preview-kind`; the box is `{id}-thumbnail`. The dialog uses `file-preview-dialog`, `file-preview-image`/`-pdf`/`-video`/`-audio`/`-unavailable`, `file-preview-details`, and `file-preview-{previous,next,zoom-in,zoom-out,zoom-fit,open,download,close}`.
- Forms: `.entityForm`, `record-create-{table}`/`record-edit-{table}`, `form-section-{section}`, `.field-wrapper.is-visible`, and `.stickyBottomButtonBar`. Default action buttons expose `data-button-variant` values such as `gradient`, `contained`, and `outlined`.
- Jobs: `button-jobs`, `jobs-active-badge`, `jobs-popover`, `link-job-{jobId}`, `link-all-jobs`, `jobs-page`, `tab-jobs-{active,recent,all}`, `job-progress-view`, `job-status`, `job-phases`, `job-progress-bar`, `job-progress-stats`, `job-progress-message`, `job-log-panel`, `job-log-scroll`, `button-job-log-{toggle,copy,latest}`, `job-background-countdown`, `button-job-{stay,background,cancel,retry,done}`, and `link-job-record`. Jobs motion is confined to `.qqq-job-{fill,shimmer,pulse,ring}` and is static under reduced motion.
The record view has a phone accordion and action sheet, a tablet tab list without the large-screen sidebar, and a large-screen tab list with the sidebar. A selector can be present in the DOM while its control is hidden at a narrower width. Test custom CSS at all three widths, including focus and contrast, before shipping it.
## Display mode and primary text
Next's persisted light/dark choice controls both theme tokens and Tailwind `dark:` variants. OS color preference does not override it; a configured Material application theme keeps light mode active. `--color-primary` controls branded fills, while `--text-color-primary` controls the existing `text-primary` classes and opacity variants. In light mode the text token follows the primary color; in dark mode it mixes the brand hue with white for readable text on dark surfaces. Explicit application theme and custom CSS rules still take precedence. Verify text and filled-button contrast separately when overriding these tokens.
+21
View File
@@ -0,0 +1,21 @@
# Help everywhere — implementation and verification
Backend: QRun-IO/qqq#1177 (`docs/HELP-CONTENT.md`).
## Implementation
- Vendored the supplied help-core `openapi.yaml` as the existing one-space-indented JSON document. The prior vendoring commits are `01dd7e9` and `8a0fbbd`; neither contains a generator script. Conversion uses the installed ESLint dependency `js-yaml`, then `JSON.stringify(document, null, 1)` with a final newline. The full core document is retained, including its other changes within UI API 1.2.0.
- Added `summary` to `QHelpContent`, reused that type for widget slots, and added app/process/report slot maps. `src/lib/api/help.ts` uses the typed API client and checks the advertised `help` feature before calling search. No off-spec exception was added. Search and coverage are optional spec operations; coverage is not called.
- `src/components/help/HelpIcon.tsx` selects role-specific help, shows the plain summary, and opens the shared long-help panel by click, Enter, or Space. Existing customization IDs are retained on the corresponding control or tooltip. Input descriptions remain available while the tooltip is closed.
- `HelpPanelProvider` is mounted in the authenticated dashboard. One `next/dynamic` panel renders sanitized long content using the existing `HelpContent` renderer, with related help, safe navigation, Escape dismissal, focus trapping, and return focus. Help controls use theme tokens and coarse-pointer touch targets.
- Existing field, section, grid-column, widget-title, widget-column, and process-step help now uses the shared icon. New placements cover app, table query/view/edit/create/copy, process, report, and record/table action headers or labels. Action help is a sibling of the action control; Radix menu help participates in roving focus.
- `HelpSearchResults` provides 200 ms debouncing, a two-character minimum, feature gating, and loading/error/empty states. Both `CommandMenu` and the global `SearchPage` use it. The page's existing grouped-results area is a natural place for help; the compact header/search-dialog listbox retains its current record/navigation model. The palette also offers **Search help…**.
- Author mode retains key suffixes. **Add help** / **Edit help** links require the core table's insert permission and use `/app/helpContent/create#/defaultValues=…` with `key` and the most specific `role`. `HELP_CONTENT_TABLE` is the one production constant for that core-defined table.
## Tests and fixtures
- New Vitest tests cover the API gate, role precedence, summaries, keyboard controls, author URL presets and permission checks, panel sanitization/focus, and palette help search.
- `tests/acceptance/fixture/HelpFixtures.java` defines synthetic Help Lab metadata, Markdown bodies and distinct summaries, an owned data table, and the core authoring table. It is wired into the normal define/reset lifecycle.
- `tests/acceptance/specs/help/help.spec.ts` and `matrix/help.json` add HLP-001 through HLP-006, light/dark cases, mobile tags, and 320/1920-pixel panel checks. Column/table-menu coverage remains desktop-only because mobile query results use cards. The area is registered in `acceptance-select.mjs`.
- Existing REC-039/040, PRC-015, and widget-column assertions now focus the help button and inspect formatted long content in the panel. This is necessary because tooltips now contain plain summaries and touch opens the panel. Existing scenarios and matrix requirements remain; none was waived.
- Historical results in `docs/acceptance/feature-matrix.md` are preserved. The new help rows are appended as **not run**.
+29
View File
@@ -0,0 +1,29 @@
# Isolated scripting editor
ScriptViewer routes explicit server-controlled ISOLATED projects before legacy hooks mount. Unknown nonempty modes fail closed. The native scripting extension must be advertised; otherwise isolated source stays unavailable. Developer page provides semantic project creation and opens the same IDE; project names never imply execution mode or actor identity.
The isolated CodeMirror editor supports multi-file TypeScript, immutable source history, explicit save/build/test/publish, server compiler diagnostics, scoped API declarations, JSON context tests and exact-commit GitHub imports with retained provenance. Edits invalidate build/test evidence. Saves and imports carry the observed latest revision. No browser credential fields or background repository synchronization.
## Semantic language service
Pinned editor-only typescript-editor alias6.0.3 powers a same-origin classic worker. It receives a bounded virtual filesystem and host-generated SDK declarations, with57 trusted ES2022 standard-library files bundled at build time. Diagnostics, completion, hover, go-to-definition(F12), formatting(Ctrl+Shift+F), file navigation and compiler diagnostic selection operate on the same workspace. Text remains ordinary undoable editor state. Tab moves focus.
There is no source execution, compiler emit, runtime fetch, arbitrary package loading, filesystem, DOM/Node declarations or user npm hooks. Browser analysis is advisory; authoritative strict builds still use the qualified isolated server. Worker cold deadline10s/warm3s terminates it on timeout; bounded request/result sizes, four pending calls, idle30s termination and recycling after200 requests limit work. The next explicit request may start a new worker; no hidden retry of timed-out source analysis. A browser worker supplies termination responsiveness, not an OS-enforced memory sandbox. Debugging and automatic imports are not implemented.
scripts/build-scripting-worker.mjs assembles only trusted package/compiler and our own session source into public/scripting-generated/typescript-6.0.3.worker.js, with license/notices. Generated9.6MB artifact is ignored and built before dev/build/build:export. Runtime worker requests no other resource. Exact compiler and library dependencies remain independent from server runtime qualification.
The alias package also exports tsc and would shadow the existing compiler under pnpm exec tsc. Use **pnpm typecheck** or **pnpm tsc**: both explicitly call node_modules/typescript/bin/tsc (the existing5.9 build compiler). Existing CI typecheck scripts follow that path. Do not replace frontend build TypeScript with the editor compiler. Compatible CodeMirror versions are pinned through pnpm overrides to avoid duplicate nominal EditorView/state classes.
## Authorization and uncertainty
Server permissions and worker availability control lifecycle actions, with backend checks authoritative. Use the matching Core `4.1.0-SNAPSHOT` dependency set described in [current integration status](QBIT-RELEASE-STATUS.md); the old mixed private-artifact/RC20 setup is superseded. Project creation and publication use typed core authority; isolated revision/file content and ownership are immutable. Real native HTTP lifecycle succeeds with protocol-only fixture worker; no production provider is implied.
Stop waiting or network timeout makes mutation outcome uncertain and prevents another mutation. History refresh does not prove completion; inspect history/reopen. Creation uncertainty directs the user to Scripts before retrying. Abort does not undo committed work. GitHub import needs a server-installed managed access provider and full immutable commit.
## Verification
2026-10-30: **114 focused tests across12 files**, existing build-compiler typecheck and changed-file ESLint passed. Real Playwright Chromium worker check exercises strict diagnostics, multi-file completion/hover/definition/format, rejects remote types and proves source does not execute or fetch. Final optimized production build with typecheck, static generation and standalone packaging passed at 21:36, including the creation panel (parent run20209, editor-build-verification.log). Native server tests separately cover create/save/build/context-test/publish and permission/association/legacy denials. No live browser-to-deployed-worker end-to-end or production sandbox qualification claim.
Run pnpm scripting:worker:check after generating the worker; this environment uses PLAYWRIGHT_BROWSERS_PATH=C:/Users/james/AppData/Local/ms-playwright. The harness serves only a local trusted page/worker with connect-src none and closes browser/server on completion.
Current integration verification (2026-11-04): the real Chromium offline-worker check passes strict diagnostics, multi-file completion/hover/definition and formatting, with no source execution or external requests. Typecheck, lint and production worker packaging pass on the canonical integration branch. See [current status](QBIT-RELEASE-STATUS.md) for the final full-suite evidence. This frontend result does not qualify the server's untrusted-code runtime.
+61
View File
@@ -0,0 +1,61 @@
# Next UI issue tracking
GitHub issues are the source of truth for scope, current status, remaining work and release decisions. Start at [QRun-IO/qqq#713](https://github.com/QRun-IO/qqq/issues/713), which links the release baseline, known failures, visual review, external-provider checks and existing feature tickets.
## Where work lives
| Area | Authoritative issue |
|---|---|
| Roadmap and complete issue index | [qqq#713](https://github.com/QRun-IO/qqq/issues/713) |
| Feature acceptance and default adoption | [qqq#649](https://github.com/QRun-IO/qqq/issues/649) |
| Behavior and metadata compatibility | [qqq#714](https://github.com/QRun-IO/qqq/issues/714) |
| Original Next visual review and owner approval | [qqq#711](https://github.com/QRun-IO/qqq/issues/711) |
| Browser failures and incomplete run evidence | [qqq#985](https://github.com/QRun-IO/qqq/issues/985) |
| Real application soak and final release hold | [qqq#712](https://github.com/QRun-IO/qqq/issues/712) |
| Cross-application launch/restart acceptance | [qqq#650](https://github.com/QRun-IO/qqq/issues/650) |
| ESB integration and acceptance | [qqq#986](https://github.com/QRun-IO/qqq/issues/986) |
| Paired runtime provenance and CI handoff | [qqq#905](https://github.com/QRun-IO/qqq/issues/905) |
Create **all Next UI tickets in [QRun-IO/qqq](https://github.com/QRun-IO/qqq/issues)** and add them to the [QQQ Roadmap, Project 12](https://github.com/orgs/QRun-IO/projects/12). Use existing issues for the same requirement or failure, and link each focused ticket to #713 and its feature or investigation parent. Frontend issues #12 and #10 were transferred to central issues #985 and #986; use the central identifiers and preserve their report history.
## Required ticket fields
Every new ticket needs these fields on the issue and its Project 12 item; complete them before work advances to **Ready**:
| Field | Required value |
|---|---|
| Issue Type | Native issue: Bug, Feature, Task or Epic. Mirror it in the Project field, using Planning Epic for Epic; Improvement is a Project-only category. |
| Assignees | The person accountable for completing or coordinating the ticket |
| Milestone | The agreed delivery milestone; keep it consistent with the target version |
| Priority | Critical, High, Medium or Low, with an impact-based rationale in the issue |
| Component | The owning implementation component: `qqq-frontend-next` for Next source, or the actual backend/sample component for a dependency. |
| Target Version | The agreed version of the owning component or an explicit pending release decision. A backend dependency in the Next UI 1.0 milestone does not imply a backend 1.0 release. Never imply delivery in an already published RC. |
| Status | The current workflow state below |
Record unresolved ownership or release decisions explicitly while a ticket remains in **Backlog**. Do not invent dates, estimates or a new release version to fill a field. A browser assertion failure alone does not establish high product severity.
## Workflow and evidence
- **Backlog / Ready:** capture the expected behavior, scope, acceptance criteria, required fields and dependencies; Ready means actionable work with an owner.
- **In progress:** link the active branch or work and record reproduction, confirmed cause, implementation progress and remaining blockers.
- **In review:** link the reviewable PR, source commit and verification evidence; keep unmet acceptance criteria visible. A local fix or passing subset does not imply merge or release.
- **Done:** close only after the accepted fix is integrated and every acceptance criterion is verified, or an explicit approved disposition is recorded. Link the merged PR/commit and evidence. Identify whether the change is unreleased or delivered, with the version and release link when published.
## Updating an issue
- Put current status and remaining acceptance criteria at the top. Preserve the original report as history.
- Record expected/actual behavior, reproduction, environment, source commit, run and artifact evidence. State whether the cause is established.
- Separate implementation evidence, passing scoped tests, owner visual approval and final release approval. They are distinct requirements.
- Resolve all original acceptance criteria before closing, or document an explicit approved disposition. A later passing run alone does not explain an intermittent failure.
- Keep external-provider exclusions visible. Controlled provider fixtures do not prove a real Auth0 tenant, Google account or QuickSight dashboard.
- Update issue links after a fix or new finding. Repository plans, session notes, release notes and test/visual ledgers support the issues; they are not competing live backlogs.
## Release and design constraints
Preserve the original Next design at `e42ad2b2bcdc76311e13002a22dd70e3ec437192`. Material Dashboard is the behavior and metadata compatibility reference. Prioritize primary-browser functional depth and disclose secondary-browser failures.
[RC9](releases/1.0.0-RC.9.md) is the published prerelease for application testing. Its immutable-tag acceptance run has known Firefox/tablet failures; consult the release notes and #985 for the exact evidence. The browser fixes in [PR #18](https://github.com/QRun-IO/qqq-frontend-next/pull/18) are [unreleased](releases/unreleased.md).
Every release and changelog entry must link its central QQQ issues and distinguish product changes, test/infrastructure corrections, verification and unresolved work. A published RC's tag and artifacts remain immutable; subsequent fixes require a separately authorized version and release. Update tickets and Project 12 with the actual delivery version and release link after publication.
Final 1.0 remains held for the owner's requested application soak and renewed, explicit final approval ([#712](https://github.com/QRun-IO/qqq/issues/712)). Passing CI, an issue marked Done or elapsed time does not release that hold. This project does not authorize releasing QQQ 4.1.
+47
View File
@@ -0,0 +1,47 @@
# PDF preview boundary
The generic file lightbox uses pinned Mozilla `pdfjs-dist` **6.4.299**, loaded only for
PDFs, and a locally packaged worker. It no longer embeds uploaded PDF responses in an
application-origin iframe or offers a PDF "Open original" navigation. Downloads retain
the existing owner authorization callback.
Retained attachment previews reach it only after the tracked-download helper verifies
server receipt, byte length and SHA-256, and assigns `application/pdf` from the fixed
PDF signature. The generic renderer accepts local same-origin blob URLs or native
file endpoints at the exact configured API origin and base path. Both dashboard hosts must permit `blob:` in CSP `connect-src` so the verified local bytes can be read; the current Javalin and standalone defaults include it. It never forwards API
credentials to arbitrary external URLs. Uploaded MIME labels alone are insufficient.
The renderer supplies bytes (not a document URL) to an explicitly created parser
Worker, preventing the library's fake-worker fallback. It creates a canvas and an
optional plain-text view only: no PDF annotation/action, form, XFA, scripting or HTML
viewer layer. Auxiliary resource requests are rejected, worker fetching and WASM are
disabled, and no document-selected external URL is followed. Missing auxiliary fonts
or unsupported resources can limit fidelity; the retained original is downloadable.
Limits: 20 MiB input, 1000 pages, one visible page at a time, 8 million canvas pixels,
8192 pixels per edge, 30 seconds to load/parse, 15 seconds per page render/text read.
Text is streamed and stopped at 10,000 items or 100,000 characters, slicing each
string before aggregation. The stream is cancelled on page change, close or timeout;
page controls remain busy through text completion.
Closing the dialog aborts input loading, cancels rendering, destroys the loading task
and terminates the worker. These are application limits, not a browser-process memory
quota: compressed PDF complexity can allocate parser memory before page checks.
Canvas rendering and a Web Worker do **not** constitute a full hostile-parser security
sandbox. Browser/library patching, CSP and deployment qualification remain necessary.
Primary references checked on 2026-10-30:
- [Mozilla release 6.4.299](https://github.com/mozilla/pdf.js/releases/tag/v6.4.299)
- [Mozilla PDF.js API](https://mozilla.github.io/pdf.js/api/draft/module-pdfjsLib.html)
- [Mozilla prior arbitrary-JavaScript advisory](https://github.com/mozilla/pdf.js/security/advisories/GHSA-wgrm-67xf-hhpq)
The prior advisory affected versions through 4.1.392 and was fixed in 4.2.67. The current
6.4.299 API no longer exposes the old `isEvalSupported` option; this integration does
not pretend an obsolete option supplies isolation. It uses the pinned current release,
no PDF scripting manager, and the application's restrictive script CSP.
## Current verification (2026-11-04)
The production export and local Maven JAR include the pinned worker. Twelve Chromium browser scenarios pass against that packaged UI, including actual PDF worker/canvas pixels, page navigation, no executed PDF OpenAction, separate retained preview/download receipts, refusal of incomplete receipts, and ordered-file submission. Real Core/Collaboration API sessions separately pass ordinary signed-PDF canvas preview, page controls and PDF download.
Signed attachment PDFs are read with credentials omitted and redirects refused, bounded to 20 MiB and 30 seconds, and checked by byte signature before a local preview URL is exposed. Their storage origin needs host CSP/CORS configuration. Tracked deliveries retain the receipt/hash path. Cancellation and streaming-limit tests cover both paths. See [current status](QBIT-RELEASE-STATUS.md) for the full suite and [release requirements](QBIT-KNOWN-ISSUES.md) for remaining deployment qualifications.
+22
View File
@@ -0,0 +1,22 @@
# Plan: API playground contrast (#724)
## Reproduction and scope
Fresh, fingerprinted desktop captures preserve the original Next composition. RapiDoc's Expand all text in both the original design and RC6 renders RGB(29, 78, 216) on RGB(42, 43, 44), a contrast ratio of 2.12:1 at 16px normal weight. This inherited defect fails the 4.5:1 requirement. Browser regression checks also found light-mode contact-link contrast of 2.36:1 and authentication-status contrast of 3.00:1.
## Implementation and verification
- [x] Retain failing browser contrast checks inside RapiDoc's shadow DOM.
- [x] Apply existing dashboard primary-text and error tokens at the component boundary. Pair dark accent backgrounds with inverse button text. Preserve layout and dependencies.
- [x] Verify normal and hovered controls in light/dark Chromium desktop and mobile, plus live Preferences theme switching.
- [x] Run types, lint, production export, all 1,901 unit tests and existing bundle budgets.
- [x] Directly inspect four fresh screenshots after color transitions settle.
- [x] Independent review approved with no remaining findings; prepare signed isolated commit.
Evidence: `test-results/visual-review/api-playground-contrast/` in the original checkout. Final focused report: four passed, zero failures, skips, flaky outcomes or report errors. These are targeted checks, not a complete acceptance gate. Initial toggle verification incorrectly asserted an HTML attribute: React 19 sets RapiDoc's non-reflected Lit property. A subsequent audit sampled active color transitions. Both runs are retained; the corrected test awaits the property, Lit render and actual shadow-root animations without fixed sleeps.
## Remaining limits
This proves the fixture's visible playground content, hover states and theme switching, not all expanded endpoint/authentication states or arbitrary custom palettes. The existing narrow-screen inner horizontal clipping remains open; this color correction does not redesign the playground.
This branch is based on RC6 source `7ddf45f` and excludes the separate TIME fix `276b65b`. Neither follow-up is part of the immutable RC6 tag. Final 1.0 remains on hold.
+31
View File
@@ -0,0 +1,31 @@
# PLAN: Next UI 1.0 query filter parity
## Goal
Complete the supported Material Dashboard query-filter workflows in QRun-IO/qqq#715 and #718, and prove them with real-backend acceptance rows.
## Approach
Build on the preserved wave 2 worktree. Keep one `QQueryFilter` as the source of truth for basic and advanced modes, with the existing `useRecordQuery` state and saved-view serialization. Share the existing metadata-driven operator and value controls between quick-filter chips and the advanced builder. Use the v1 metadata settings from backend PR #787 for weekday options and help.
## Files affected
- `src/components/query/RecordQuery.tsx`, `RecordQueryToolbar.tsx`: mode switch, quick filters, sort, clear confirmation, advanced preview.
- `src/components/query/QuickFilterBar.tsx`: basic-mode chips, field picker, operator/value editor and accessible clear/remove actions.
- `src/components/query/FilterBuilder.tsx` and its extracted helpers: complete advanced operator and value behavior from #718.
- `src/lib/utils/quick-filter-utils.ts`, `filter-utils.ts`, `saved-view-utils.ts`, `src/lib/hooks/use-record-query.ts`: metadata defaults, criteria edits, mode reconciliation, persistence and view round-trips.
- `tests/acceptance/matrix/query.json` and `tests/acceptance/specs/query/`: real-backend evidence for every applicable #715/#718 behavior.
## Steps
1. [x] Add meaningful unit tests for quick-filter defaults, complex-filter rejection, edits, weekday operators, date expressions and saved-view round-trips; finish any missing utility behavior.
2. [x] Wire instance filter settings and add the basic/advanced switch with a reason tooltip when basic cannot represent the current filter.
3. [x] Build and test basic quick-filter chips, add-field menu, operator/value editing, clear/remove behavior and advanced preview.
4. [x] Add sort picker, clear-all confirmation that preserves sort, and per-row validation; cover keyboard and touch behavior.
5. [ ] Add real-backend acceptance rows and tests, then run typecheck, lint, unit, static-export budget and the five-project browser gate after integration.
## Constraints
- Render field labels from metadata and preserve readable join and virtual-field behavior.
- Keep the filters and saved views interoperable with Material Dashboard JSON.
- Preserve unrelated wave 2 worktrees. Do not release QQQ 4.1.
+11
View File
@@ -0,0 +1,11 @@
# Script completion compatibility
Goal: close the autocomplete gap in the parity ledger while retaining the original Next textarea editor and its keyboard accessibility. Authorized by the active Next UI 1.0 completion goal.
- Preserve syntax overlay, split panes, per-file language, undo, read-only behavior and Tab escape.
- Opt script editors into a caret-adjacent suggestion list: QQQ API/logger methods, current-file identifiers, and highlighter language keywords. No new dependency or backend contract.
- Typing a prefix or Ctrl+Space opens suggestions; arrows select, Enter/Tab insert, Escape dismisses; pointer selection retains textarea focus. Composition input must not be intercepted.
- Replace the current token at the caret, preserve surrounding text, and avoid duplicating an existing call parenthesis.
- Verify unit regressions, real-backend script edit/test/save/undo behavior, focus/keyboard and visual states. Update guides and ledger only to the extent proven.
Verified: baseline Chromium 95847c2 passed 556 tests; backend PR #913 merged. Autocomplete passes 1,840 unit tests, types, affected lint, production export and bundle budgets; 15 Chromium developer/script workflows and 10 focused cases across all five profiles. Both review findings fixed after failing regressions. A native textarea probe explained WebKit undo grouping; completion now has a loaded-revision undo/redo test without changing native history behavior. Light/dark screenshots inspected; whole-family visual review remains open.
+17
View File
@@ -0,0 +1,17 @@
# Sidebar icon label assertion (#728)
## Evidence
RC6 candidate WebKit failed NAV-002 because raw link text included decorative legacy icon codepoints. The same assertion was reproduced against the public RC6 JAR after explicitly awaiting the fixture's legacy glyph. This is a test assertion defect: accessible link names should match metadata labels, while decorative SVG text remains hidden from assistive technology.
## Correction
Await the legacy glyph, then assert exact link count and each accessible label in backend order. Check that rendered legacy SVGs remain `aria-hidden`. Retain nested-app, parent expansion and child label assertions. No product code or release artifact changes.
## Verification
Retain red and focused Chromium/WebKit/mobile reports under `test-results/visual-review/sidebar-icon-labels/` in the original checkout. The focused check exercises the actual public RC6 JAR with backend `e0d57dd`. Targeted ESLint and diff checks are required; independent review precedes integration. Original CI failure remains recorded, not erased by a later focused pass.
Focused result: three passed, zero failures, skips, flaky outcomes or report errors. Targeted ESLint and diff checks passed.
Independent review approved with no findings. This change verifies navigation semantics; glyph appearance remains covered by the separate legacy-icon workflows.
+10
View File
@@ -0,0 +1,10 @@
# Plan: TIME block seconds compatibility (#728)
Preserve Next layout while making standalone and process TIME inputs accept and submit seconds, matching the shared form editor and QQQ TIME values.
1. Reproduce native step validation failure with standalone and process tests; retain a regular form control.
2. Apply the existing one-second input step to both plain block paths.
3. Verify edited/seeded values through actual Chromium workflows, including backend process readback; run affected tests and relevant quality gates.
4. Keep this follow-up separate from the unchanged RC6 candidate while its hosted CI runs.
Verification: two focused component regressions failed before the fix (regular form control passed); three real-backend Chromium scenarios failed at native step assertions. After the two-line correction, all 66 affected component tests and eight desktop/mobile backend workflows passed, including independent SQL checks. Full unit suite, type/lint, production build and budgets passed. This is isolated follow-up work, not part of RC6 candidate `7ddf45f`.
+13
View File
@@ -0,0 +1,13 @@
# Metadata-driven process result fields
`ViewFormComponent` delegates adorned result fields to the shared `FieldValue` component. Hidden fields remain in the result value map so native metadata can supply display companions; they are not rendered as separate controls. Ordinary process values and validation errors keep their existing behavior.
`LINK` supports either `toRecordFromTable` (a static table name) or `toRecordFromTableField` (the name of another result field containing the table name). The field value is the record identity. Navigation uses the native encoded table/record route. A missing companion table produces no link. Possible-value sources that identify a known instance table also retain native record navigation.
`FILE_DOWNLOAD` can specify `fileNameField` and `deliveryModeField`. If the latter is present, its companion value must be `SIGNED_URL` or `AUTHENTICATED_FETCH`; missing or unknown modes render no access action. Older fields without that metadata retain their existing behavior.
For `AUTHENTICATED_FETCH`, the issued URL is never placed in an `href`, image source or iframe. Download uses the shared authenticated transfer helper, which checks the configured origin, response bounds, exact digest/length and completed durable receipt. An additional `FILE_PREVIEW` adornment enables an explicit preview action. Only verified PDF/raster blobs reach the shared preview component; component-owned blob URLs are revoked when closed or unmounted. Unsupported content and transfer/audit errors remain visible and never fall back to direct access.
`CODE_EDITOR` result fields use the shared read-only source display, including renderer-supplied plain catalog help and HTML/JSON examples. No template or script evaluation occurs in a process result.
Verification for this increment: 178 focused tests across 18 files, owned ESLint and the explicit build-compiler typecheck passed. Each actual transfer creates a fresh correlation UUID only after validating the configured endpoint, preserving the immutable file coordinate and using that same fresh attempt for its receipt. Source changes and unmount abort active body/receipt work. Final integrated production-build/browser acceptance remains pending. These generic components do not embed a QBit name or depend on its backend implementation.
+106
View File
@@ -0,0 +1,106 @@
# QA capture (Jam)
Optional QA bug capture for the Next UI. The backend is the `qbit-qa-capture` QBit, which publishes the `qa-capture` UI API extension (1.0.0). [Jam](https://jam.dev) is the first provider. The UI calls only the two operations in [`src/lib/api/openapi/extensions/qa-capture.json`](../src/lib/api/openapi/extensions/qa-capture.json):
| Operation | Call | Used for |
|---|---|---|
| `qaCaptureCapability` | `GET /qqq/v1/qbit/qa-capture/capability` | Whether capture is on for the signed-in user, and the Jam team, sanitizer rules, blur selectors and backend context |
| `qaCaptureReportCreate` | `POST /qqq/v1/qbit/qa-capture/report` | "Report a bug": creates a report and returns the Recording Link `jam-*` parameters |
The vendored document is hand-written from the shared contract until the QBit publishes its generated `openapi/qqq-v1/qa-capture.yaml`. Re-vendor it from the QBit before release.
## When anything loads
Capture is off unless all of the following hold. When it is off, the UI loads no Jam chunk, adds no Jam meta tags and makes no Jam request:
1. `GET /qqq/v1/version` lists the `qa-capture` extension. Otherwise the capability is never requested.
2. The user is signed in. `QaCaptureLoader` in the root providers then requests the capability once (TanStack Query, `['qqq', 'qa-capture', 'capability']`).
3. The capability has `enabled: true`, provider `jam`, a team id and sanitizer rules. Anything else counts as disabled.
When capture is enabled, `src/lib/qa-capture/jam-loader.ts` (the only module that imports Jam, and only through `import()`) does the following:
- adds `<meta name="jam:team">` and `<meta name="jam:blur">` (the server's `blurSelectors` plus `[data-qqq-secure]`);
- dynamic-imports `@jam.dev/recording-links/sdk` (0.4.0) and `@jam.dev/sdk` (1.0.1);
- calls `jam.initialize({ teamId, blurSelectors, network, console })` once, with the sanitizers;
- registers `jam.metadata(...)`.
A Vitest check fails if any other file imports Jam at runtime.
What the SDKs fetch was checked in their published source. `initialize` loads `https://js.jam.dev/recorder.js` only in two cases: the page URL has `jam-recording` (checked on load and on every `pushState`, `replaceState` or `popstate`), or `loadRecorder()` is called. It loads `https://js.jam.dev/capture.js` only while some tab has an active recorder, which it tracks with the `jam:numRecorders` localStorage counter. `jam.metadata` only stores a callback on `window.__jam__`.
The browser cannot undo the Jam SDK, so turning capture off for a user (QA mode off) takes effect on the next page load. Request-id collection stops immediately.
## QA mode
When `capability.qaMode.preferenceSection` is set, the Preferences page renders that section like any other. Saving it refetches the capability, so turning QA mode on loads Jam without a reload. While `reason` is `QA_MODE`, the header shows a "QA mode on" badge that links to the section.
## Report a bug
The user menu shows **Report a bug** when `canReport` is true, and **QA reports** when `reportsHref` is an in-app path. The UI follows that link as given and never builds table paths itself. The Next UI has no help menu (the header help button opens the keyboard shortcuts), so both entries are in the user menu only.
Report a bug sends `POST /report` with:
- the page URL, with the sanitizer `queryParamNames`, the OAuth `code`, `state` and token parameters, and every `jam-*` parameter removed;
- the title `Bug: <page title>`;
- the app, table, record id or process, resolved from `/app/<slug>[/<id>[/<process>]]` against the instance metadata;
- the last request ids;
- the frontend version, user agent and viewport.
When the response has `recordingParams`, the UI opens the recorder with `await jam.loadRecorder({ openImmediately: false })` and then `Recorder.open(params['jam-recording'], { jamParams: { 'jam-title', 'jam-reference', ... } })`. The SDK puts those parameters on the URL while recording and removes them when the recorder closes, which lets the QBit's `jam.created` webhook match the capture to the report by `jam-reference`.
Without `recordingParams`, a toast explains that an administrator must set the Recording Link in the QA capture settings. It includes a QA reports action when `reportsHref` is present.
## Sanitizers
`src/lib/qa-capture/sanitizers.ts` builds the four Jam sanitizers from `capability.sanitizer`. They are pure, synchronous and deterministic, and they never log or fetch. The server's rules always get a baseline added: the `authorization`, `proxy-authorization`, `cookie` and `set-cookie` headers; the `password`, `sessionUUID`, `accessToken`, `refreshToken`, `idToken`, `clientSecret` and `code_verifier` fields; the `code`, `state`, `access_token` and `id_token` parameters; and JWTs.
| Data | Rule |
|---|---|
| Request and response headers | `headerNames` (lowercase) are deleted |
| JSON bodies | values of `bodyFieldNames` keys are replaced at any depth (case-insensitive); `valuePatterns` are applied to every string; the body stays valid JSON |
| Form-encoded bodies | the same, field by field |
| Other text bodies, WebSocket data | `valuePatterns` |
| Request URLs | `queryParamNames` values are redacted; `valuePatterns` are applied |
| Same-origin paths under `dropPathPrefixes` | request and response bodies are dropped |
| Console arguments | object keys and string patterns as for JSON |
A `valuePatterns` entry that is not a valid RegExp makes the affected sanitizers throw. Jam then redacts that data instead of sending it (fail closed).
## Secure fields
The UI knows a value is secure from its field metadata:
- the field type is `PASSWORD`;
- the field has the `REVEAL` adornment;
- secured data restricts the field (`accessTier` is `MASKED` or `REVEALABLE`).
Elements that show or edit such a value carry `data-jam-blur` and `data-qqq-secure`. This covers `MaskedValue`, a revealed restricted value, grid password and reveal cells, record view reveal and password values, password inputs (including while shown as text) and secure text inputs. The attributes are always present and have no effect when capture is off.
## Request ids
While capture is enabled, an axios interceptor keeps the last 50 `{ id, method, path, status, at }` entries from the response header named by `capability.requestIdHeader` (default `X-Request-Id`). The path is stored without its query string. They go into the Jam metadata and the report. Disabling capture clears the buffer.
## Jam metadata
At capture time Jam receives the following, kept under 10 KB by dropping the oldest request ids first:
- the app, table, record id and process;
- the frontend version (`NEXT_PUBLIC_APP_VERSION`);
- the backend `environment`, `backendVersion` and `node` from the capability;
- the request ids;
- the report reference;
- the page path, filtered as above.
## Recording Links and sign-in
A Recording Link can open a protected page as `?jam-recording=...&jam-title=...`. The login redirect, the OIDC/OAuth2 callback and the 401 redirect already carry the query in `returnTo`. Every page load also stashes the `jam-*` parameters in sessionStorage. After sign-in they are put back on the URL with `history.replaceState`, unless the page already has a `jam-recording`. The stash expires after 30 minutes. The Jam SDK watches `replaceState`, so the recorder then opens.
The public `/qa/recorder` page for Jam domain verification is served by the QBit, not by Next. The standalone server forwards `/qa/*` to the backend.
## Content-Security-Policy
Next adds no CSP of its own. In a jar deployment the dashboard CSP comes from the QQQ server (`NextDashboardSecurityHeaders`), and the QBit or application must add the Jam origins listed in `capability.cspOrigins`. The standalone container server (`standalone/qqq-server.mjs`) adds them only when `QQQ_QA_CAPTURE_CSP=true`:
- `script-src https://js.jam.dev`
- `frame-src https://*.jam.dev`
- `connect-src https://*.jam.dev`
+11
View File
@@ -0,0 +1,11 @@
# QBit integration known issues and release requirements
Updated: 2026-11-05.
- The Core, Collaboration and Next UI changes are verified source branches and local snapshots, not a published dependency set. Public Core RC.21 / Collaboration RC.6 lack the required retention contracts. Merge and publication through the existing release process remain separate from local verification.
- The frontend checks include real Core/Collaboration sessions and file storage, plus browser protocol fixtures for retained receipts. They do not establish end-to-end document generation/printing or deployed untrusted-script execution. Those QBits and workers retain their own qualification requirements.
- External signed PDF storage needs CORS for the dashboard origin and its origin in CSP `connect-src`. Application credentials are never sent to those URLs. Hosts replacing dashboard CSP must retain `blob:` in `connect-src` for verified local previews.
- Lint reports six existing warnings in unchanged files. The unit suite has one existing skipped test. Canonical Next 16.4.1 and Zod 3 are preserved; dependency patch upgrades remain a separate release review.
- The local artifact is `qqq-frontend-next:1.0.0-SNAPSHOT`, built with an explicit Maven revision override. It includes generated PDF and TypeScript workers. The prepared release-managed version is `1.0.0-RC.38`; RC.36 and RC.37 already exist in Central and must never be overwritten.
- Next publisher support and the shared producer are merged (Praetor PR238, platform-ci PR67); the local caller pins327e9b3a9d45a08665fa9468f0f9741e972d8e3e. Runtime qualification passed736 Linux tests and its vulnerability scan. GitOps PR269 deployed the qualified runtime and exact workflow/GAV enrollment; ArgoCD adoption and the scheduled publisher startup are verified. The signed application tag and resulting Central artifact still remain. The existing tag, package.json and POM version agreement, typecheck/lint/tests/static export and signed Central delivery are preserved. Neither the local JAR nor source merge proves publication.
- The full application acceptance matrix across all browsers and deployment-specific storage/printer/scanner behavior is not claimed by the scoped QBit integration checks.
+58
View File
@@ -0,0 +1,58 @@
# Next UI QBit integration status
Updated: 2026-11-05. Source integration is prepared; no published release or deployment is claimed.
Current main `090f5febeaacf3f9c39a22952e5c93c859cdbeb7` (permission editor and companion Tools workspace) is integrated. The latest merge passed all 110 affected Authentik/widget/process tests, typecheck and the copyright-header gate. Both branches supplied the same worker limit; the merged configuration retains one setting. Prior focused Tools/UI-API checks covered 61 cases. These are integration checks, not completed release CI.
Core PR1343 merged as7825261ab after full CI4482 passed; promotion PR1345 merged as1bef44cf and immutable publication4629 is running. Collaboration must consume the resulting published version. Next publisher support merged in Praetor PR238 and platform-ci PR67. GitOps PR269 merged as883e1ce3; ArgoCD adopted the exact configuration and a scheduled publisher is running image sha256:ea068b2a02147280cacedd095fbc55114daa9e3db72f1b0ca8511bc43bfd12c7 with the exact Next enrollment. Its 736 Linux tests and vulnerability scan passed. The dashboard candidate version is 1.0.0-RC.38; this source preparation is not a published release.
Candidate CI4602 passed 3,800 tests but hit the unchanged five-second timeout in two `RecordQuery` filter tests. These also timed out on an earlier baseline; the newer main baseline's Node job passed. Profiling the focused cases identified repeated whole-page accessibility lookups. Scoping those lookups to the existing filter controls and confirmation dialog reduced their sampled CPU cost from 387 to 261 samples; the same assertions, interactions and timeouts are preserved. All 17 tests in the affected file and focused lint pass after the change. Release CI for this correction remains required; these focused results do not replace it.
## Changes
The canonical Next UI integration renders file preview/download adornments, retained-transfer receipts, isolated TypeScript editing and generic process results through QQQ metadata and UIUX APIs. It adds no table-specific application routes. The completion pass fixes Windows static-export execution, local PDF reads under dashboard CSP, and ordinary signed-storage PDF previews. Signed storage reads omit credentials, refuse redirects and enforce byte/time limits before canvas rendering. Retained transfers preserve their receipt/hash checks. Tests now use four workers by default to avoid high-core host contention; no timeout or coverage threshold was relaxed.
## Verified dependencies
- Core `feat/standalone-scripting-api`, code commit `d384d1c753cb0ac2ee4d0f6ccd564a3e47488a1c`, locally installed as `4.1.0-SNAPSHOT`.
- Collaboration `feat/record-files-standalone`, `53bd8f00d8a180b0fa4eb6cd029dc31044b6d4b3`, locally installed as `0.1.0-SNAPSHOT`.
- Next UI retains canonical Next 16.4.1, Zod 3 and existing dependencies. Local packaging explicitly overrides Maven revision to `1.0.0-SNAPSHOT`; it does not replace public RC.36.
## Completed verification
| Check | Result |
| --- | --- |
| Full `pnpm test:coverage` | 359 files; **3,784 passed**, 1 existing skip; all coverage gates passed |
| Coverage | 93.14% statements, 87.00% branches, 94.57% functions, 94.95% lines; API/utility sub-gates also passed |
| Lint and license headers | Zero errors, six existing warnings in unchanged files; Apache headers passed |
| Typecheck | Passed |
| Offline TypeScript worker in Chromium | Strict diagnostics, multi-file completion/hover/definition and formatting passed; no source execution or external fetch |
| Windows production static export | Passed using `pnpm build:export` |
| Local Maven snapshot JAR | Built successfully with PDF/TypeScript workers included |
| Browser file/process regressions | **12 passed** against the packaged production UI; API fixtures exercise PDF canvas, separate audit receipts, incomplete-receipt refusal and keyboard ordering |
| Live Core + Collaboration file workflow | Upload, thumbnail, preview, open, exact-byte download, rename/describe, reload and delete passed with synthetic data |
| Live PDF and authorization | Signed PDF canvas/page controls and download passed; another session was denied access to the parent record |
| Live UI API specification check | Bundled 1.8.0 compatible with running Core 1.10.0 |
The live tests used the existing disposable `CollaborationDevServer`, synthetic Alice/Bob sessions and the current UI JAR on loopback. No API mocks were used for those file/permission checks. The separate 12-case browser suite uses its existing API fixtures, not a deployed Record Files or scripting worker. Test-owned services and browsers were stopped after verification.
## Reproduce
```shell
pnpm install --frozen-lockfile
pnpm test:coverage
pnpm lint
pnpm typecheck
pnpm scripting:worker:check
pnpm build:export
mvn -B -Drevision=1.0.0-SNAPSHOT package
pnpm ui-api:check --server-url http://localhost:18098
```
Start the matching Collaboration demo using its README and place the resulting UI JAR first on its classpath. The live check used port 18098. The standard `pnpm test:e2e` owns its frontend fixture for the browser scenarios. Maven was invoked through the sibling Collaboration wrapper during this Windows run.
Local JAR SHA-256: `0626B33DF49569476E17FA080E2777A7AD9C7AF717CB75DFABF9EB40BE0A46A8`.
## Release boundary
See [known issues and release requirements](QBIT-KNOWN-ISSUES.md). Compatible public artifact publication, the complete application acceptance matrix and deployed document/script worker qualification are not implied by these source and local integration results.
+21
View File
@@ -0,0 +1,21 @@
# Record Files client integration
Updated: 2026-11-04. Current canonical integration branch: `feat/standalone-qbits-ui`. See [verification and dependencies](QBIT-RELEASE-STATUS.md).
## Standard controls and APIs
The Files tab uses standard themed buttons for Preview, Open, Download and Rename / describe. Navigation uses Next links with keyboard focus. Attachment metadata supplies independent `canEdit` and `canDelete` permissions; an explicitly denied edit capability never falls back to deletion permission. The server remains authoritative.
Every download requests a fresh owner-issued descriptor. `AUTHENTICATED_FETCH` deliveries use the configured native Record Files route, a new correlation identifier, and a separately verified completion receipt, byte count and SHA-256. Preview and save are separate attempts. A failed receipt never becomes a direct link or a successful save. Generic `FILE_DOWNLOAD` and `FILE_PREVIEW` adornments use the same controls; they do not require table-specific pages.
Tracked raster/PDF previews expose a local object URL only after verification. Ordinary signed PDF previews read bounded bytes with credentials omitted, redirects refused and a 30-second deadline. Both use the same canvas PDF renderer; uploaded PDFs are never framed. Closing the preview aborts work and revokes local URLs. Hosts serving signed files from another origin must allow that storage origin in CSP `connect-src` and configure storage CORS for the dashboard origin. This never forwards QQQ credentials to storage.
Open previews supported media; unsupported types use the authorized download action. The UI does not claim that a browser saved bytes or that a printer physically printed them. Retention, upload sessions, collections and durable audits belong to their server QBits.
## Dependency order
Install matching Core, then Collaboration, then Record Files and other feature QBits before the matching frontend. Public Core RC.21 and Collaboration RC.6 do not contain the new retention contracts. Local verification uses Core `4.1.0-SNAPSHOT` and Collaboration `0.1.0-SNAPSHOT`; see their branch status files for exact source commits. Core's dashboard CSP must include `blob:` in `connect-src` for local PDF-byte reads.
## Verification scope
The full component/API suite and coverage gates, production export, browser PDF/receipt/ordering scenarios and live Collaboration file workflow are recorded in [current status](QBIT-RELEASE-STATUS.md). Live tests use disposable synthetic data and real Core/Collaboration APIs. Browser mocks of the retained-transfer protocol do not establish production storage or worker qualification.
+1 -1
View File
@@ -15,7 +15,7 @@ import type { QInstance, QTableMetaData, QRecord, QQueryFilter } from '@/types'
| Type | Source file | Purpose |
|------|-------------|---------|
| `QInstance` | `metadata.ts` | Top-level descriptor returned by `/metaData/instance`; the single source of truth for all apps, tables, processes, reports, widgets, and branding. **Key fields:** `apps`, `tables`, `processes`, `widgets`, `branding`, `appTree`, `environmentValues` |
| `QAuthenticationMetaData` | `metadata.ts` | Describes the auth provider type (`AUTH_0`, `OAUTH2`, `FULLY_ANONYMOUS`, `MOCK`) and the provider-specific values (client ID, base URL, audience) needed to boot the correct auth flow. |
| `QAuthenticationMetaData` | `metadata.ts` | Describes the auth provider type (`AUTH_0`, `OAUTH2`, `TABLE_BASED`, `FULLY_ANONYMOUS`, `MOCK`), the provider-specific values (client ID, base URL, audience) needed to boot the correct auth flow, and the pre-sign-in `branding` (`QLoginBranding`) the login page shows. |
| `QBrandingMetaData` | `metadata.ts` | Controls visual identity: company name, app name, logo URL, accent color, notification banners, and custom CSS injected via `data-qqq-id` selectors. |
| `QThemeMetaData` | `metadata.ts` | Backend-supplied Tailwind CSS token overrides (`primaryColor`, `accentColor`, `mode`). Applied on top of the default theme by the theme provider. |
| `Banner` | `metadata.ts` | A single notification banner (text, severity `info|warning|error`, optional color, dismissible flag) rendered at the top of the dashboard layout. |
+17
View File
@@ -0,0 +1,17 @@
# UI API 1.6 adoption
Adopts QQQ UI API 1.6.0 (QQQ 4.1.0-RC.15): table clientCapabilities, field validation, BasicErrorResponseV1, /qqq/v1/branding.
- Vendors the supplied core, api-aware, ESB, alerts, collaboration, data-analysis and EDI documents; regenerates EDI types. Assistant, quick-search, secured-data and sql-workbench documents are unchanged.
- Saved views, audits, sharing and bulk actions use per-user table capabilities. Legacy discovery is used only when `clientCapabilities` is absent; a missing or unavailable feature in a present capability object does not enable legacy access.
- Forms use `field.validation` for required values, length policies, inclusive/exclusive bounds and whole-value patterns. Existing record-write error mapping remains intact.
- Errors prefer `message` over legacy error fields. Global error toasts and process error details expose the trace identifier. Session-expiration handling remains in place.
- Standalone branding aliases call `/qqq/v1/branding/{role}` and retry the old public route only after a 404. Instance consumers prefer `supplementalInstanceMetaData.uiSettings`, with deprecated settings as a fallback.
The app version is unchanged. No commit, push, PR or deployment was made.
## Verification
The supplied server specification comparison passes at 1.6.0. `OFF_SPEC_CALLS` remains empty and `OFF_SPEC_CEILING` remains 0.
Vitest startup is blocked by this session's Windows sandbox (`spawn EPERM` launching esbuild), so no Vitest pass count is claimed. The production bundle compiles, but `pnpm build` stops at its TypeScript child-process launch with `spawn EPERM`; no completed build or bundle-budget delta is available. Standalone `pnpm typecheck` passes. `pnpm lint` passes with six pre-existing warnings. Twelve direct Node smoke checks pass: seven lossless JSON vendoring comparisons and five branding status/fallback cases. The focused contract/extension Vitest invocation and full `pnpm test` both stop before running tests.
+272 -58
View File
@@ -1,93 +1,244 @@
# Browser matrix — real-backend acceptance (QRun-IO/qqq#649)
# Browser matrix — real-backend acceptance (QRun-IO/qqq#649, #708)
> **Current status lives in GitHub:** [Next UI 1.0 roadmap](https://github.com/QRun-IO/qqq/issues/713). This document records scoped evidence and historical checkpoints; older open/closed wording and summary counts do not override current issue status. See [tracking policy](../ISSUE-TRACKING.md). Final 1.0 remains held for owner testing and explicit approval.
## CI layout
`plan` and `build` run concurrently. The build produces one static export, dashboard
JAR and sample backend; the sample and middleware API JARs plus `out/` are shared
with all shards. Each shard has its own backend, one worker and zero retries.
The dependency graph is `plan + build → shards → acceptance gate`, with an
independent, non-blocking `quarantine` job after `plan + build` in full mode.
| Trigger | Chromium | Firefox | WebKit | Mobile | Tablet | Selection |
| --- | --- | --- | --- | --- | --- | --- |
| PR | 2 | — | — | 2 | — | Changed areas plus landing/navigation/CRUD/query/dashboard smoke |
| main, `v*` tag, workflow dispatch | 3 | 3 | 4 | 4 | 4 | Entire suite, respecting touch tags |
| PR labeled `full-acceptance` | 3 | 3 | 4 | 4 | 4 | Same as full |
Counts are independent Playwright shards. Mobile and tablet are separate projects
and jobs. `scripts/acceptance-select.mjs` owns path selection: docs-only runs smoke;
shared infrastructure changes select all areas on the two PR projects. Label changes
trigger replanning. No change narrows full runs on main or tags except the explicit
quarantine below.
Require **`acceptance gate`** in branch protection. It always runs, merges blobs
into JSON and HTML, checks the matrix (`--partial` only for PR mode), and requires
all planned shards to succeed. Its `acceptance` artifact retains the merged report,
gate summary and failure attachments for 30 days. Full browser coverage cannot be
claimed from a partial PR run. The under-30-minute PR target needs hosted timing
validation; local syntax/unit checks do not measure it.
`WID-029` ([#93](https://github.com/QRun-IO/qqq-frontend-next/issues/93)) has
`@quarantine` tags and an entry in `tests/acceptance/quarantine.json`. The default
suite excludes it and reports it in `summary.quarantined`. Full runs execute it
separately on Chromium with `QQQ_ACCEPTANCE_QUARANTINE=only` and
`continue-on-error: true`, retaining `acceptance-quarantine` results. This is a
visible known failure, not evidence of a passing feature. Missing issue links or
unknown quarantine IDs fail the gate. See the [acceptance README](../../tests/acceptance/README.md)
for local commands, artifact contents and cache/retry behavior.
## September 30 — post-RC4 report and menu checkpoint
Local production export at `a8e4d3b`:583 Chromium tests and all391 required feature rows passed, zero failures/skips/flaky results, three approved external-service exclusions. This includes the uniform table backgrounds, record overview spacing and hidePreview compatibility fixes. Retained full report/gate: `report-preview-full-chromium/` in local visual-review evidence.
The subsequent column-menu height correction passes28 targeted Chromium/WebKit cases. The valid red run failed both320px-window cases and passed both720px cases; final checks cover bounds, keyboard/focus, Copy/sort, standalone query actions and variants. Hosted predecessor36739388053 WebKit remains failed578/1: Copy full query values was outside the viewport before variant switching. Its exact scenario passed locally before correction; hosted resolution is unproven. Other predecessor jobs remain pending at this checkpoint. These scoped results do not establish complete browser compatibility.
The acceptance suite runs against the owned sample backend and the production static
export (javalin mode). The default gate runs Chromium only; the documented matrix is the
four configured Playwright projects below, in one run and one gate.
five configured Playwright projects below. CI shards each project separately and merges
their evidence into the single gate described above. Prioritize Chromium
workflow depth for RC1 and final 1.0; the complete final browser claim still requires all five projects.
## Verification order
First complete workflows in desktop Chromium against the real backend: normal defaults,
required and invalid inputs, dependent controls, edit/cancel, save/reopen, permission
boundaries, error recovery, and database or downloaded-file results. Expand browser and
viewport coverage after those behaviors work. Keep existing secondary results visible;
a passing row or test count does not establish that every path within a feature works.
For example, WID-073 checks report column totals and the exported value distribution
against SQL for the active filter, then verifies cancellation leaves saved data unchanged.
Opening the statistics dialog alone is insufficient evidence of that workflow.
## RC3 published checkpoint — 2026-09-29
Published source `f1a6a3a` / `v1.0.0-RC.3` passed **570 Chromium tests and all 391 required rows** in the [candidate run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36652168977), with zero failures, skips or flaky results. The tested PR merge has the same Git tree as the tag; backend commit was `cba758507dcc54cf282a6638ab50a7d3a1904c7c`.
The candidate run is complete: mobile **545 passed**; Firefox **569 passed, one failed** (WID-005: Widget Gallery heading timeout); WebKit **568 passed, two failed** (NAV-056: application-request access-control errors; QRY-030: density-menu timeout); tablet **543 passed, two failed** (QRY-030: density-menu timeout; REC-046: WebKit internal navigation error). No skips or flaky results. Reports and traces are retained; causes remain unproven. The overall candidate run failed. The [tag run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36654410984) passed **570 Chromium tests and all 391 required rows**. Tag WebKit passed **568 and failed two** (NAV-016 application-request access-control errors; QRY-030 density-menu timeout); tag Firefox passed **568 and failed two** (QRY-007 error toast not found; WID-073 Render Report action timeout). Tag mobile passed **545 tests**; tablet passed **543 and failed two** (NAV-026 widget-request access-control errors; QRY-030 density-menu timeout). The tag run is complete and failed overall, with zero skips or flaky results. Reports and traces are retained; no complete cross-browser pass is claimed.
The public Maven JAR passed signature/checksum checks, normal dependency resolution in a separate consumer, and seven affected Chromium workflows with independent database readback. Both image architectures are published; the actual arm64 image passed app-route and all 21 referenced JavaScript asset checks. See [RC3 release notes](../releases/1.0.0-RC.3.md) for immutable artifact checksums and limitations. Final 1.0 remains on hold.
## RC2 published checkpoint — 2026-09-29
Published source `0905cfb` / `v1.0.0-RC.2` passed **563 Chromium tests and all 390 required rows** in both its PR and tag runs. The [completed tag run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36642612560) also passed all **538 mobile tests**. WebKit passed 561 and failed QRY-030 (density menu) and RPT-012 (internal navigation error); Firefox passed 561 and failed PRC-061 (bulk-load default selection) and INT-011 (failed-save feedback); tablet passed 537 and failed QRY-030. No tests skipped or passed through retries. The overall run failed, and failure causes remain under investigation.
The earlier PR run had a different set of secondary-browser failures. Both sets remain recorded in the [RC2 release notes](../releases/1.0.0-RC.2.md); a passing later scenario does not establish why an earlier failure occurred. The published Maven JAR was independently downloaded, signature/checksum verified, resolved by a fresh consumer and exercised in five Chromium workflows. Both container architectures are published; the arm64 image passed runtime HTML/JavaScript checks. RC2 is immutable and final 1.0 remains on hold.
The preparation checkpoints below are historical results and do not supersede this published-artifact evidence.
## Post-RC1 checkpoint — 2026-09-29
The report capability candidate `2221999` passed **562 Chromium tests**, all 390 required rows, zero failures/skips/flaky tests in [CI](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36633453712). WebKit passed 561 with one QRY-030 failure: the density option did not appear after a trigger tap. Its trace is retained and the cause remains unproven. Firefox passed 560 with two failures: NAV-014 reported an owned embedded-page request failure during navigation; SEC-003 timed out awaiting its initial record row, before the create-restriction assertions. The final screenshot shows the row, and the trace shows a roughly 15-second delay before the query was sent; the cause remains unproven. Mobile passed all 537 tests; tablet passed 536 with one QRY-030 density-menu timeout matching the desktop WebKit symptom. The run is complete and failed overall. These results precede the child-export correction.
The subsequent `542ac47` local full Chromium run passed 562 and failed WID-046: the generic parent CSV export was hidden while child dropdowns awaited selection, despite the parent's independent CSV payload. The correction preserves the selection restriction for specialized child export and restores generic payload export. Its new regression was observed failing before the fix; 29 focused tests, all 1,857 unit tests, and the production build pass. The corrected export then passed **563 Chromium tests and all 390 required rows**, with zero failures/skips/flaky tests. A clean local JAR matches the tested export byte-for-byte. Exact-commit CI and published-artifact verification remain pending; this local result does not establish artifact availability.
The preceding d937507 [CI run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36626338487) passed **558 tests each in Chromium and WebKit**, all 390 required rows. Firefox passed **556 with two failures**: NAV-014 reported `NS_ERROR_FAILURE` for the owned embedded page during breadcrumb navigation; QRY-007 timed out hovering an application-theme error toast after it detached. Logs and traces are retained for diagnosis. All five focused breadcrumb/error-theme scenarios subsequently passed locally in Firefox against RC2 without source or test changes. That recheck does not establish the cause or resolution of the CI failures. Mobile passed 533; tablet passed 532 and failed REC-019 with a WebKit internal navigation error while opening a field-lab record. Its log and trace are retained. These results do not establish a complete cross-browser pass or erase the RC1 results below.
## RC1 checkpoint — 2026-09-29
Published candidate `e901df9` / `v1.0.0-RC.1`: Chromium **547 passed**, no failed/skipped/flaky tests, all 389 required rows passed. WebKit **546 passed, 1 failed** in NAV-034 legacy table-process redirect navigation, with an internal WebKit resource-loading error; trace retained for investigation. The completed Firefox run had 546 passes and one WID-021 widget visibility timeout; phone/tablet had 1,043 passes and one tablet SEC-002 internal WebKit navigation error. The separate tag run also had failures; see the release notes for both runs. [CI run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36601699322). This does not establish a complete cross-browser pass.
The actual Maven Central JAR additionally passed Sleep Interactive defaults/validation/completion and report-context filtered statistics/CSV checks. The published arm64 container passed a runtime/HTML smoke check; its manifest also includes amd64. See [RC1 release evidence](../releases/1.0.0-RC.1.md).
```bash
export QQQ_SAMPLE_JAR=/path/to/qqq-sample-project-<version>-jar-with-dependencies.jar
# full gate, all four projects, fresh static export
QQQ_ACCEPTANCE_BROWSERS=chromium,firefox,webkit,mobile node scripts/acceptance.mjs
# primary workflow gate, fresh static export
QQQ_ACCEPTANCE_BROWSERS=chromium node scripts/acceptance.mjs
# subsequent compatibility gate, all five projects, fresh static export
QQQ_ACCEPTANCE_BROWSERS=chromium,firefox,webkit,mobile,tablet node scripts/acceptance.mjs
# the same with explicit ports (use distinct ports for concurrent runs)
QQQ_ACCEPTANCE_BROWSERS=chromium,firefox,webkit,mobile \
QQQ_ACCEPTANCE_BROWSERS=chromium,firefox,webkit,mobile,tablet \
QQQ_ACCEPTANCE_BACKEND_PORT=18793 QQQ_ACCEPTANCE_FRONTEND_PORT=13793 \
node scripts/acceptance.mjs
# one project, reusing out/ (a filtered run is a partial gate)
QQQ_ACCEPTANCE_BROWSERS=mobile node scripts/acceptance.mjs --skip-build
# the touch projects only, reusing out/ (a filtered run is a partial gate)
QQQ_ACCEPTANCE_BROWSERS=mobile,tablet node scripts/acceptance.mjs --skip-build
```
| Project | Playwright device | Engine (Playwright 1.58.2) | Runs |
| Project | Playwright device | Engine (Playwright 1.64.0-alpha-2026-10-01) | Runs |
|----------|-------------------|----------------------------|------|
| chromium | Desktop Chrome | Chrome for Testing 145 | every spec |
| firefox | Desktop Firefox | Firefox 146 | every spec |
| webkit | Desktop Safari | WebKit 26.0 | every spec |
| mobile | Pixel 7 (412 px, touch) | Chromium | tests tagged `@mobile` |
| chromium | Desktop Chrome | Chrome for Testing 155 | every spec |
| firefox | Desktop Firefox | Firefox 156 | every spec |
| webkit | Desktop Safari | WebKit 26.6 | every spec |
| mobile | Pixel 7 (412 × 839, touch) | Chromium | tests tagged `@mobile` |
| tablet | iPad (gen 7) (810 × 1080, touch) | WebKit | tests tagged `@mobile` or `@tablet` |
## Phone scope
## Phone and tablet scope (QRun-IO/qqq#708)
Below 768 px the UI is a different layout by design: lists are card lists, record actions
live in an action sheet, navigation is a drawer. Specs that assert the desktop layout
(grid columns and cells, column resizing, grid keyboard navigation, desktop menus) do not
describe phone behavior, and skipping them is not allowed (skips fail the gate). The
`mobile` project therefore runs only tests whose title carries `@mobile`
(`grep: /@mobile/` in `tests/acceptance/playwright.config.ts`). Those tests run on the
desktop projects as well, either layout-neutral or pinned to a 412 px touch viewport, so
every phone scenario is also checked in Firefox and WebKit at phone width.
Below 768 px the UI uses a different layout by design:
The phone-relevant rows and their `@mobile` tests:
- lists are card lists;
- filters open in a bottom sheet;
- record sections are an accordion;
- record actions are in an action sheet;
- navigation is a modal drawer.
| Phone behavior | Tests |
|----------------|-------|
| Navigation drawer | NAV-026, INT-008 |
| List / card view, loading state | INT-008, INT-009 (busy placeholder cards, QRun-IO/qqq#694) |
| Record view | INT-008, INT-003 |
| Forms and validation | INT-008, INT-007, PRC-009 |
| Process run | PRC-001, PRC-004, PRC-009 |
| Dialogs and focus (action sheet, delete dialog) | INT-003 (QRun-IO/qqq#694) |
| Sign-in, logout, session expiry | SEC-020, SEC-021, SEC-022 (both) |
| Accessibility scan at phone width | INT-005 |
The tablet in portrait (810 px) gets the desktop layout (grid, sidebar, tabs) with a touch
pointer. Specs that assert the desktop layout (grid columns and cells, column resizing,
grid keyboard navigation) do not describe phone behavior, and skipping them is not
allowed: skips fail the gate. The touch projects therefore run only tagged tests:
- `mobile` runs tests whose title carries `@mobile`;
- `tablet` runs tests tagged `@mobile` or `@tablet` (`grep` in
`tests/acceptance/playwright.config.ts`).
Tagged tests also run on the desktop projects. They are either layout-neutral (helpers
`listCell`, `columnCells`, `listRows`, `navigation`, `recordList`, `openRecord` and
`recordAction` pick the grid or the cards, the tabs or the accordion) or pinned to a
412 px touch viewport, so every phone scenario is also checked in Firefox and WebKit at
phone width.
### Coverage rule, enforced by the gate
When a touch project runs, `scripts/acceptance-gate.mjs` requires every required row to
have a passing test in `mobile` and in `tablet`. The only exception is a row that records
why it is not a touch scenario (`"desktopOnly": "<reason>"`, which currently no row uses).
The result is in `summary.phone`: `covered`, `desktopOnly`, `uncovered`. The desktop-layout
variants that stay untagged (the grid-keyboard INT-001, the grid-loading INT-009, the
desktop REC-004 tab test) have phone counterparts for the same rows.
### Touch targets and layout checks
On a coarse pointer every control is at least 44 × 44 CSS px (WCAG 2.5.5). This is a
`@media (pointer: coarse)` rule in `src/styles/globals.css`, plus `pointer-coarse:`
classes in components. It covers:
- buttons, inputs, selects, tabs, menu items and options;
- a label wrapping a checkbox, radio or switch;
- breadcrumbs, sidebar entries, record links and widget block links.
Links in running text and data values keep their inline size. Mouse layouts are unchanged
at every width, because Playwright emulates `pointer: coarse` only when `hasTouch` is set.
`tests/acceptance/support/touch.ts` provides the checks:
- `expectNoHorizontalScroll`: the page never scrolls sideways; wide data scrolls inside
its own container;
- `expectTouchTargets` / `expectTouchReady`: no visible control below 44 px, applied only
under a coarse pointer.
The phone and tablet specs call these on the shell, lists, filter sheet, column
configuration, bulk bar, record view and forms, dialogs and sheets, every process screen
type, dashboards and reports.
Phone behaviors with their own rows and tests:
| Phone behavior | Rows |
|----------------|------|
| Navigation drawer: tap, backdrop, keyboard focus trap, Escape, focus return | NAV-026, NAV-037 |
| Shell on phones and tablets: header, breadcrumbs, sidebar, skip link, not-found | NAV-038, NAV-014 |
| Card list: grid columns in order, formatted values, selections, loading | QRY-008, INT-009 |
| Filter sheet: modal dialog, focus in and out, Escape, scrolls inside | QRY-009 |
| Column configuration by touch | QRY-024 |
| Bulk actions from a card selection | QRY-036 |
| Saved views and export menus on a phone | QRY-055 |
| Record header and phone action sheet | REC-058, INT-003 |
| Forms with every editor type on phones and tablets | REC-059, INT-008 |
| Tooltips and help by tap (fields, widget labels, widget help) | REC-036, REC-039, WID-043, WID-044 |
| Dashboards: one column below 1024 px, wide tables scroll in their card, dropdown/date/reload/export by tap | WID-022, WID-040, WID-019, WID-045–WID-048 |
| Process screens of every type touch-ready | PRC-001–PRC-047 |
| Sign-in, logout, session expiry; accessibility scan at phone width | SEC-020–SEC-023, INT-014, INT-005 |
| TABLE_BASED password sign-in, failure, expiry and logout | SEC-034–SEC-037 (QRun-IO/qqq#700) |
| Login page branding before sign-in | NAV-033 (QRun-IO/qqq#703) |
| Search dialog with record search | NAV-030 (QRun-IO/qqq#701) |
| Keyboard use on a phone (card, action sheet, edit, save) | INT-001 |
The gate also fails any configured project that ran no tests.
## Results
Recorded 2026-09-25 on macOS (Darwin 27), sample `4.1.0-SNAPSHOT` with the #649 backend
changes including QRun-IO/qqq#674 and #675, Next static export of
`feature/GH-649-next-acceptance` with all six areas integrated (navigation, records, query,
processes, widgets, security), one run of the full gate with the four projects:
Recorded 2026-09-26 on macOS (Darwin 27) for the Next UI 1.0 integration (QRun-IO/qqq#713):
the sample from the qqq `feature/next-1.0` backend (`a55c71a2d`, with the v1 supplemental
metadata, developer-mode and table-variant process routes of #714) and the Next static export
of `feature/next-1.0` (`e3bea6e`: the batch 1-3 streams, Material parity #714 and phone and
tablet coverage #708), built from a clean `.next`. One run of the full gate with the five
projects:
Tests passed / run, per spec area and project (the mobile project runs the `@mobile` tests only):
Tests passed / run, per spec area and project (the touch projects run the `@mobile` tests only):
| Area | chromium | firefox | webkit | mobile |
|---|---|---|---|---|
| navigation | 41/41 | 41/41 | 41/41 | 1/1 |
| processes | 50/50 | 50/50 | 50/50 | 3/3 |
| query | 50/50 | 50/50 | 50/50 | — |
| records | 48/48 | 48/48 | 48/48 | — |
| security | 57/57 | 57/57 | 57/57 | 9/9 |
| widgets | 80/80 | 80/80 | 80/80 | — |
| **total** | **326/326** | **326/326** | **326/326** | **13/13** |
| Area | chromium | firefox | webkit | mobile | tablet |
|---|---|---|---|---|---|
| navigation | 59/59 | 59/59 | 59/59 | 56/56 | 56/56 |
| performance | 5/5 | 5/5 | 5/5 | 5/5 | 5/5 |
| processes | 61/61 | 61/61 | 61/61 | 54/54 | 54/54 |
| query | 68/68 | 68/68 | 68/68 | 68/68 | 68/68 |
| records | 90/90 | 90/90 | 90/90 | 84/84 | 84/84 |
| security | 73/73 | 73/73 | 73/73 | 67/67 | 67/67 |
| widgets | 93/93 | 93/93 | 93/93 | 91/91 | 91/91 |
| **total** | **449/449** | **449/449** | **449/449** | **425/425** | **425/425** |
`—`: the area has no `@mobile` test; the phone list, record and form behavior is proven by
the interaction tests (INT-003, INT-007, INT-008, INT-009) in the security area.
Gate (`test-results/acceptance/gate.json`, 991 tests, 18.1 min, retries 0, commit `2c5eb2d`):
Gate (`test-results/acceptance/gate.json`, 2,197 tests, 44.3 min, retries 0, commit `e3bea6e`):
```json
{
"summary": { "passed": 289, "failed": 0, "missing": 0, "excluded": 3 },
"summary": { "passed": 347, "failed": 0, "missing": 0, "excluded": 3,
"phone": { "covered": 347, "desktopOnly": 0, "uncovered": 0 } },
"byProject": {
"chromium": { "passed": 326, "failed": 0, "skipped": 0, "flaky": 0 },
"firefox": { "passed": 326, "failed": 0, "skipped": 0, "flaky": 0 },
"webkit": { "passed": 326, "failed": 0, "skipped": 0, "flaky": 0 },
"mobile": { "passed": 13, "failed": 0, "skipped": 0, "flaky": 0 }
"chromium": { "passed": 449, "failed": 0, "skipped": 0, "flaky": 0 },
"firefox": { "passed": 449, "failed": 0, "skipped": 0, "flaky": 0 },
"webkit": { "passed": 449, "failed": 0, "skipped": 0, "flaky": 0 },
"mobile": { "passed": 425, "failed": 0, "skipped": 0, "flaky": 0 },
"tablet": { "passed": 425, "failed": 0, "skipped": 0, "flaky": 0 }
},
"problems": []
}
```
Every required row passes in every project that runs it. PRC-039, WID-033 and SEC-030 are
`required: false` pending approval and are reported as excluded.
Every required row passes in every desktop project and in both touch projects; no row is
`desktopOnly`. PRC-039, WID-033 and SEC-030 are `required: false` pending approval and are
reported as excluded.
## Differences found between engines
@@ -122,4 +273,67 @@ Fixed:
open, closes on Escape, and returns focus to its trigger, also when a dialog opened from
one of its items closes.
Open: none.
The fixes above describe earlier checkpoints. Open failures from the RC1 runs are recorded in the RC1 checkpoint and release notes; they have not been waived.
## RC4 candidate density touch correction
The recurring QRY-030 density-menu failure was reproduced in Linux WebKit using
Playwright 1.58.2's official Noble image. Instrumented events showed that touch
pointer-down opened the menu, then synthetic mouse focus returned to its trigger;
Radix treated that focus as outside the non-modal menu and dismissed it. macOS
WebKit did not reproduce this event sequence.
The RC4 candidate source applies the existing Selection/Views/Export trigger guard to
Density. It ignores outside interactions only when they target that same trigger;
other controls still dismiss the menu. No layout, dependency or timeout changed.
Both the existing density-selection test and the new touch/keyboard/outside-dismissal
regression passed with the selection-menu workflow: **nine checks across Linux
WebKit, tablet WebKit and Chromium, zero failures/skips/flaky results**. The new
regression failed before the fix. An initial added-test run used an incorrect
`textbox` locator for the searchbox; it was interrupted, retained, corrected and
rerun. The full unit suite passed 1,884 tests; production export, types, affected
lint and independent review also passed. The combined candidate at `f2cdf03` passed **577 Chromium tests and all 391
required rows**, with zero failures/skips/flaky results and three approved exclusions.
The preceding numeric-wheel correction separately passed its 576-test Chromium gate.
Both corrections were integrated before RC4 publication at
`f2cdf03`, superseding its earlier preparation checkpoint `1dbe1d9`. Published
RC3 does not contain them. Historical RC3/precursor failures above remain
valid evidence for those revisions; this focused result does not certify the whole
browser matrix.
## Completed precursor CI before RC4 integration
Run `36658766595` at `1283bac07e81741bfadc34bf7fe1c7805604d16f` is terminal.
Chromium passed 574 tests and all 391 required rows. Mobile passed 547; tablet
passed 544 and failed three: NAV-015 internal WebKit navigation, NAV-056 API
access-control errors, and QRY-030 density touch. Firefox passed 573 and failed
WID-024 waiting for the record heading. WebKit passed 573 and failed QRY-030.
All profiles had zero skips or flaky outcomes; general CI and image checks passed.
These results predate the report-editor, numeric-wheel and density corrections.
The Linux density regression is corrected and locally verified as described above;
other secondary failures remain open pending diagnosis or new candidate evidence.
Hosted checks for the integrated RC4 candidate and tag are complete; see the RC4 published checkpoint below.
## RC4 published checkpoint — 2026-09-30
Published source `3c409f9` / `v1.0.0-RC.4` passed **577 Chromium tests and all 391 required rows** in both [candidate](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36664932061) and [tag](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36714593674) acceptance. Both used backend `e0d57dd31b979b17215370546ef4e26cd8a38d21`; the candidate merge tree matches the tagged source.
Both acceptance runs completed and failed overall because of secondary-browser failures. All profiles had zero skips or flaky outcomes; failures were retained, not waived or silently retried.
| Browser | Candidate result | Tag result |
| --- | --- | --- |
| Chromium | 577 passed | 577 passed |
| Mobile | 547 passed, 1 failed: REC-054 script-suggestion listbox bounding-box timeout in dark mode | 548 passed |
| Firefox | 576 passed, 1 failed: WID-026 record-heading timeout | 576 passed, 1 failed: WID-019 dashboard-heading timeout |
| WebKit | 576 passed, 1 failed: REC-046 internal navigation error on a read-only user's copy route | 576 passed, 1 failed: REL-005 internal navigation error returning to the pet list |
| Tablet | 547 passed, 1 failed: NAV-026 widget-request access-control errors during drawer navigation | 547 passed, 1 failed: SEC-002 internal navigation error opening a denied ledger create route |
Density scenarios passed in every profile of both runs. The other failures remain open; a later pass does not erase the earlier failure or prove full browser compatibility.
The actual public Maven JAR passed signature/checksum verification, fresh-consumer dependency resolution and 17 affected real-backend Chromium workflows. The public ARM64 container served the application and all 21 referenced JavaScript assets. These checks establish artifact delivery and the stated workflow coverage, not full compatibility or visual approval. See [RC4 release notes](../releases/1.0.0-RC.4.md) for checksums and remaining requirements. Final 1.0 remains on hold for owner testing and renewed approval.
+5
View File
@@ -0,0 +1,5 @@
# ESB integration acceptance
The ten required [ESB matrix scenarios](../../tests/acceptance/matrix/esb.json) pass in Chromium, Firefox and WebKit: 30 cases with no failures, skips or retries. They exercise the stock sample’s embedded Artemis broker, table-event delivery into `syncPerson`, service/table/process views, permission denial, subscription browsing, trigger pause/resume/restart and confirmed dead-letter replay. A further 38 Chromium navigation, permission and table Developer-view cases, plus 12 security-variant and search cases, pass without skips or retries. The main and security fixtures use separate broker ports. These are focused ESB results; general Next browser/parity and final release acceptance remain tracked separately.
The backend integration fixes are [QQQ PR #980](https://github.com/QRun-IO/qqq/pull/980), based on `5bf02d54`: normal sample ESB registration, API documentation route isolation and nullable subscription/dead-letter pause state. Frontend verification passes 105 focused ESB tests, all 1,951 unit tests with coverage thresholds, type checking, the production static export and bundle budgets. The [acceptance README](../../tests/acceptance/README.md) gives the commands and fixture boundaries; [Next #10](https://github.com/QRun-IO/qqq-frontend-next/issues/10) and [QQQ #739](https://github.com/QRun-IO/qqq/issues/739) track delivery. RabbitMQ and broker management behavior are covered by the backend conformance suite; these browser tests use Artemis without an HTTP management endpoint, so unavailable broker actions remain hidden.
+426 -291
View File
@@ -1,331 +1,466 @@
# Next UI feature acceptance matrix
> **Current status lives in GitHub:** [Next UI 1.0 roadmap](https://github.com/QRun-IO/qqq/issues/713). This document records scoped evidence and historical checkpoints; older open/closed wording and summary counts do not override current issue status. See [tracking policy](../ISSUE-TRACKING.md). Final 1.0 remains held for owner testing and explicit approval.
Generated by `node scripts/acceptance-matrix-md.mjs` from `tests/acceptance/matrix/*.json` and the
acceptance gate results. Each row is a supported QQQ UI feature or use case with executable
real-backend acceptance coverage (QRun-IO/qqq#649). See `tests/acceptance/README.md` to run it.
Rows: 292. Required rows passing in the recorded runs: 289.
Rows: 394. Required rows passing in the recorded runs: 391.
Recorded runs: `test-results/acceptance/gate.json` (2026-09-25T20:52:12.358Z).
Recorded runs: `/Users/james.maes/Git.Local/QRunIO/qqq-frontend-next/test-results/visual-review/rc8-corrected-full-chromium/gate.json` (2026-10-01T01:03:05.420Z), `/Users/james.maes/Git.Local/QRunIO/qqq-frontend-next/test-results/visual-review/rc8-corrected-ci-chromium/gate.json` (2026-10-01T01:23:18.171Z).
Corrected RC8 source cd3d473: 602 local and 602 hosted Chromium cases passed, with zero failed/skipped/flaky cases or report errors. Hosted run 36798799502. Three approved external-service exclusions remain. Earlier failed runs are retained and described in docs/releases/1.0.0-RC.8.md; this matrix records the corrected primary runs only. Other browser profiles and whole-product compatibility/visual approval remain separate.
The additional ESB integration scenarios and their focused three-browser results are recorded in [ESB acceptance](esb.md). The historical full-suite counts above predate those ten new matrix rows.
## Navigation, application structure, branding and icons
| ID | Feature | Scenarios | Negative | Fixture | Result | Issues |
|---|---|---|---|---|---|---|
| NAV-001 | Direct link to a table list renders seeded rows | success | | prime-test-database.sql | pass (chromium, firefox, webkit) | |
| NAV-002 | Sidebar lists every permitted top-level app once, by label, in backend order, with nested apps only under their parent | admin sees Miscellaneous, Nav Empty App, Nav Level One, People App, Sample Widgets Dashboard, Sharing Demo in backend order; Greetings App appears only under People App (regression: it was duplicated at top level); group children use metadata labels | noPets persona: Pet and Pet Note are absent from the sidebar; backend returns 404 for their metadata and 403 for a query | stock sample apps + NavigationFixtures (Nav Level One, Nav Empty App); persona noPets | pass (chromium, firefox, webkit) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-003 | Apps nested deeper than two levels render as nested sidebar groups; deep links expand every enclosing group | expand three nested groups and open Nav Deep Item; grid rows match nav_deep_item; deep link to /app/navDeepItem expands all three groups and marks the item current | | NavigationFixtures: navLevelOne > navLevelTwo > navLevelThree > navDeepItem | pass (chromium, firefox, webkit) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-004 | Sidebar click navigation to app homes, tables and processes with active-entry highlighting and collapse/expand | Dashboard is current on /app; click app, table, process; URL, content and aria-current follow; collapse hides children, expand shows them | | stock sample (Miscellaneous app) | pass (chromium, firefox, webkit) | |
| NAV-005 | Declared icons (QIcon name, color, path) render on navigation surfaces | sidebar glyph per declared Material name, color applied, image path loads; app home entries and section headings show declared icons; command palette entries show declared icons | no declared icon renders the type fallback glyph | sample icons (stars, person, widgets, share, pets, local_shipping, badge) + NavigationFixtures (path icon, colored icon, inventory_2, inbox, layers) | pass (chromium, firefox, webkit) | [#538](https://github.com/QRun-IO/qqq/issues/538) |
| NAV-006 | Hidden tables and processes never appear in navigation; direct links to hidden objects the user may not access show not-found and the backend denies them | sidebar, app home, landing page and command palette omit City, Greet People, Simple Sleep and Nav Hidden Note | /app/city and /app/greet show the not-found state; backend returns 404 and 403 | sample city (hidden table), greet and simpleSleep (hidden processes); NavigationFixtures navHiddenNote (hidden, NOT_PROTECTED) | pass (chromium, firefox, webkit) | |
| NAV-007 | A hidden table the user may access opens by direct link (hidden is not denied) | /app/navHiddenNote lists nav_hidden_note rows; breadcrumbs show its enclosing apps | | NavigationFixtures navHiddenNote (isHidden, PermissionLevel.NOT_PROTECTED) | pass (chromium, firefox, webkit) | |
| NAV-008 | App home sections list processes (Actions), reports (Reports) and tables (Data) by label with icons, and each entry navigates | Miscellaneous: Actions Sleep Interactive, Simple Throw; Data Carrier, Field Lab, Pet Species (labels, not names); section heading shows its label and declared icon; clicking an entry opens the table or process | hidden or unpermitted section entries are omitted (navHiddenNote, noPets persona) | sample greetingsApp and miscellaneous sections | pass (chromium, firefox, webkit) | [#651](https://github.com/QRun-IO/qqq/issues/651) |
| NAV-009 | App home table entries show the backend record count | each Data entry reads 'N total records' equal to the SQL count | | sample carrier, fieldLab, petSpecies, person, pet, petNote rows | pass (chromium, firefox, webkit) | [#651](https://github.com/QRun-IO/qqq/issues/651) |
| NAV-010 | App homes of apps with child apps, only processes, or nothing render without errors | People App shows Clone People and the Greetings App child app (regression: crashed on missing widgets); Nav Level One lists its child app and navigates to it; Nav Empty App shows the empty state | viewer persona: People App shows only the Greetings App child app (no denied process) | sample peopleApp; NavigationFixtures navLevelOne, navEmptyApp; persona viewer | pass (chromium, firefox, webkit) | [#651](https://github.com/QRun-IO/qqq/issues/651) |
| NAV-011 | A dashboard app (widgets only) opens from navigation and shows one labelled container per permitted declared widget | sidebar click opens the dashboard; one widget region per declared widget label, in order | | sample SampleWidgetsDashboard (11 widgets) | pass (chromium, firefox, webkit) | |
| NAV-012 | Branding: sidebar logo (Material parity), app name, favicon from the icon, accent color, and partial branding accepted without warnings | sidebar shows /samples-logo.png with alt QQQ Sample, loaded, linking to the dashboard; favicon link points to /kr-icon.png; primary color variables equal the branding accent color; no 'schema validation' console warning for branding without companyName/companyUrl | | sample branding (appName, logo /samples-logo.png, icon /kr-icon.png) + NavigationFixtures accentColor #1d4ed8 | pass (chromium, firefox, webkit) | [#539](https://github.com/QRun-IO/qqq/issues/539) |
| NAV-013 | Branding banners render in their slots (top of site, top of body, side nav under logo) with text or sanitized HTML, severity and colors | each slot shows its message in the right place with severity and colors from metadata | unknown slots are not rendered | NavigationFixtures banners: QFMD_TOP_OF_SITE (INFO text), QFMD_TOP_OF_BODY (WARNING HTML), QFMD_SIDE_NAV_UNDER_LOGO (SUCCESS, colors, additional styles) | pass (chromium, firefox, webkit) | [#539](https://github.com/QRun-IO/qqq/issues/539) |
| NAV-014 | Breadcrumbs show the full app hierarchy for tables, processes, records, create and edit pages, and each crumb navigates | /app/person: People App / Greetings App / Person; /app/navDeepItem/2/edit: Nav Level One / Nav Level Two / Nav Level Three / Nav Deep Item / 2 / Edit; process and create pages; clicking a crumb opens that app home | no breadcrumbs on the dashboard root | sample person/greetInteractive; NavigationFixtures navDeepItem | pass (chromium, firefox, webkit) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-015 | Document title names the page, its enclosing apps and the application (Material parity) | Dashboard \| QQQ Sample; Person \| Greetings App \| People App \| QQQ Sample; record label title on the record page | unknown route title is Not Found \| QQQ Sample | stock sample | pass (chromium, firefox, webkit) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-016 | The site root redirects to the dashboard | / replaces itself with /app (back does not return to /) | | stock sample | pass (chromium, firefox, webkit) | |
| NAV-017 | Dashboard landing page: counts, quick actions and application cards derived from navigable, permitted metadata by label | counts equal the navigable tables, processes, apps and their widgets from backend metadata; quick actions create only navigable insertable tables and run only navigable processes, by label (regression: listed internal and table-bulk processes, crashed on apps without widgets); application cards open app homes | viewer persona: no create or process quick actions | stock sample + NavigationFixtures; persona viewer | pass (chromium, firefox, webkit) | [#651](https://github.com/QRun-IO/qqq/issues/651) |
| NAV-018 | Direct (hard-loaded) links open every route shape with the shell, sidebar state and breadcrumbs | app home, nested app home, table, process, record view, record edit, create, saved view | | stock sample + NavigationFixtures | pass (chromium, firefox, webkit) | |
| NAV-019 | Browser refresh keeps the route, its query string and the rendered content | refresh on a table with a query string, a record view, and an app home | | stock sample | pass (chromium, firefox, webkit) | |
| NAV-020 | Browser back and forward traverse navigation history with the right content | dashboard > app home > table > record, then back three times and forward twice | | stock sample | pass (chromium, firefox, webkit) | |
| NAV-021 | An unknown /app/{name} shows an in-shell not-found state with a link to the dashboard | /app/noSuchThing shows 'Page not found' naming the segment; the link opens the dashboard; no failed requests | | stock sample | pass (chromium, firefox, webkit) | [#654](https://github.com/QRun-IO/qqq/issues/654) |
| NAV-022 | Unknown deeper or top-level paths return the 404 page with a working home link | /app/person/1/no/such and /no-such-page respond 404 and render the 404 page; Go Home reaches the dashboard | only the document 404 is reported | stock sample | pass (chromium, firefox, webkit) | |
| NAV-023 | Record lookup by key: /app/{table}/key?{field}={value} opens the single matching record | ?code=NAV-B2 opens record 2 (Tide Chart); ?email=... opens that person | no match, several matches (?shelf=North), unknown field, no parameters: error message, no navigation | NavigationFixtures navDeepItem (unique key code); sample person emails | pass (chromium, firefox, webkit) | [#654](https://github.com/QRun-IO/qqq/issues/654) |
| NAV-024 | Command palette (Ctrl+K and '.') lists navigable apps, tables and processes by label with type and app context, filters, and navigates by keyboard | filter 'pet' shows Pet, Pet Note, Pet Species with type and app; Enter opens the first; '.' opens the palette; Escape closes it | hidden objects are not listed | stock sample + NavigationFixtures | pass (chromium, firefox, webkit) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-025 | Header search, '/' search dialog and search page jump to navigation targets and recently viewed records without a backend search endpoint | typing a label lists matching pages with app context; choosing one navigates; a viewed record is offered as a recent record; Enter without a selection opens /app/search?q= with the matches, surviving refresh | no POST /qqq/v1/search request is made (regression: every keystroke produced a 404) | stock sample | pass (chromium, firefox, webkit) | [#653](https://github.com/QRun-IO/qqq/issues/653) |
| NAV-026 | Mobile navigation drawer opens from the header, navigates and closes | hamburger opens the drawer; choosing a table navigates and closes it; close button closes it | | stock sample, 390x844 viewport | pass (chromium, firefox, mobile, webkit) | |
| NAV-027 | Developer page reports the backend metadata counts and the running framework versions | metadata counts equal the backend's; framework shows the bundled Next.js version (regression: hardcoded 'Next.js 15') | | stock sample | pass (chromium, firefox, webkit) | [#654](https://github.com/QRun-IO/qqq/issues/654) |
| NAV-028 | Reports in the app tree are listed under their app (sidebar and app home Reports group) and open the report page | sidebar entry with its icon opens /app/navDeepItemReport with the report heading, title and breadcrumbs; app home Reports group lists and opens it | | NavigationFixtures navDeepItemReport (child of Nav Level Three, icon assessment) | pass (chromium, firefox, webkit) | [#654](https://github.com/QRun-IO/qqq/issues/654) |
| NAV-029 | A user who may access no apps sees the Dashboard entry and the no-apps message (Material NoApps parity) | sidebar shows only Dashboard; landing shows You do not have permission to access any apps. | app home links show the not-found state | harness persona noApps (every permission except App HAS_ACCESS) - requested: not yet in AcceptanceSampleServer | pass (chromium, firefox, webkit) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-001 | Direct link to a table list renders seeded rows | success | | prime-test-database.sql | pass (chromium) | |
| NAV-002 | Sidebar lists every permitted top-level app once, by label, in backend order, with nested apps only under their parent | admin sees Miscellaneous, Nav Empty App, Nav Level One, People App, Sample Widgets Dashboard, Sharing Demo in backend order; Greetings App appears only under People App (regression: it was duplicated at top level); group children use metadata labels | noPets persona: Pet and Pet Note are absent from the sidebar; backend returns 404 for their metadata and 403 for a query | stock sample apps + NavigationFixtures (Nav Level One, Nav Empty App); persona noPets | pass (chromium) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-003 | Apps nested deeper than two levels render as nested sidebar groups; deep links expand every enclosing group | expand three nested groups and open Nav Deep Item; grid rows match nav_deep_item; deep link to /app/navDeepItem expands all three groups and marks the item current | | NavigationFixtures: navLevelOne > navLevelTwo > navLevelThree > navDeepItem | pass (chromium) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-004 | Sidebar click navigation to app homes, tables and processes with active-entry highlighting and collapse/expand | Dashboard is current on /app; click app, table, process; URL, content and aria-current follow; collapse hides children, expand shows them | | stock sample (Miscellaneous app) | pass (chromium) | |
| NAV-005 | Declared icons (QIcon name, color, path) render on navigation surfaces | sidebar glyph per declared Material name, color applied, image path loads; app home entries and section headings show declared icons; command palette entries show declared icons | no declared icon renders the type fallback glyph | sample icons (stars, person, widgets, share, pets, local_shipping, badge) + NavigationFixtures (path icon, colored icon, inventory_2, inbox, layers) | pass (chromium) | [#538](https://github.com/QRun-IO/qqq/issues/538) |
| NAV-006 | Hidden tables and processes never appear in navigation; direct links to hidden objects the user may not access show not-found and the backend denies them | sidebar, app home, landing page and command palette omit City, Greet People, Simple Sleep and Nav Hidden Note | /app/city and /app/greet show the not-found state; backend returns 404 and 403 | sample city (hidden table), greet and simpleSleep (hidden processes); NavigationFixtures navHiddenNote (hidden, NOT_PROTECTED) | pass (chromium) | |
| NAV-007 | A hidden table the user may access opens by direct link (hidden is not denied) | /app/navHiddenNote lists nav_hidden_note rows; breadcrumbs show its enclosing apps | | NavigationFixtures navHiddenNote (isHidden, PermissionLevel.NOT_PROTECTED) | pass (chromium) | |
| NAV-008 | App home sections list processes (Actions), reports (Reports) and tables (Data) by label with icons, and each entry navigates | Miscellaneous: Actions Sleep Interactive, Simple Throw; Data Carrier, Field Lab, Pet Species (labels, not names); section heading shows its label and declared icon; clicking an entry opens the table or process | hidden or unpermitted section entries are omitted (navHiddenNote, noPets persona) | sample greetingsApp and miscellaneous sections | pass (chromium) | [#651](https://github.com/QRun-IO/qqq/issues/651) |
| NAV-009 | App home table entries show the backend record count | each Data entry reads 'N total records' equal to the SQL count | | sample carrier, fieldLab, petSpecies, person, pet, petNote rows | pass (chromium) | [#651](https://github.com/QRun-IO/qqq/issues/651) |
| NAV-010 | App homes of apps with child apps, only processes, or nothing render without errors | People App shows Clone People and the Greetings App child app (regression: crashed on missing widgets); Nav Level One lists its child app and navigates to it; Nav Empty App shows the empty state | viewer persona: People App shows only the Greetings App child app (no denied process) | sample peopleApp; NavigationFixtures navLevelOne, navEmptyApp; persona viewer | pass (chromium) | [#651](https://github.com/QRun-IO/qqq/issues/651) |
| NAV-011 | A dashboard app (widgets only) opens from navigation and shows one labelled container per permitted declared widget | sidebar click opens the dashboard; one widget region per declared widget label, in order | | sample SampleWidgetsDashboard (11 widgets) | pass (chromium) | |
| NAV-012 | Branding: sidebar logo (Material parity), app name, favicon from the icon, accent color, and partial branding accepted without warnings | sidebar shows /samples-logo.png with alt QQQ Sample, loaded, linking to the dashboard; favicon link points to /kr-icon.png; primary color variables equal the branding accent color; no 'schema validation' console warning for branding without companyName/companyUrl | | sample branding (appName, logo /samples-logo.png, icon /kr-icon.png) + NavigationFixtures accentColor #1d4ed8 | pass (chromium) | [#539](https://github.com/QRun-IO/qqq/issues/539) |
| NAV-013 | Branding banners render in their slots (top of site, top of body, side nav under logo) with text or sanitized HTML, severity and colors | each slot shows its message in the right place with severity and colors from metadata | unknown slots are not rendered | NavigationFixtures banners: QFMD_TOP_OF_SITE (INFO text), QFMD_TOP_OF_BODY (WARNING HTML), QFMD_SIDE_NAV_UNDER_LOGO (SUCCESS, colors, additional styles) | pass (chromium) | [#539](https://github.com/QRun-IO/qqq/issues/539) |
| NAV-014 | Breadcrumbs show the full app hierarchy for tables, processes, records, create and edit pages, and each crumb navigates | /app/person: People App / Greetings App / Person; /app/navDeepItem/2/edit: Nav Level One / Nav Level Two / Nav Level Three / Nav Deep Item / 2 / Edit; process and create pages; clicking a crumb opens that app home | no breadcrumbs on the dashboard root | sample person/greetInteractive; NavigationFixtures navDeepItem | pass (chromium) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-015 | Document title names the page, its enclosing apps and the application (Material parity) | Dashboard \| QQQ Sample; Person \| Greetings App \| People App \| QQQ Sample; record label title on the record page | unknown route title is Not Found \| QQQ Sample | stock sample | pass (chromium) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-016 | The site root redirects to the dashboard | / replaces itself with /app (back does not return to /) | | stock sample | pass (chromium) | |
| NAV-017 | Dashboard landing page: counts, quick actions and application cards derived from navigable, permitted metadata by label | counts equal the navigable tables, processes, apps and their widgets from backend metadata; quick actions create only navigable insertable tables and run only navigable processes, by label (regression: listed internal and table-bulk processes, crashed on apps without widgets); application cards open app homes | viewer persona: no create or process quick actions | stock sample + NavigationFixtures; persona viewer | pass (chromium) | [#651](https://github.com/QRun-IO/qqq/issues/651) |
| NAV-018 | Direct (hard-loaded) links open every route shape with the shell, sidebar state and breadcrumbs | app home, nested app home, table, process, record view, record edit, create, saved view | | stock sample + NavigationFixtures | pass (chromium) | |
| NAV-019 | Browser refresh keeps the route, its query string and the rendered content | refresh on a table with a query string, a record view, and an app home | | stock sample | pass (chromium) | |
| NAV-020 | Browser back and forward traverse navigation history with the right content | dashboard > app home > table > record, then back three times and forward twice | | stock sample | pass (chromium) | |
| NAV-021 | An unknown /app/{name} shows an in-shell not-found state with a link to the dashboard | /app/noSuchThing shows 'Page not found' naming the segment; the link opens the dashboard; no failed requests | | stock sample | pass (chromium) | [#654](https://github.com/QRun-IO/qqq/issues/654) |
| NAV-022 | Unknown deeper or top-level paths return the 404 page with a working home link | /app/person/1/no/such and /no-such-page respond 404 and render the 404 page; Go Home reaches the dashboard | only the document 404 is reported | stock sample | pass (chromium) | |
| NAV-023 | Record lookup by key: /app/{table}/key?{field}={value} opens the single matching record | ?code=NAV-B2 opens record 2 (Tide Chart); ?email=... opens that person | no match, several matches (?shelf=North), unknown field, no parameters: error message, no navigation | NavigationFixtures navDeepItem (unique key code); sample person emails | pass (chromium) | [#654](https://github.com/QRun-IO/qqq/issues/654) |
| NAV-024 | Command palette (Ctrl+K and '.') lists navigable apps, tables and processes by label with type and app context, filters, and navigates by keyboard | filter 'pet' shows Pet, Pet Note, Pet Species with type and app; Enter opens the first; '.' opens the palette; Escape closes it | hidden objects are not listed | stock sample + NavigationFixtures | pass (chromium) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-025 | Header search, '/' search dialog and search page jump to navigation targets and recently viewed records by label (matched locally, alongside record search when the backend offers it: NAV-030) | typing a label lists matching pages with app context; choosing one navigates; a viewed record is offered as a recent record; Enter without a selection opens /app/search?q= with the matches, surviving refresh; search dialog contains Tab and Shift+Tab with empty or populated results; Clear returns to the input; Escape and backdrop dismissal restore keyboard and pointer openers | no search request fails (regression: every keystroke produced a 404 from a missing /search endpoint); with no searchable tables no request is made at all (NAV-032) | stock sample | pass (chromium) | [#653](https://github.com/QRun-IO/qqq/issues/653) |
| NAV-026 | Mobile navigation drawer opens from the header, navigates and closes | hamburger opens the drawer; choosing a table navigates and closes it; close button and a backdrop tap close it; drawer controls are 44 px touch targets | | stock sample, 412x839 touch viewport | pass (chromium) | |
| NAV-027 | Developer page reports the backend metadata counts and the running framework versions | metadata counts equal the backend's; framework shows the bundled Next.js version (regression: hardcoded 'Next.js 15') | | stock sample | pass (chromium) | [#654](https://github.com/QRun-IO/qqq/issues/654) |
| NAV-028 | Reports in the app tree are listed under their app (sidebar and app home Reports group) and open the report page | sidebar entry with its icon opens /app/navDeepItemReport with the report heading, title and breadcrumbs; app home Reports group lists and opens it | | NavigationFixtures navDeepItemReport (child of Nav Level Three, icon assessment) | pass (chromium) | [#654](https://github.com/QRun-IO/qqq/issues/654) |
| NAV-029 | A user who may access no apps sees the Dashboard entry and the no-apps message (Material NoApps parity) | sidebar shows only Dashboard; landing shows You do not have permission to access any apps. | app home links show the not-found state | harness persona noApps (every permission except App HAS_ACCESS) - requested: not yet in AcceptanceSampleServer | pass (chromium) | [#652](https://github.com/QRun-IO/qqq/issues/652) |
| NAV-030 | Global search (header box, '/' dialog, search page) lists the records a backend record search (POST /qqq/v1/search over each table's searchFields) finds, after the matching pages, by record label with the table label; results open by keyboard and the search page survives refresh | '/' dialog: 'COCO' finds the pet Coco (label and table label), case-insensitively; ArrowDown and Enter open /app/pet/{id}; the request searches the readable searchable tables (person, pet, savedView) with a per-table limit of 5; header box: 'Sample' lists the matching people (database order, limit 5) after pages; Enter opens /app/search?q=Sample whose Records region lists every match (limit 25) before and after refresh; a link opens the record | a one-character term is not sent (unit), and a recent record already found is not listed twice (unit) | stock sample + NavigationFixtures (search fields on person, pet, savedView) | pass (chromium) | [#701](https://github.com/QRun-IO/qqq/issues/701) |
| NAV-031 | Record search respects table read permissions and record security locks, in the UI and the API | noPets: 'ey' lists the matching people only; the UI never asks for pets and the metadata advertises no pet search fields; admin: the backend returns the pets matching 'EY' for tableNames [pet]; alice finds her saved view 'Alice People View' with the saved view table label, in the UI and from the API; bob, with whom the view is shared read-only, finds it from the API (the lock admits shared readers) | noPets: POST /qqq/v1/search with tableNames [pet] returns no results; an unscoped search returns no pets; casey (neither owner nor shared reader): the locked saved view is not found in the UI or from the API | stock sample + NavigationFixtures + QueryFixtures (alice's saved view shared read-only with bob); noPets persona; sharing demo users | pass (chromium) | [#701](https://github.com/QRun-IO/qqq/issues/701) |
| NAV-032 | Without searchable tables (the backend declares no searchFields) global search stays local and never calls a search endpoint | the variant's v1 metadata lists no table with searchFields; header box, '/' dialog and search page keep the local labels and matches, with no Records group or region | no request to /qqq/v1/search or /search is made while typing, opening the dialog or loading the search page | security variant (stock sample + SecurityFixtures) | pass (chromium) | [#701](https://github.com/QRun-IO/qqq/issues/701) [#653](https://github.com/QRun-IO/qqq/issues/653) |
| NAV-033 | Login branding before sign-in: logo, app name, accent color, favicon and title from GET /qqq/v1/metaData/authentication (no session), never the banners | unauthenticated authentication metadata carries exactly appName, logo, icon and accentColor; after logout and a fresh load the login card shows the loaded logo and QQQ Sample; primary color variables and the Sign in button use the accent; favicon /kr-icon.png; title Sign in \| QQQ Sample | no banner text in the unauthenticated response or on the login page | sample branding (appName, logo /samples-logo.png, icon /kr-icon.png) + NavigationFixtures accentColor #1d4ed8 and three banners | pass (chromium) | [#703](https://github.com/QRun-IO/qqq/issues/703) [#539](https://github.com/QRun-IO/qqq/issues/539) |
| NAV-034 | Material table-scoped URLs: /app/{table}/{process} opens the table process (or an instance process); /app/{table}/{id}/{process} runs it for that record and returns to it | /app/person/person.bulkEdit?recordsParam=recordIds&recordIds=1,2 replaces itself with the run (init sends 1,2; Back returns to the table); /app/person/prcQuickTask opens the instance process with returnTo the table; /app/person/1/clonePeople runs for record 1 (init and SQL clone) and Return lands on the record; /app/person/2/bulkEdit resolves the table process by suffix; Cancel returns to the record | /app/person/1/noSuchProcess shows the in-shell not-found state and starts no process; report URLs (/app/{table}/{report}) are not covered: the backend frontend report metadata (QFrontendReportMetaData) has no tableName, so no report can match (#732) | sample person.bulkEdit, clonePeople (table processes), prcQuickTask (ProcessesFixtures, instance process) | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) [#732](https://github.com/QRun-IO/qqq/issues/732) |
| NAV-035 | Process runs launched from a record view or a filtered query return there (Return and Cancel honor returnTo) | record Actions > Clone People runs for the record and Return lands on the record; Bulk Edit from /app/person?filter=isEmployed: returnTo is the launching path and query; after the edit (SQL) Return restores the same filter and the filtered rows | the unmatched record (Morgan) stays out of the returned grid | sample person, clonePeople, person.bulkEdit | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| NAV-036 | Material app settings (MaterialDashboardAppMetaData): showAppLabelOnHomeScreen and includeTableCountsOnHomeScreen turned off hide the home-screen title and the table counts | v1 metaData carries supplementalAppMetaData.materialDashboard for the app; The app home has no visible title (a screen-reader-only h1 remains) and the table entry shows no count; No count request is made for the table | Apps without the settings keep their title and counts (Material defaults) | NavigationFixtures.java navQuietHome app (navQuietItem table) with MaterialDashboardAppMetaData(false, false) | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| NAV-037 | Mobile navigation drawer is a keyboard-usable modal dialog: focus moves in on open, Tab stays inside, and Escape, the close button or a chosen link close it and return focus to the menu button | Enter on the menu button opens the Navigation dialog with focus inside; Tab and Shift+Tab visit every drawer control and never reach the page behind; Escape, the close button and choosing Carrier close it; focus returns to the menu button; Carrier opens | Tab never leaves the open drawer (QRun-IO/qqq#708: focus stayed on the menu button; Firefox could not reach the drawer) | stock sample (Miscellaneous app), 412x839 touch viewport | pass (chromium) | [#708](https://github.com/QRun-IO/qqq/issues/708) |
| NAV-038 | Shell on phones and tablets: skip link, header controls, breadcrumbs, navigation entries, user menu, app homes and not-found links are 44 px touch targets, and the page never scrolls sideways | skip link is the first Tab stop and 44 px tall when shown; header, breadcrumbs, sidebar or drawer entries and the user menu meet the 44 px target; no sideways page scroll with a four-level breadcrumb trail | under a mouse (pointer: fine) the desktop sizes are unchanged | stock sample + NavigationFixtures (navDeepItem three apps deep); mobile (Pixel 7) and tablet (iPad, desktop layout with touch) projects | pass (chromium) | [#708](https://github.com/QRun-IO/qqq/issues/708) |
| NAV-055 | Material Dashboard CSS and test hooks remain available on navigation, banners, app homes, forms, record views and query controls | Material data-qqq-id and class selectors identify corresponding Next UI controls; application CSS keyed to those selectors changes the rendered style; record and query actions retain the expected behavior and button variants | | stock sample with NavigationFixtures and RecordsFixtures; application CSS applied in css-hooks.spec.ts | pass (chromium) | [QRun-IO/qqq-frontend-next#731](https://github.com/QRun-IO/qqq-frontend-next/issues/731) |
| NAV-056 | The Material Dashboard theme from versioned QQQ metadata changes Next UI tokens, application CSS and the branded header | v1 metaData publishes the selected Material theme settings; the rendered dashboard applies its primary token, branded header colors and tagline, and custom CSS; Every theme scenario resets instance metadata before navigation; Preferences exposes accessible Light and Dark choices; keyboard changes, real reload persistence, reset and light/dark dialog accessibility are verified; Application theme override is explained in Preferences without erasing the saved browser appearance choice; Pivot validation meets text contrast in default light, default dark and a configured orange error theme; API playground visible text and hovered controls meet contrast in light/dark themes; Preferences changes update the rendered palette without reload | | AcceptanceSampleServer /acceptance/theme; reset removes the theme before each test | pass (chromium) | [QRun-IO/qqq-frontend-next#719](https://github.com/QRun-IO/qqq-frontend-next/issues/719) |
## Performance budgets on the production export: large tables, heavy dashboards and long processes (QRun-IO/qqq#710). Bundle budgets are checked separately by scripts/perf-budget.mjs.
| ID | Feature | Scenarios | Negative | Fixture | Result | Issues |
|---|---|---|---|---|---|---|
| PRF-001 | large table: a 10,000-row, 40-column table opens with every column and the exact first page within budget | all 40 columns have a sort header; the first 25 rows carry the exact computed values; the total reads 10,000; time from navigation to the first page is within PERFORMANCE_BUDGET.firstPageMs | only one page of rows is in the DOM (25 rows, 1,000 cells), not the table | tests/acceptance/fixture/PerformanceFixtures.java prfWide | pass (chromium) | [#710](https://github.com/QRun-IO/qqq/issues/710) |
| PRF-002 | large table: 250-row pages of 40 columns render, page and reach the last page within budget | page size 250 shows ids 10000..9751 with 10,000 cells within PERFORMANCE_BUDGET.pageMs; next page shows 9750..9501 and last page 250..1 (page 40), each within PERFORMANCE_BUDGET.pageMs; the backend is asked for skip/limit 250 | the DOM never holds more than one page (250 rows) | tests/acceptance/fixture/PerformanceFixtures.java prfWide | pass (chromium) | [#710](https://github.com/QRun-IO/qqq/issues/710) |
| PRF-003 | large table: sorting and filtering 10,000 rows return exact results within budget | sorting by Name ascending shows Row 00001..Row 00025 within PERFORMANCE_BUDGET.queryMs; Count 04 equals 0 narrows to the 40 matching rows (the SQL answer) within PERFORMANCE_BUDGET.queryMs | | tests/acceptance/fixture/PerformanceFixtures.java prfWide | pass (chromium) | [#710](https://github.com/QRun-IO/qqq/issues/710) |
| PRF-004 | heavy dashboard: 24 widgets (statistics, bar, line and pie charts, 100-row tables) load once each within budget | every widget finishes loading within PERFORMANCE_BUDGET.dashboardMs of navigation; statistics show their exact counts, chart data tables their exact points, tables their 100 rows | each widget's data is requested exactly once | tests/acceptance/fixture/PerformanceFixtures.java prfDashboard | pass (chromium) | [#710](https://github.com/QRun-IO/qqq/issues/710) |
| PRF-005 | long process: a 10-second job over 10,000 rows reports progress, polls at a bounded rate and shows its result promptly | progress messages advance while the job runs; the result shows Row Count 10000 and the SQL total; the result appears within PERFORMANCE_BUDGET.processResultMs of the job finishing | status is polled no more than once per POLL_INITIAL_MILLIS (1.5 s) and never concurrently | tests/acceptance/fixture/PerformanceFixtures.java prfLongRun | pass (chromium) | [#710](https://github.com/QRun-IO/qqq/issues/710) |
## Processes: steps, components, lifecycle, bulk load/edit/delete
| ID | Feature | Scenarios | Negative | Fixture | Result | Issues |
|---|---|---|---|---|---|---|
| PRC-001 | Selected-record run: record ids reach the backend, screen values are submitted, mixed view and record-list results render | recordIds 1,2 -> setup prefix/suffix -> results view values and both greeted rows | | sample greetInteractive | pass (chromium, firefox, mobile, webkit) | [#645](https://github.com/QRun-IO/qqq/issues/645) [#656](https://github.com/QRun-IO/qqq/issues/656) |
| PRC-002 | All-records run from a filter selection with no criteria | filterJSON {criteria:[]} greets every seeded person (count checked against SQL) | | sample greetInteractive | pass (chromium, firefox, webkit) | |
| PRC-003 | Zero-record runs from a filter that matches nothing | clonePeople input 0, validation on 0 with no lines, 0 processed | greetInteractive record-list step with zero records is refused ("Missing input records.") | sample greetInteractive, clonePeople | pass (chromium, firefox, webkit) | [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-004 | Processes launched from an app (no records) | app shortcut opens Component Lab without records | greetInteractive without records is refused at init ("Missing input records.") | prcLab app, sample greetInteractive | pass (chromium, firefox, mobile, webkit) | |
| PRC-005 | Process launched from selected rows of a table query | select Avery and Casey, Actions -> Greet Interactive -> both greeted | | sample person + greetInteractive | pass (chromium, firefox, webkit) | |
| PRC-006 | Minimum and maximum input records | 1-2 selected records run and are listed | 0 or 3 explicit records show a message without init; filter selecting 5 shows the backend refusal; API filter selecting 0 is refused | prcBounds (min 1, max 2) | pass (chromium, firefox, webkit) | [#662](https://github.com/QRun-IO/qqq/issues/662) |
| PRC-007 | Every declared component renders on one screen, in declared order, with one action bar | HELP_TEXT, HELP_TEXT, VIEW_FORM, EDIT_FORM, HTML, EDIT_FORM in order | | prcComponents mixed screen | pass (chromium, firefox, webkit) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-008 | HELP_TEXT with line breaks and previewText toggle | plain lines; Show/Hide preview text | | prcComponents | pass (chromium, firefox, webkit) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-009 | EDIT_FORM field subsets (includeFieldNames), section labels and persisted input values | Lab Inputs section holds only its fields; values saved (SQL prc_lab_run) | | prcComponents | pass (chromium, firefox, mobile, webkit) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-010 | Required input validation on screens and in the backend | filled required field proceeds | empty required field blocks without a request; API step without the required input is refused | prcComponents | pass (chromium, firefox, webkit) | |
| PRC-011 | Process possible-value fields | options from the process route; chosen id saved, label shown on review; search filters options | | prcComponents labColor (prcColor enum) | pass (chromium, firefox, webkit) | [#667](https://github.com/QRun-IO/qqq/issues/667) |
| PRC-012 | VIEW_FORM labels and backend values | Introduction and Status values | | prcComponents | pass (chromium, firefox, webkit) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-013 | HTML component renders the step html value, sanitized | markup rendered | script and onerror removed, never executed | prcComponents mixed.html | pass (chromium, firefox, webkit) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-014 | DOWNLOAD_FORM delivers the generated file | download bytes equal the generated file | unregistered file path refused (403) | prcComponents compute step | pass (chromium, firefox, webkit) | |
| PRC-015 | Step help content for process screens | help text shown | | prcComponents helpContent (PROCESS_SCREEN) | pass (chromium, firefox, webkit) | |
| PRC-016 | Back to the backend back step with entered values | Back restarts at the back step with values (possible-value label shown); resubmitting saves again | no Back without a backend backStep | prcComponents review, prcWizard longDetails, greetInteractive | pass (chromium, firefox, webkit) | [#655](https://github.com/QRun-IO/qqq/issues/655) [#667](https://github.com/QRun-IO/qqq/issues/667) |
| PRC-017 | Dynamic step list from processMetaDataAdjustment.updatedFrontendStepList | short route drops a screen from the stepper; values logged (SQL) | | prcWizard | pass (chromium, firefox, webkit) | [#662](https://github.com/QRun-IO/qqq/issues/662) |
| PRC-018 | Updated field metadata from processMetaDataAdjustment.updatedFields | field relabelled and required on the later screen | empty relabelled required field blocks | prcWizard long route | pass (chromium, firefox, webkit) | [#662](https://github.com/QRun-IO/qqq/issues/662) |
| PRC-019 | Async job progress (message, N of M, progress bar, updated time) then continue | RUNNING progress shown while polling; finished screen and SQL log | | prcProgress | pass (chromium, firefox, webkit) | [#657](https://github.com/QRun-IO/qqq/issues/657) |
| PRC-020 | Cancel from a screen and mid-job runs the cancel step and leaves | confirm dialog; cancel step row (SQL) from a screen and while working | Stay on Page keeps the run | prcProgress cancelStep | pass (chromium, firefox, webkit) | |
| PRC-021 | Step errors: user-facing messages directly, internal errors behind a detail toggle, Close | user-facing message; internal detail toggle; simpleThrow "I always throw."; Close returns to the app | | prcFailures, prcComponents, sample simpleThrow | pass (chromium, firefox, webkit) | |
| PRC-022 | Retry after an error restarts with the original record selection | retry loads the selected records 2 and 4 | | prcFlaky (fails once) | pass (chromium, firefox, webkit) | [#658](https://github.com/QRun-IO/qqq/issues/658) |
| PRC-023 | Completion: Next/Submit labels, Return on the last screen or when noMoreSteps | Submit on the step before the last screen; Return only on the last screen; noMoreSteps ends early | | prcComponents, prcWizard, prcEarlyFinish | pass (chromium, firefox, webkit) | |
| PRC-024 | Linear stepper and step position | all step labels; current step marked; Step i of n | | prcComponents | pass (chromium, firefox, webkit) | |
| PRC-025 | RECORD_LIST paging and page size over process records | 1-10, 11-20, 21-23; page size 25 | next disabled on the last page | prcManyRows (23 generated records) | pass (chromium, firefox, webkit) | |
| PRC-026 | Validation review: input count, validate-or-skip choice, Next/Submit label, record preview | Input: 2 Person records.; radio switches the button label; preview 1 of 2 / 2 of 2 | | sample clonePeople | pass (chromium, firefox, webkit) | [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-027 | Full validation summary lines (OK, WARNING, ERROR) with record links | four lines with counts and messages; link filters to the refused record | nothing persisted by validation | sample clonePeople + inserted clone persons | pass (chromium, firefox, webkit) | [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-028 | Process summary results after execution and persisted effects | result lines and processed count; clones persisted (SQL); skip validation executes immediately | refused and nested records not cloned | sample clonePeople | pass (chromium, firefox, webkit) | [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-029 | Bulk edit: switched-on fields only, review and result | edited fields persisted for selected records only (SQL); result lines | no switched-on field blocks without a request | enricher person.bulkEdit | pass (chromium, firefox, webkit) | [#656](https://github.com/QRun-IO/qqq/issues/656) [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-030 | Bulk delete: review, validation and result | selected records removed (SQL) | validation deletes nothing | enricher person.bulkDelete | pass (chromium, firefox, webkit) | [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-031 | Bulk insert: upload, column mapping, value mapping, profile, review preview and result | uploaded CSV mapped and inserted (SQL) | missing required file blocks | enricher person.bulkInsert + prcUploadArchive | pass (chromium, firefox, webkit) | [#656](https://github.com/QRun-IO/qqq/issues/656) [#660](https://github.com/QRun-IO/qqq/issues/660) [#543](https://github.com/QRun-IO/qqq/issues/543) |
| PRC-032 | Bulk load mapping validation and Back through mapping and upload | Back from review keeps the mapping; Back from mapping to upload | unmapped required column blocks without a request | enricher person.bulkInsert | pass (chromium, firefox, webkit) | [#655](https://github.com/QRun-IO/qqq/issues/655) [#660](https://github.com/QRun-IO/qqq/issues/660) |
| PRC-033 | Bulk edit with a file matched by key fields | rows matched by Id updated (SQL) | | enricher person.bulkEditWithFile | pass (chromium, firefox, webkit) | [#656](https://github.com/QRun-IO/qqq/issues/656) [#660](https://github.com/QRun-IO/qqq/issues/660) |
| PRC-034 | Upload instructions HTML with downloadable template | template CSV downloads with the header row | | enricher person.bulkInsert upload.html | pass (chromium, firefox, webkit) | |
| PRC-035 | Named WIDGET fetched with the process UUID and values | widget text built from process values | | prcWidgets prcHtmlWidget | pass (chromium, firefox, webkit) | [#661](https://github.com/QRun-IO/qqq/issues/661) |
| PRC-036 | Named composite WIDGET seeded from process values | seeded blocks rendered, no fetch | | prcWidgets prcCompositeWidget | pass (chromium, firefox, webkit) | [#661](https://github.com/QRun-IO/qqq/issues/661) |
| PRC-037 | Ad hoc composite WIDGET: interpolation, conditional blocks, action buttons, submit-on-enter inputs | Approve submits actionCode (SQL); Enter submits the scan code (SQL) | conditional block hidden | prcWidgets ad hoc blocks | pass (chromium, firefox, webkit) | [#661](https://github.com/QRun-IO/qqq/issues/661) |
| PRC-038 | GOOGLE_DRIVE_SELECT_FOLDER screen keeps its other components working | help text and edit form submit (SQL); picker explains it is not configured | required export note blocks | prcDrive | pass (chromium, firefox, webkit) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-001 | Selected-record run: record ids reach the backend, screen values are submitted, mixed view and record-list results render | recordIds 1,2 -> setup prefix/suffix -> results view values and both greeted rows | | sample greetInteractive | pass (chromium) | [#645](https://github.com/QRun-IO/qqq/issues/645) [#656](https://github.com/QRun-IO/qqq/issues/656) |
| PRC-002 | All-records run from a filter selection with no criteria | filterJSON {criteria:[]} greets every seeded person (count checked against SQL) | | sample greetInteractive | pass (chromium) | |
| PRC-003 | Zero-record runs from a filter that matches nothing | clonePeople input 0, validation on 0 with no lines, 0 processed | greetInteractive record-list step with zero records is refused ("Missing input records.") | sample greetInteractive, clonePeople | pass (chromium) | [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-004 | Processes launched from an app (no records) | app shortcut opens Component Lab without records | greetInteractive without records is refused at init ("Missing input records.") | prcLab app, sample greetInteractive | pass (chromium) | |
| PRC-005 | Process launched from selected rows of a table query | select Avery and Casey, Actions -> Greet Interactive -> both greeted | | sample person + greetInteractive | pass (chromium) | |
| PRC-006 | Minimum and maximum input records | 1-2 selected records run and are listed | 0 or 3 explicit records show a message without init; filter selecting 5 shows the backend refusal; API filter selecting 0 is refused | prcBounds (min 1, max 2) | pass (chromium) | [#662](https://github.com/QRun-IO/qqq/issues/662) |
| PRC-007 | Every declared component renders on one screen, in declared order, with one action bar | HELP_TEXT, HELP_TEXT, VIEW_FORM, EDIT_FORM, HTML, EDIT_FORM in order | | prcComponents mixed screen | pass (chromium) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-008 | HELP_TEXT with line breaks and previewText toggle | plain lines; Show/Hide preview text | | prcComponents | pass (chromium) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-009 | EDIT_FORM field subsets (includeFieldNames), section labels and persisted input values | Lab Inputs section holds only its fields; values saved (SQL prc_lab_run) | | prcComponents | pass (chromium) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-010 | Required input validation on screens and in the backend | filled required field proceeds | empty required field blocks without a request; API step without the required input is refused | prcComponents | pass (chromium) | |
| PRC-011 | Process possible-value fields | options from the process route; chosen id saved, label shown on review; search filters options | | prcComponents labColor (prcColor enum) | pass (chromium) | [#667](https://github.com/QRun-IO/qqq/issues/667) |
| PRC-012 | VIEW_FORM labels and backend values | Introduction and Status values | | prcComponents | pass (chromium) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-013 | HTML component renders the step html value, sanitized | markup rendered | script and onerror removed, never executed | prcComponents mixed.html | pass (chromium) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-014 | DOWNLOAD_FORM delivers the generated file | download bytes equal the generated file | unregistered file path refused (403) | prcComponents compute step | pass (chromium) | |
| PRC-015 | Step help content for process screens | help text shown | | prcComponents helpContent (PROCESS_SCREEN) | pass (chromium) | |
| PRC-016 | Back to the backend back step with entered values | Back restarts at the back step with values (possible-value label shown); resubmitting saves again | no Back without a backend backStep | prcComponents review, prcWizard longDetails, greetInteractive | pass (chromium) | [#655](https://github.com/QRun-IO/qqq/issues/655) [#667](https://github.com/QRun-IO/qqq/issues/667) |
| PRC-017 | Dynamic step list from processMetaDataAdjustment.updatedFrontendStepList | short route drops a screen from the stepper; values logged (SQL) | | prcWizard | pass (chromium) | [#662](https://github.com/QRun-IO/qqq/issues/662) |
| PRC-018 | Updated field metadata from processMetaDataAdjustment.updatedFields | field relabelled and required on the later screen | empty relabelled required field blocks | prcWizard long route | pass (chromium) | [#662](https://github.com/QRun-IO/qqq/issues/662) |
| PRC-019 | Async job progress in the live job view (message, N / M, determinate progress bar, elapsed) then continue | RUNNING progress shown while polling; finished screen and SQL log | | prcProgress | pass (chromium) | [#657](https://github.com/QRun-IO/qqq/issues/657) |
| PRC-020 | Cancel from a screen and mid-job runs the cancel step and leaves | confirm dialog; cancel step row (SQL) from a screen and while working | Stay on Page keeps the run | prcProgress cancelStep | pass (chromium) | |
| PRC-021 | Step errors: user-facing messages directly, internal errors behind a detail toggle, Close | user-facing message; internal detail toggle; expanded details meet contrast requirements in light, dark and application themes; simpleThrow "I always throw."; Close returns to the app | | prcFailures, prcComponents, sample simpleThrow | pass (chromium) | |
| PRC-022 | Retry after an error restarts with the original record selection | retry loads the selected records 2 and 4 | | prcFlaky (fails once) | pass (chromium) | [#658](https://github.com/QRun-IO/qqq/issues/658) |
| PRC-023 | Completion: Next/Submit labels, Return on the last screen or when noMoreSteps | Submit on the step before the last screen; Return only on the last screen; noMoreSteps ends early | | prcComponents, prcWizard, prcEarlyFinish | pass (chromium) | |
| PRC-024 | Linear stepper and step position | all step labels; current step marked; Step i of n | | prcComponents | pass (chromium) | |
| PRC-025 | RECORD_LIST paging and page size over process records | 1-10, 11-20, 21-23; page size 25 | next disabled on the last page | prcManyRows (23 generated records) | pass (chromium) | |
| PRC-026 | Validation review: input count, validate-or-skip choice, Next/Submit label, record preview | Input: 2 Person records.; radio switches the button label; preview 1 of 2 / 2 of 2 | | sample clonePeople | pass (chromium) | [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-027 | Full validation summary lines (OK, WARNING, ERROR) with record links | four lines with counts and messages; link filters to the refused record | nothing persisted by validation | sample clonePeople + inserted clone persons | pass (chromium) | [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-028 | Process summary results after execution and persisted effects | result lines and processed count; clones persisted (SQL); skip validation executes immediately | refused and nested records not cloned | sample clonePeople | pass (chromium) | [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-029 | Bulk edit: switched-on fields only, review and result | edited fields persisted for selected records only (SQL); result lines | no switched-on field blocks without a request | enricher person.bulkEdit | pass (chromium) | [#656](https://github.com/QRun-IO/qqq/issues/656) [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-030 | Bulk delete: review, validation and result | selected records removed (SQL) | validation deletes nothing | enricher person.bulkDelete | pass (chromium) | [#659](https://github.com/QRun-IO/qqq/issues/659) |
| PRC-031 | Bulk insert: upload, column mapping, value mapping, profile, review preview and result | uploaded CSV mapped and inserted (SQL) | missing required file blocks | enricher person.bulkInsert + prcUploadArchive | pass (chromium) | [#656](https://github.com/QRun-IO/qqq/issues/656) [#660](https://github.com/QRun-IO/qqq/issues/660) [#543](https://github.com/QRun-IO/qqq/issues/543) |
| PRC-032 | Bulk load mapping validation and Back through mapping and upload | Back from review keeps the mapping; Back from mapping to upload | unmapped required column blocks without a request | enricher person.bulkInsert | pass (chromium) | [#655](https://github.com/QRun-IO/qqq/issues/655) [#660](https://github.com/QRun-IO/qqq/issues/660) |
| PRC-033 | Bulk edit with a file matched by key fields | rows matched by Id updated (SQL) | | enricher person.bulkEditWithFile | pass (chromium) | [#656](https://github.com/QRun-IO/qqq/issues/656) [#660](https://github.com/QRun-IO/qqq/issues/660) |
| PRC-034 | Upload instructions HTML with downloadable template | template CSV downloads with the header row | | enricher person.bulkInsert upload.html | pass (chromium) | |
| PRC-035 | Named WIDGET fetched with the process UUID and values | widget text built from process values | | prcWidgets prcHtmlWidget | pass (chromium) | [#661](https://github.com/QRun-IO/qqq/issues/661) |
| PRC-036 | Named composite WIDGET seeded from process values | seeded blocks rendered, no fetch | | prcWidgets prcCompositeWidget | pass (chromium) | [#661](https://github.com/QRun-IO/qqq/issues/661) |
| PRC-037 | Ad hoc composite WIDGET: interpolation, conditional blocks, action buttons, submit-on-enter inputs | Approve submits actionCode (SQL); Enter submits the scan code (SQL) | conditional block hidden | prcWidgets ad hoc blocks | pass (chromium) | [#661](https://github.com/QRun-IO/qqq/issues/661) |
| PRC-038 | GOOGLE_DRIVE_SELECT_FOLDER screen keeps its other components working | help text and edit form submit (SQL); picker explains it is not configured | required export note blocks | prcDrive | pass (chromium) | [#645](https://github.com/QRun-IO/qqq/issues/645) |
| PRC-039 | Google Drive folder selection through the Google Picker | pick a folder and submit its id | | needs GOOGLE_APP_CLIENT_ID, GOOGLE_APP_API_KEY and a Google account | excluded (Approved by James 2026-09-25 (QRun-IO/qqq#649): Google Drive folder picker needs a real Google OAuth client and account; the rest of the step is covered by PRC-038; real-service testing is follow-up work.) | |
| PRC-040 | Reload mid-process starts a fresh run | new process UUID, first screen, empty inputs; earlier effects kept (SQL) | | prcComponents | pass (chromium, firefox, webkit) | |
| PRC-041 | Expired session mid-process returns to sign-in | 401 redirects to login | nothing saved | prcComponents + expired persona | pass (chromium, firefox, webkit) | [#668](https://github.com/QRun-IO/qqq/issues/668) |
| PRC-042 | Process permission enforcement | revoked permission shows the permission message | step refused, nothing saved; direct link offers no process; API init refused (403) | prcComponents + noProcesses persona | pass (chromium, firefox, webkit) | |
| PRC-043 | Default process values from the link (defaultProcessValues) | sleepMillis preset drives an async sleep to screen1; labName prefilled | | sample sleepInteractive, prcComponents | pass (chromium, firefox, webkit) | |
| PRC-044 | Scanner-format screens: no heading or action bar, input blocks submit | auto-focused input submits on Enter (SQL) | no Next/Cancel buttons rendered | prcScanner (format scanner) | pass (chromium, firefox, webkit) | [#661](https://github.com/QRun-IO/qqq/issues/661) |
| PRC-045 | State-machine step flow: repeated screens, no linear stepper | screen repeats with fresh inputs and counted rounds; noMoreSteps ends with Return | no stepper or Step i of n | prcLoop (STATE_MACHINE) | pass (chromium, firefox, webkit) | |
| PRC-046 | Saved bulk load profiles: save, choose for a later load, delete | Save As stores the mapping (SQL); choosing the profile restores the mapping and is named on review; Delete removes it (SQL) | a duplicate name is refused | SavedBulkLoadProfileMetaDataProvider on owned saved_bulk_load_profile tables | pass (chromium, firefox, webkit) | [#660](https://github.com/QRun-IO/qqq/issues/660) |
| PRC-047 | A run with no frontend screens completes with a completion screen | completion heading and message; backend effect persisted (SQL) | | prcQuickTask (backend step only) | pass (chromium, firefox, webkit) | |
| PRC-040 | Reload mid-process starts a fresh run | new process UUID, first screen, empty inputs; earlier effects kept (SQL) | | prcComponents | pass (chromium) | |
| PRC-041 | Expired session mid-process returns to sign-in | 401 redirects to login | nothing saved | prcComponents + expired persona | pass (chromium) | [#668](https://github.com/QRun-IO/qqq/issues/668) |
| PRC-042 | Process permission enforcement | revoked permission shows the permission message | step refused, nothing saved; direct link offers no process; API init refused (403) | prcComponents + noProcesses persona | pass (chromium) | |
| PRC-043 | Default process values from the link (defaultProcessValues) | sleepMillis preset drives an async sleep to screen1; labName prefilled; normal navigation shows a required sleep duration with a 1000ms default and completes to screen1 | clearing sleep duration blocks submission on screen0 | sample sleepInteractive, prcComponents | pass (chromium) | |
| PRC-044 | Scanner-format screens: no heading or action bar, input blocks submit | auto-focused input submits on Enter (SQL) | no Next/Cancel buttons rendered | prcScanner (format scanner) | pass (chromium) | [#661](https://github.com/QRun-IO/qqq/issues/661) |
| PRC-045 | State-machine step flow: repeated screens, no linear stepper | screen repeats with fresh inputs and counted rounds; noMoreSteps ends with Return | no stepper or Step i of n | prcLoop (STATE_MACHINE) | pass (chromium) | |
| PRC-046 | Saved bulk load profiles: save, choose for a later load, delete | Save As stores the mapping (SQL); choosing the profile restores the mapping and is named on review; Delete removes it (SQL) | a duplicate name is refused | SavedBulkLoadProfileMetaDataProvider on owned saved_bulk_load_profile tables | pass (chromium) | [#660](https://github.com/QRun-IO/qqq/issues/660) |
| PRC-047 | A run with no frontend screens completes with a completion screen | completion heading and message; backend effect persisted (SQL) | | prcQuickTask (backend step only) | pass (chromium) | |
| PRC-048 | Process-field possible values load and search through the v1 API | the process combobox loads its options from the v1 process route; the chosen value reaches the process and persists; a v1 search returns the exact match | an unknown process field is 404 | prcComponents | pass (chromium) | [#699](https://github.com/QRun-IO/qqq/issues/699) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| PRC-049 | Processes run on the v1 process routes (upload, step, back step, records, metadata) | a bulk load uploads its file, maps, reviews, steps back (isStepBack=true) and inserts through /qqq/v1/processes/... and /qqq/v1/metaData/process/...; the inserted row is in the database; this session can cancel its own run | an unsafe upload filename is 400 before anything is stored; an unknown run is refused; (another session's run is refused 403 by records and cancel - backend unit tests) | person.bulkInsert, prcComponents | pass (chromium) | [#699](https://github.com/QRun-IO/qqq/issues/699) [#406](https://github.com/QRun-IO/qqq/issues/406) [#543](https://github.com/QRun-IO/qqq/issues/543) |
| PRC-050 | Processes the instance adds to every query and record screen (Material processNamesToAddToAllQueryAndViewScreens) | v1 /metaData names the process in supplementalInstanceMetaData.materialDashboard (the only supplemental entry v1 publishes; the unversioned route's javalin server configuration stays off v1); casey receives it with no table and in no app (API); the person query Actions menu lists it last, after a divider, and runs it on the selected rows with tableName=person (SQL); the record actions menu of qryBin (no processes of its own) runs it on that record (SQL); the phone record action sheet runs it on that record (SQL) | a user whose metadata omits it (alice): not offered on query or record screens and its run page is not found (no SQL rows); noProcesses persona: absent from the metadata, not offered on query or record screens, and the backend refuses it (403, no SQL rows); a user whose metadata omits the process gets an empty v1 processNamesToAddToAllQueryAndViewScreens list (no hidden process names); the v1 and unversioned init routes refuse the process for a user without permission | ProcessesFixtures.java prcTagRecords (no table, in no app) named by MaterialDashboardInstanceMetaData; a metadata customizer lists it only for the sample user casey, so other areas keep their menus; person, qryBin | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| PRC-051 | Dependent possible values on a process screen: the choices follow the screen's other values | every search posts the screen values as the values map of the v1 JSON body; choices narrow to the chosen Category and change when it changes; the chosen id is recorded (SQL) and its label shows on the result screen | Category empty: the unset variable makes no matches for a form, shown as No options found; the backend refuses an out-of-filter id lookup and returns only the filtered choices | prcSpecimenPick specimenId (prcSpecimen source filtered on category = ${input.category}) | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| PRC-058 | Saved bulk load profile menu (Material SavedBulkLoadProfiles): new profile, update after the confirm, rename, save as a copy, delete, unsaved-change count with its list, Reset All Changes, New Bulk Load Profile, Reset to Empty or Suggested Mapping | a new profile is named in the Save New Bulk Load Profile dialog and Enter saves it (SQL row owned by the user); the heading names it; a change shows 1 Unsaved Change with the list of changes; Save asks Update Existing Bulk Load Profile and Enter confirms; the same row holds the new mapping (SQL); Rename keeps the row id under the new name; Save As stores a separate copy, which is then in use; a saved profile's changes are listed (header row, file column) and Reset All Changes restores its mapping; New Bulk Load Profile leaves the profile for an empty mapping; Reset to Suggested Mapping and Empty Mapping replace the mapping, and the suggested one is what the review screen shows | Enter in the delete dialog does not delete (SQL count unchanged); only the Delete button does, and the screen starts a new, empty mapping | enricher person.bulkInsert; SavedBulkLoadProfileMetaDataProvider on owned saved_bulk_load_profile tables | pass (chromium) | [#726](https://github.com/QRun-IO/qqq/issues/726) |
| PRC-059 | Saved bulk load profile ownership: yours vs shared with you, and only the owner may save, rename or delete a profile | the query process returns both profiles with their owners; the menu lists alice's under Your Saved Bulk Load Profiles and bob's under Bulk Load Profiles Shared with you; on bob's profile Save, Rename and Delete are disabled with the not-owner reason, the changes list says why, and no Save link is offered; Save As is enabled; alice saves her own copy (SQL row owned by sample:alice) | bob's row is unchanged (label, owner and mapping, SQL) and no store or delete request named it; the backend has no ownership lock on saved bulk load profiles, so the UI is the only guard, as in Material | sample users alice and bob; bob's profile stored through storeSavedBulkLoadProfile and shared READ_ONLY with alice in shared_saved_bulk_load_profile | pass (chromium) | [#726](https://github.com/QRun-IO/qqq/issues/726) |
| PRC-060 | Bulk load file mapping form fidelity: field help, column tooltips, repeated-header warning, header-row toggle re-mapping by header name, scroll to the first error, bulk-edit Key Fields / Fields To Update and the unmapped key field error | help for the header row, layout and table key fields shows under each and describes its control; a mapped column's tooltip names its fields; a repeated header is flagged and not offered as a column; without a header row columns are letters; a mapping to the repeated header is cleared with a warning when headers come back; off and on again the step posts header names (First Name, Last Name, Email) and the first Email column is inserted (SQL); an unmappable submit brings the first field error into view; bulk edit shows Key Fields (Select table key fields to continue.) and Fields To Update (Clear if empty only there); choosing Id names it as an unmapped key field until a column is chosen; the matched records update (SQL) | no step request is sent while a field has no column | enricher person.bulkInsert and person.bulkEditWithFile; ProcessesFixtures.primeBulkLoadHelp help content keyed process:person.bulkInsert;field:hasHeaderRow\|layout and process:person.bulkEditWithFile;field:tableKeyFields | pass (chromium) | [#726](https://github.com/QRun-IO/qqq/issues/726) |
| PRC-061 | Typed bulk load default values, searchable value mapping and the grouped, searchable Add Fields menu (Material QHierarchyAutoComplete) | the Person default value is chosen by searching person possible values and shows its label; Birth Date takes a date; Is Employed takes Yes or No; the Add Fields menu groups Pet and Pet Note, searches, disables mapped fields with a tooltip and adds the WIDE note field twice (Pet Note: Note (1) and (2)); species values are mapped by searching the species possible values; the pets, their person, species and birth date and both notes each are inserted (SQL) | a default value left empty is refused with A value is required. before any step request | enricher pet.bulkInsert (pet with its notes association, WIDE layout) and person.bulkInsert | pass (chromium) | [#726](https://github.com/QRun-IO/qqq/issues/726) |
| PRC-062 | Composite INPUT_FIELD metadata editors share process validation and multipart submission | Choose a labeled possible value, edit currency and multiline code, update a widget filter without changing columns, upload BLOB and FILE_UPLOAD data and verify backend-read content; Desktop wheel scrolling over a plain numeric block preserves its value; intentional keyboard edits and touch input changes are returned by the backend process review | Required uploads block submission; code-editor Enter does not submit the process | prcBlockEditors | pass (chromium) | [#728](https://github.com/QRun-IO/qqq/issues/728) |
| PRC-063 | INPUT_FIELD autofocus and trimmed Enter values on subsequent process screens | Plain and metadata editors receive configured focus, placeholders and previous process values; Enter saves trimmed text verified in SQL; ordinary result screens receive heading focus | First blur does not show premature validation; required whitespace cannot advance either input screen | prcInputFocus | pass (chromium) | [#728](https://github.com/QRun-IO/qqq/issues/728) |
| PRC-064 | DATE_TIME process inputs display local time and submit UTC instants | Unchanged seeded timestamps preserve seconds and instant across plain/shared blocks and regular form fields, including the repeated autumn DST hour; Edited timestamps crossing daylight saving submit UTC values verified from backend SQL; TIME inputs in plain/shared blocks and forms retain seconds for unchanged and edited submissions, verified through SQL readback. | Zoned values must not disappear from datetime-local controls or lose their timezone on submission | prcInputDateTimes with browser timezone America/New_York | pass (chromium) | [#728](https://github.com/QRun-IO/qqq/issues/728) |
| PRC-065 | Configured Google Drive shared-folder selection and process submission | Select a shared-drive folder and submit ID, name, token and other required input through the real process; independently verify database outcome | Reject a file and retry; cancel a replacement selection clears the destination and token; missing required note cannot advance | prcDrive with owned Google SDK script responses and real configured backend CSP | pass (chromium) | [#704](https://github.com/QRun-IO/qqq/issues/704) |
| PRC-066 | INPUT_FIELD case behaviors preserve caret and submitted process values | Backend uppercase/lowercase metadata transforms a middle-of-value edit without moving the caret; Enter and Submit persist exact values verified through independent SQL; Multiline TEXT case conversion preserves the middle-edit caret and inserted newline; Submit persists the exact transformed text through independent SQL; Unicode case expansion preserves continued typing in STRING and TEXT fields; submitted values are verified through independent SQL; Explicitly configured PASSWORD case behaviors preserve masking and show/hide values; Enter and Submit persist Unicode-expanded values verified through independent SQL | Typing alone does not submit or persist the process; Multiline Enter does not submit or navigate away | prcInputCase / prcTextCase | pass (chromium) | [#728](https://github.com/QRun-IO/qqq/issues/728) |
## Querying, relationships, saved views, export and bulk selection
| ID | Feature | Scenarios | Negative | Fixture | Result | Issues |
|---|---|---|---|---|---|---|
| QRY-001 | Record list renders metadata labels and all records in the default primary-key-descending order | Headers use field labels; Rows equal SQL select ordered by id desc; Possible-value cells show labels, booleans Yes/No, money/decimals formatted | Null boolean renders empty, not No; Hidden and heavy (TEXT) fields are not columns | QueryFixtures.java qryItem (8 rows, every field type) | pass (chromium, firefox, webkit) | [#686](https://github.com/QRun-IO/qqq/issues/686) [#691](https://github.com/QRun-IO/qqq/issues/691) |
| QRY-002 | Pagination with Material page sizes and URL state | Page 2 shows the remaining rows; Page sizes 10/25/50/100/250; Page and size survive reload via URL | Next is disabled on the last page | sample (person, pet, petNote, carrier, petSpecies) carrier (11 rows) | pass (chromium, firefox, webkit) | [#686](https://github.com/QRun-IO/qqq/issues/686) |
| QRY-003 | Column header sort cycles ascending, descending and default; sort kept in the URL | Ascending and descending match SQL order by; Sort persists across reload | Third click returns to the PK-descending default | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | [#686](https://github.com/QRun-IO/qqq/issues/686) |
| QRY-004 | Column visibility and order persist per table | Hide/show and reorder columns; Order and visibility survive reload; Show/hide all per table group | Another table keeps its own columns | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | |
| QRY-005 | Column widths, density and card view | Resized width persists; Density changes row height; Card view shows the same records | Keyboard resize separator exposes its value | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | |
| QRY-006 | Opening a record and returning keeps the query; refresh shows backend changes | Filter, sort and page are restored after Back; Refresh shows a row changed through the API | No stale cached rows after refresh | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | |
| QRY-007 | A failed query reports the backend error with a retry | Backend error text shown; Retry reloads after the failure clears | No rows from the failed request are shown | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | |
| QRY-010 | Text operators match Material labels and backend semantics | equals, does not equal, contains, starts/ends with, is any of, is like, is empty/not empty; results equal SQL | does not equal includes empty values (NOT_EQUALS_OR_IS_NULL); Incomplete criteria are not sent | QueryFixtures.java qryItem.name/code | pass (chromium, firefox, webkit) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-011 | Number operators including ranges and lists | equals, greater/less (or equal), is between, is any of; results equal SQL; Values sent as numbers | not between and none of exclude the bounds/list | QueryFixtures.java qryItem.quantity/price | pass (chromium, firefox, webkit) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-012 | Date operators and relative date expressions | is/after/before/between with dates; today, N days ago, start of this/last month send backend expression JSON and match SQL | Clearing an expression restores a plain value input | QueryFixtures.java qryItem.receivedDate (relative to today) | pass (chromium, firefox, webkit) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-013 | Date-time operators convert local input to UTC and support relative values | Local input sent as UTC instant; now and N hours ago expressions match SQL | Grid shows formatted date-times, not raw ISO | QueryFixtures.java qryItem.checkedAt | pass (chromium, firefox, webkit) | [#684](https://github.com/QRun-IO/qqq/issues/684) [#679](https://github.com/QRun-IO/qqq/issues/679) |
| QRY-014 | Boolean, blob and long-text operators | equals yes / equals no / is empty for booleans; blob is empty / not empty; long text contains | Blob offers no value operators | QueryFixtures.java qryItem.isActive/photo/notes | pass (chromium, firefox, webkit) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-015 | Possible-value selectors for table and enum sources show labels and send ids | is / is any of / is none of with label chips; Link-loaded ids render labels; Results equal SQL | Search with no match shows no options | QueryFixtures.java qryItem.ownerId (person), speciesId (petSpecies enum) | pass (chromium, firefox, webkit) | [#684](https://github.com/QRun-IO/qqq/issues/684) [#667](https://github.com/QRun-IO/qqq/issues/667) |
| QRY-016 | AND/OR and nested groups combine as declared | AND group, OR group and nested subfilter match SQL | Removing the last criterion clears the filter | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-017 | Filters live in the URL: reload, back/forward and Material-style JSON links | Reload keeps the filter; Back/forward switches filters; Plain JSON filter link opens filtered | Malformed filter param falls back to no filter | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-018 | Backend-only operators from links render and filter correctly | Each operator from a link renders its label and returns the SQL rows | FALSE matches no records; builder does not crash | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-019 | Quick search combines with the advanced filter | Search term ANDs with Is Active = yes | Clearing search restores the filtered rows | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | [#686](https://github.com/QRun-IO/qqq/issues/686) |
| QRY-020 | One-side exposed join: labelled columns, filters and sorts match SQL | Join column 'Owner: First Name' hidden by default, shown on demand; Join filter and sort match SQL LEFT JOIN | Joins only sent when used; rows without a match are kept | QueryFixtures.java qryItem -> person (MANY_TO_ONE) | pass (chromium, firefox, webkit) | [#686](https://github.com/QRun-IO/qqq/issues/686) [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-021 | Many-side exposed join repeats rows and reports the distinct count | Repeated rows per note; Count shows (N distinct) | Row selection dedupes repeated ids | QueryFixtures.java qryItem -> qryItemNote (ONE_TO_MANY) | pass (chromium, firefox, webkit) | [#686](https://github.com/QRun-IO/qqq/issues/686) |
| QRY-022 | Column statistics aggregate the filtered rows | Value distribution and count/distinct/sum/avg/min/max equal SQL under the filter | Sort by value/count | QueryFixtures.java qryItem (QUERY_STATS) | pass (chromium, firefox, webkit) | [#691](https://github.com/QRun-IO/qqq/issues/691) |
| QRY-023 | Tables without QUERY_STATS offer no column statistics | No statistics button on headers | Process not launched | sample (person, pet, petNote, carrier, petSpecies) person | pass (chromium, firefox, webkit) | [#691](https://github.com/QRun-IO/qqq/issues/691) |
| QRY-030 | Selection menu: this page, full query result, first N, clear | Banners and counts for page, all and subset | Subset dialog rejects invalid N | sample (person, pet, petNote, carrier, petSpecies) carrier | pass (chromium, firefox, webkit) | [#689](https://github.com/QRun-IO/qqq/issues/689) |
| QRY-031 | Bulk Edit launches with the checked record ids | recordIds param equals checked rows | No selection shows 'No records were selected to Bulk Edit.' | sample (person, pet, petNote, carrier, petSpecies) carrier | pass (chromium, firefox, webkit) | [#689](https://github.com/QRun-IO/qqq/issues/689) |
| QRY-032 | Bulk Delete of the full query result sends the whole filter | filterJSON has no skip/limit; backend recordCount equals SQL count | Current page paging is not sent | sample (person, pet, petNote, carrier, petSpecies) carrier (page 2) | pass (chromium, firefox, webkit) | [#689](https://github.com/QRun-IO/qqq/issues/689) |
| QRY-033 | First-N selection under a filter launches with that limit | filterJSON limit N with the filter and sort | N larger than the result is capped | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | [#689](https://github.com/QRun-IO/qqq/issues/689) |
| QRY-034 | Actions menu offers bulk load, bulk processes and table processes | Bulk Load/Edit/Edit With File/Delete then table processes; Launch carries the selection | Process min/max record checks block launch with a message | sample (person, pet, petNote, carrier, petSpecies) person | pass (chromium, firefox, webkit) | [#689](https://github.com/QRun-IO/qqq/issues/689) [#662](https://github.com/QRun-IO/qqq/issues/662) |
| QRY-035 | Viewers get no create, bulk or process actions and the backend refuses them | No Create, bulk or write process entries | Direct insert/delete API calls return 403 | viewer persona on sample (person, pet, petNote, carrier, petSpecies) | pass (chromium, firefox, webkit) | [#689](https://github.com/QRun-IO/qqq/issues/689) [#671](https://github.com/QRun-IO/qqq/issues/671) |
| QRY-040 | CSV export has visible columns in order and exactly the filtered, sorted rows | File name, headers and rows equal SQL; Join columns exported | Hidden columns excluded | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | [#688](https://github.com/QRun-IO/qqq/issues/688) |
| QRY-041 | XLSX and JSON exports carry the same rows | XLSX sheet and JSON array equal SQL rows | JSON keys limited to exported fields | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | [#688](https://github.com/QRun-IO/qqq/issues/688) |
| QRY-042 | Nothing to export disables the formats | Empty filter result disables CSV/XLSX/JSON | No export request sent | QueryFixtures.java qryItem | pass (chromium, firefox, webkit) | [#688](https://github.com/QRun-IO/qqq/issues/688) |
| QRY-043 | Backend refuses exports of unreadable tables | Export of a readable table succeeds | Export of pet returns 403 without data | noPets persona | pass (chromium, firefox, webkit) | [#688](https://github.com/QRun-IO/qqq/issues/688) |
| QRY-050 | Your saved views are listed and open on their own route | 'Your Saved Views' lists backend views; /savedView/{id} applies filter/columns | Empty group message | sample sharing demo (Alice People View) | pass (chromium, firefox, webkit) | [#687](https://github.com/QRun-IO/qqq/issues/687) |
| QRY-051 | Saving a new view stores filter, sort, columns and page size | saved_view row viewJson matches Material RecordQueryView | Unsaved-changes indicator clears after save | sample (person, pet, petNote, carrier, petSpecies) person | pass (chromium, firefox, webkit) | [#687](https://github.com/QRun-IO/qqq/issues/687) |
| QRY-052 | Update, rename and delete your view | Update keeps unrelated view JSON; Rename and delete change the saved_view row | Reset All Changes restores the stored view | sample (person, pet, petNote, carrier, petSpecies) person | pass (chromium, firefox, webkit) | [#687](https://github.com/QRun-IO/qqq/issues/687) |
| QRY-053 | Duplicate names and unknown views are reported without changes | Duplicate name error shown in the dialog | Unknown view id shows an error; saved_view unchanged | sample (person, pet, petNote, carrier, petSpecies) person | pass (chromium, firefox, webkit) | [#687](https://github.com/QRun-IO/qqq/issues/687) |
| QRY-054 | A view shared with you opens read-only and the backend refuses changes | Listed under 'Views Shared with you' for bob; Opens with its filter | Owner-only actions disabled; storeSavedView by bob refused | QueryFixtures.java (Alice People View shared with sample:bob READ_ONLY) | pass (chromium, firefox, webkit) | [#687](https://github.com/QRun-IO/qqq/issues/687) |
| QRY-060 | Variant table asks for a variant, queries with it and remembers it | Picker lists /variants; Rows equal the chosen store; Choice remembered and changeable; export uses it | Filter narrows options | QueryFixtures.java qryStock on qryVariantMemory (qryStore) | pass (chromium, firefox, webkit) | [#690](https://github.com/QRun-IO/qqq/issues/690) |
| QRY-061 | Dismissing the variant prompt queries nothing; backend requires a variant | Prompt shown with a choose button | No query sent; API query without a variant fails | QueryFixtures.java qryStock | pass (chromium, firefox, webkit) | [#690](https://github.com/QRun-IO/qqq/issues/690) |
| QRY-062 | A table without count, export or write capabilities offers none of them | Rows listed without a total; Export disabled with explanation; No create or bulk actions | Backend insert refused | QueryFixtures.java qryLedger (no TABLE_INSERT/UPDATE/DELETE/EXPORT/COUNT) | pass (chromium, firefox, webkit) | [#671](https://github.com/QRun-IO/qqq/issues/671) [#688](https://github.com/QRun-IO/qqq/issues/688) |
| QRY-063 | Enum-backed Pet Species table is read-only | Metadata omits write capabilities; no Create or bulk writes | Insert returns 403, not 500 | sample (person, pet, petNote, carrier, petSpecies) petSpecies | pass (chromium, firefox, webkit) | [#692](https://github.com/QRun-IO/qqq/issues/692) |
| QRY-064 | Backend refuses count and export for a table without those capabilities | Query still succeeds | v1 count, legacy count and export return 403 | QueryFixtures.java qryLedger | pass (chromium, firefox, webkit) | [#693](https://github.com/QRun-IO/qqq/issues/693) |
| REL-001 | Possible-value cells show the related record label and link to it | Owner cell shows the person label; Link opens /app/person/{id} | Enum values are not links | sample (person, pet, petNote, carrier, petSpecies) pet.personId | pass (chromium, firefox, webkit) | [#691](https://github.com/QRun-IO/qqq/issues/691) |
| REL-002 | Parent lists exactly its own children; View All opens the filtered child list | Panel rows equal SQL children; View All filters /app/pet and Back returns | Parent without children shows an empty panel | sample (person, pet, petNote, carrier, petSpecies) person -> pets | pass (chromium, firefox, webkit) | [#686](https://github.com/QRun-IO/qqq/issues/686) |
| REL-003 | Adding a child from its parent links it and shows it immediately | New pet stored with person_id of the parent; Panel refreshes | Join field not editable; no other rows changed | sample (person, pet, petNote, carrier, petSpecies) person -> pets | pass (chromium, firefox, webkit) | |
| REL-004 | Three-level associations: each pet shows only its own notes | Notes equal SQL per pet | Other pets' notes absent | sample (person, pet, petNote, carrier, petSpecies) person -> pet -> petNote | pass (chromium, firefox, webkit) | |
| REL-005 | Editing and deleting a child leaves no stale parent, list or unexpected rows | Renamed pet shown in parent panel and list; Deleting a pet removes its notes | Other rows unchanged | sample (person, pet, petNote, carrier, petSpecies) pet, petNote | pass (chromium, firefox, webkit) | |
| REL-006 | Denied child reads show an unavailable panel without data or actions | Panel says related records are unavailable | No data, View All or Create; API query 403 | noPets persona; qryShelter whose associated qryShelterPet is denied with DenyBehavior.DISABLED | pass (chromium, firefox, webkit) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| REL-007 | Aliased and composite associations bind to their widgets and list the right members | childRecordList and rowBuilder show SQL-matching members; Composite join on code + review date | Household without members shows empty | QueryFixtures.java qryHousehold/qryMember (members, care group / primary, scheduled reviews) | pass (chromium, firefox, webkit) | |
| REL-008 | Adding through a composite association stores every join value | New member stored with household_code and review_date from the parent | Aliased association unaffected; other rows unchanged | QueryFixtures.java qryHousehold/qryMember | pass (chromium, firefox, webkit) | |
| REL-009 | Deleting a parent removes associated children and leaves other parents intact | Person and its pets removed | Other pets and notes unchanged; list reflects SQL | sample (person, pet, petNote, carrier, petSpecies) person -> pets | pass (chromium, firefox, webkit) | |
| QRY-090 | Column menu sorts, pins, opens a field filter and hides a column | Sort matches SQL; Pin right is reflected in the grid; Filter opens the field editor; Hide removes the column | | QueryFixtures.java qryItem | pass (chromium) | [#716](https://github.com/QRun-IO/qqq/issues/716) |
| QRY-091 | Column menu copies page or full query values in backend sort order | Page copy contains the first ten ids; Full copy contains all eleven ids | | sample carrier (11 rows) | pass (chromium) | [#716](https://github.com/QRun-IO/qqq/issues/716) |
| QRY-092 | Column chooser searches, groups, counts and marks changed layouts | Search narrows fields; Groups collapse and expand; Hiding a field marks the columns dirty; Keyboard opening focuses the popup and Tab reaches search; Explicit close and Escape restore Columns focus; outside click keeps the clicked control focused | Unknown search shows an empty message | QueryFixtures.java qryItem | pass (chromium) | [#716](https://github.com/QRun-IO/qqq/issues/716) |
| QRY-093 | Last saved view and unsaved edits survive reopening and reload | Dirty filter survives reload; Table route opens the last saved view; Reset restores the backend view; Displayed filter updates the URL before a delayed page navigation completes; immediate reload preserves it, along with unrelated parameters, hash and history length | | sample Alice People View | pass (chromium) | [#717](https://github.com/QRun-IO/qqq/issues/717) |
| QRY-094 | Counted quick views render and open their saved-view route | Count reflects the backend filter; Quick-view button opens the filtered view | | sample Alice People View marked as a quick view in H2 | pass (chromium) | [#717](https://github.com/QRun-IO/qqq/issues/717) |
| QRY-095 | A new query defaults to fifty rows per page | Fifty visible rows; Pagination reports fifty of the full count | | QueryFixtures.java qryManyRow (1,234 rows) | pass (chromium) | [#717](https://github.com/QRun-IO/qqq/issues/717) |
| QRY-096 | A saved view reconciles removed fields and warns the user | Obsolete criteria and sort are removed before querying; Warning names the removed field; Heading names the active saved view | | sample Alice People View with retiredField references in H2 | pass (chromium) | [#717](https://github.com/QRun-IO/qqq/issues/717) |
| QRY-001 | Record list renders metadata labels and all records in the default primary-key-descending order | Headers use field labels; Rows equal SQL select ordered by id desc; Possible-value cells show labels, booleans Yes/No, money/decimals formatted; Original Next uniform row backgrounds in light and dark mode, with opaque pinned cells and selection highlighting | Null boolean renders empty, not No; Hidden and heavy (TEXT) fields are not columns | QueryFixtures.java qryItem (8 rows, every field type) | pass (chromium) | [#686](https://github.com/QRun-IO/qqq/issues/686) [#691](https://github.com/QRun-IO/qqq/issues/691) |
| QRY-002 | Pagination with Material page sizes and URL state | Page 2 shows the remaining rows; Page sizes 10/25/50/100/250; Page and size survive reload via URL | Next is disabled on the last page | sample (person, pet, petNote, carrier, petSpecies) carrier (11 rows) | pass (chromium) | [#686](https://github.com/QRun-IO/qqq/issues/686) |
| QRY-003 | Column header sort cycles ascending, descending and default; sort kept in the URL | Ascending and descending match SQL order by; Sort persists across reload | Third click returns to the PK-descending default | QueryFixtures.java qryItem | pass (chromium) | [#686](https://github.com/QRun-IO/qqq/issues/686) |
| QRY-004 | Column visibility and order persist per table | Hide/show and reorder columns; Order and visibility survive reload; Show/hide all per table group | Another table keeps its own columns | QueryFixtures.java qryItem | pass (chromium) | |
| QRY-005 | Column widths, density and card view | Resized width persists; Density changes row height; Card view shows the same records | Keyboard resize separator exposes its value | QueryFixtures.java qryItem | pass (chromium) | |
| QRY-006 | Opening a record and returning keeps the query; refresh shows backend changes | Filter, sort and page are restored after Back; Refresh shows a row changed through the API | No stale cached rows after refresh | QueryFixtures.java qryItem | pass (chromium) | |
| QRY-007 | A failed query reports the backend error with a retry | Backend error text shown; Retry reloads after the failure clears; Query error text and the front error notification meet text contrast in light, dark and configured application themes; Retry repeats the backend request; hover remains readable; Dismiss hides the error; resetting the invalid filter restores SQL-verified records | No rows from the failed request are shown | QueryFixtures.java qryItem | pass (chromium) | |
| QRY-010 | Text operators match Material labels and backend semantics | equals, does not equal, contains, starts/ends with, is any of, is like, is empty/not empty; results equal SQL | does not equal includes empty values (NOT_EQUALS_OR_IS_NULL); Incomplete criteria are not sent | QueryFixtures.java qryItem.name/code | pass (chromium) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-011 | Number operators including ranges and lists | equals, greater/less (or equal), is between, is any of; results equal SQL; Values sent as numbers | not between and none of exclude the bounds/list | QueryFixtures.java qryItem.quantity/price | pass (chromium) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-012 | Date operators and relative date expressions | is/after/before/between with dates; today, N days ago, start of this/last month send backend expression JSON and match SQL | Clearing an expression restores a plain value input | QueryFixtures.java qryItem.receivedDate (relative to today) | pass (chromium) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-013 | Date-time operators convert local input to UTC and support relative values | Local input sent as UTC instant; now and N hours ago expressions match SQL | Grid shows formatted date-times, not raw ISO | QueryFixtures.java qryItem.checkedAt | pass (chromium) | [#684](https://github.com/QRun-IO/qqq/issues/684) [#679](https://github.com/QRun-IO/qqq/issues/679) |
| QRY-014 | Boolean, blob and long-text operators | equals yes / equals no / is empty for booleans; blob is empty / not empty; long text contains | Blob offers no value operators | QueryFixtures.java qryItem.isActive/photo/notes | pass (chromium) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-015 | Possible-value selectors for table and enum sources show labels and send ids | is / is any of / is none of with label chips; Link-loaded ids render labels; Results equal SQL | Search with no match shows no options | QueryFixtures.java qryItem.ownerId (person), speciesId (petSpecies enum) | pass (chromium) | [#684](https://github.com/QRun-IO/qqq/issues/684) [#667](https://github.com/QRun-IO/qqq/issues/667) |
| QRY-016 | AND/OR and nested groups combine as declared | AND group, OR group and nested subfilter match SQL | Removing the last criterion clears the filter | QueryFixtures.java qryItem | pass (chromium) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-017 | Filters live in the URL: reload, back/forward and Material-style JSON links | Reload keeps the filter; Back/forward switches filters; Plain JSON filter link opens filtered | Malformed filter param falls back to no filter | QueryFixtures.java qryItem | pass (chromium) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-018 | Backend-only operators from links render and filter correctly | Each operator from a link renders its label and returns the SQL rows | FALSE matches no records; builder does not crash | QueryFixtures.java qryItem | pass (chromium) | [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-019 | Quick search combines with the advanced filter | Search term ANDs with Is Active = yes | Clearing search restores the filtered rows | QueryFixtures.java qryItem | pass (chromium) | [#686](https://github.com/QRun-IO/qqq/issues/686) |
| QRY-020 | One-side exposed join: labelled columns, filters and sorts match SQL | Join column 'Owner: First Name' hidden by default, shown on demand; Join filter and sort match SQL LEFT JOIN | Joins only sent when used; rows without a match are kept | QueryFixtures.java qryItem -> person (MANY_TO_ONE) | pass (chromium) | [#686](https://github.com/QRun-IO/qqq/issues/686) [#684](https://github.com/QRun-IO/qqq/issues/684) |
| QRY-021 | Many-side exposed join repeats rows and reports the distinct count | Repeated rows per note; Count shows (N distinct) | Row selection dedupes repeated ids | QueryFixtures.java qryItem -> qryItemNote (ONE_TO_MANY) | pass (chromium) | [#686](https://github.com/QRun-IO/qqq/issues/686) |
| QRY-022 | Column statistics aggregate the filtered rows | Value distribution and count/distinct/sum/avg/min/max equal SQL under the filter | Sort by value/count | QueryFixtures.java qryItem (QUERY_STATS) | pass (chromium) | [#691](https://github.com/QRun-IO/qqq/issues/691) |
| QRY-023 | Tables without QUERY_STATS offer no column statistics | No statistics button on headers | Process not launched | sample (person, pet, petNote, carrier, petSpecies) person | pass (chromium) | [#691](https://github.com/QRun-IO/qqq/issues/691) |
| QRY-030 | Selection menu: this page, full query result, first N, clear | Banners and counts for page, all and subset; Touch opens and toggles the density menu without synthetic trigger focus dismissing it; Enter, Escape and outside search focus retain their normal behavior; Pinned selection targets remain exposed and respond at both edges for every density | Subset dialog rejects invalid N | sample (person, pet, petNote, carrier, petSpecies) carrier | pass (chromium) | [#689](https://github.com/QRun-IO/qqq/issues/689) |
| QRY-031 | Bulk Edit launches with the checked record ids | recordIds param equals checked rows | No selection shows 'No records were selected to Bulk Edit.' | sample (person, pet, petNote, carrier, petSpecies) carrier | pass (chromium) | [#689](https://github.com/QRun-IO/qqq/issues/689) |
| QRY-032 | Bulk Delete of the full query result sends the whole filter | filterJSON has no skip/limit; backend recordCount equals SQL count | Current page paging is not sent | sample (person, pet, petNote, carrier, petSpecies) carrier (page 2) | pass (chromium) | [#689](https://github.com/QRun-IO/qqq/issues/689) |
| QRY-033 | First-N selection under a filter launches with that limit | filterJSON limit N with the filter and sort | N larger than the result is capped | QueryFixtures.java qryItem | pass (chromium) | [#689](https://github.com/QRun-IO/qqq/issues/689) |
| QRY-034 | Actions menu offers bulk load, bulk processes and table processes | Bulk Load/Edit/Edit With File/Delete then table processes; Launch carries the selection | Process min/max record checks block launch with a message | sample (person, pet, petNote, carrier, petSpecies) person | pass (chromium) | [#689](https://github.com/QRun-IO/qqq/issues/689) [#662](https://github.com/QRun-IO/qqq/issues/662) |
| QRY-035 | Viewers get no create, bulk or process actions and the backend refuses them | No Create, bulk or write process entries | Direct insert/delete API calls return 403 | viewer persona on sample (person, pet, petNote, carrier, petSpecies) | pass (chromium) | [#689](https://github.com/QRun-IO/qqq/issues/689) [#671](https://github.com/QRun-IO/qqq/issues/671) |
| QRY-040 | CSV export has visible columns in order and exactly the filtered, sorted rows | File name, headers and rows equal SQL; Join columns exported | Hidden columns excluded | QueryFixtures.java qryItem | pass (chromium) | [#688](https://github.com/QRun-IO/qqq/issues/688) |
| QRY-041 | XLSX and JSON exports carry the same rows | XLSX sheet and JSON array equal SQL rows | JSON keys limited to exported fields | QueryFixtures.java qryItem | pass (chromium) | [#688](https://github.com/QRun-IO/qqq/issues/688) |
| QRY-042 | Nothing to export disables the formats | Empty filter result disables CSV/XLSX/JSON | No export request sent | QueryFixtures.java qryItem | pass (chromium) | [#688](https://github.com/QRun-IO/qqq/issues/688) |
| QRY-043 | Backend refuses exports of unreadable tables | Export of a readable table succeeds | Export of pet returns 403 without data | noPets persona | pass (chromium) | [#688](https://github.com/QRun-IO/qqq/issues/688) |
| QRY-050 | Your saved views are listed and open on their own route | 'Your Saved Views' lists backend views; /savedView/{id} applies filter/columns | Empty group message | sample sharing demo (Alice People View) | pass (chromium) | [#687](https://github.com/QRun-IO/qqq/issues/687) |
| QRY-051 | Saving a new view stores filter, sort, columns and page size | saved_view row viewJson matches Material RecordQueryView | Unsaved-changes indicator clears after save | sample (person, pet, petNote, carrier, petSpecies) person | pass (chromium) | [#687](https://github.com/QRun-IO/qqq/issues/687) |
| QRY-052 | Update, rename and delete your view | Update keeps unrelated view JSON; Rename and delete change the saved_view row | Reset All Changes restores the stored view | sample (person, pet, petNote, carrier, petSpecies) person | pass (chromium) | [#687](https://github.com/QRun-IO/qqq/issues/687) |
| QRY-053 | Duplicate names and unknown views are reported without changes | Duplicate name error shown in the dialog | Unknown view id shows an error; saved_view unchanged | sample (person, pet, petNote, carrier, petSpecies) person | pass (chromium) | [#687](https://github.com/QRun-IO/qqq/issues/687) |
| QRY-054 | A view shared with you opens read-only and the backend refuses changes | Listed under 'Views Shared with you' for bob; Opens with its filter | Owner-only actions disabled; storeSavedView by bob refused | QueryFixtures.java (Alice People View shared with sample:bob READ_ONLY) | pass (chromium) | [#687](https://github.com/QRun-IO/qqq/issues/687) |
| QRY-060 | Variant table asks for a variant, queries with it and remembers it | Picker lists /variants; Rows equal the chosen store; Choice remembered and changeable; export uses it | Filter narrows options | QueryFixtures.java qryStock on qryVariantMemory (qryStore) | pass (chromium) | [#690](https://github.com/QRun-IO/qqq/issues/690) |
| QRY-061 | Dismissing the variant prompt queries nothing; backend requires a variant | Prompt shown with a choose button | No query sent; API query without a variant fails | QueryFixtures.java qryStock | pass (chromium) | [#690](https://github.com/QRun-IO/qqq/issues/690) |
| QRY-062 | A table without count, export or write capabilities offers none of them | Rows listed without a total; Export disabled with explanation; No create or bulk actions | Backend insert refused | QueryFixtures.java qryLedger (no TABLE_INSERT/UPDATE/DELETE/EXPORT/COUNT) | pass (chromium) | [#671](https://github.com/QRun-IO/qqq/issues/671) [#688](https://github.com/QRun-IO/qqq/issues/688) |
| QRY-063 | Enum-backed Pet Species table is read-only | Metadata omits write capabilities; no Create or bulk writes | Insert returns 403, not 500 | sample (person, pet, petNote, carrier, petSpecies) petSpecies | pass (chromium) | [#692](https://github.com/QRun-IO/qqq/issues/692) |
| QRY-064 | Backend refuses count and export for a table without those capabilities | Query still succeeds | v1 count, legacy count and export return 403 | QueryFixtures.java qryLedger | pass (chromium) | [#693](https://github.com/QRun-IO/qqq/issues/693) |
| QRY-065 | Export streams from the v1 export route | the export menu posts {format, filename, fieldNames, filter} to /qqq/v1/table/{table}/export and downloads every row; the v1 route streams the same rows as CSV | a table without the export capability is refused (403) by the v1 route | qryItem, qryLedger | pass (chromium) | [#699](https://github.com/QRun-IO/qqq/issues/699) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| QRY-066 | Variant options come from the v1 variants route | the variant picker lists the stores from GET /qqq/v1/table/{table}/variants; the v1 route lists every option with its type and id; a table without variants lists none | an unknown table is refused (403) | qryStock (qryStore options) | pass (chromium) | [#699](https://github.com/QRun-IO/qqq/issues/699) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| QRY-067 | Go To opens a record by its primary key or a configured unique key (Material gotoFieldNames) | v1 table metadata carries supplementalMetaData.materialDashboard.gotoFieldNames (API); Go To... on the query toolbar and the record view header offers the primary key, then each key, labelled by field labels; primary key, single-field and two-field keys each open the one matching record (EQUALS query, id from SQL); no match shows "Record not found."; a partial key matching two bins shows "More than 1 record was found..." (counts from SQL); Enter in an input submits its option; Escape closes and returns focus to the Go To button; a table with GET but no QUERY capability opens Go To automatically and it cannot be dismissed | a table without gotoFieldNames has no Go To button on its query or record screen; no navigation on no match or several matches | QueryFixtures.java qryBin (gotoFieldNames [[code], [aisle, shelf]]), qryLocker (GET without QUERY), qryItem (no Go To) | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| QRY-068 | Default grid columns follow the table sections (section order, each field once), not the metadata field order (Material bySection order) | Headers read Id, Name, Code, Price, Quantity; Rows equal SQL select ordered by id desc | The metadata field-map order (price first) is not used | QueryFixtures.java qryOrdered (qry_item rows; fields declared price, quantity, code, id, name; sections identity [id, name, code], details [price], stock [quantity]) | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| QRY-069 | Pagination numbers are locale formatted in the shown range and the total | Page 5 at 250 per page reads Showing 1,001–1,234 of 1,234; The page shows exactly the last 234 ids by SQL | No unformatted range such as 1001–1234 | QueryFixtures.java qryManyRow (1,234 rows) | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| QRY-070 | Query screen keyboard shortcuts n (new record), r (refresh the query), f (open the filter builder) | r sends a new query request and shows a value changed through the API (SQL confirms the change); f opens the Advanced Filters panel and a second f keeps it open; n opens the create form | keys typed in quick search are text (no filter panel, no navigation); viewer persona: n does nothing (f still works); the backend refuses the insert (403, SQL unchanged) | sample person | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| QRY-071 | Processes launched from a variant table run with the chosen variant (tableVariant on init and every step) and return to the query | Bulk Delete of the South store's full query result counts its one record; Every init and step request carries tableVariant {type: qryStore, id: 2}; Return comes back to the variant query, which is empty for South while North keeps its two rows | Without the variant the backend could not read the variant table's records | QueryFixtures.java qryStock (memory backend keyed by qryStore variants) | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| QRY-080 | Basic mode displays default quick filters from table metadata and applies their criteria | First Name quick filter finds the SQL-matching records; Type-specific defaults gate Boolean until chosen, default possible values to any-of and date-time to after; A multi-value chip summarizes extra values; A saved Basic view restores its criterion and SQL rows | No application-specific field names are embedded in the UI; Cancel discards an un-applied quick-filter draft | sample person and QueryFixtures.java qryItem | pass (chromium) | [#715](https://github.com/QRun-IO/qqq/issues/715) |
| QRY-081 | Add quick filter accepts metadata fields and remembers a chosen field | A field offered by metadata appears as a chip after selection and reload; Joined and virtual fields query SQL-backed records | Field choice is scoped to this table | sample person and QueryFixtures.java qryItem/qryCaseItem | pass (chromium) | [#715](https://github.com/QRun-IO/qqq/issues/715) |
| QRY-082 | Basic mode cannot simplify repeated or nested advanced conditions and explains why | Repeated Name conditions disable Basic with a focusable reason tooltip; URL criteria become chips; OR criteria open Advanced with a reason | Clicking Basic does not discard either condition | QueryFixtures.java qryItem | pass (chromium) | [#715](https://github.com/QRun-IO/qqq/issues/715) |
| QRY-083 | Advanced query shows and removes individual conditions (Next inline editor) | Name and Quantity conditions filter SQL rows; deleting Name in the inline editor preserves Quantity | Removing one condition does not reset the whole query | QueryFixtures.java qryItem | pass (chromium) | [#715](https://github.com/QRun-IO/qqq/issues/715) |
| QRY-084 | Sort picker changes field and direction; clearing filters requires confirmation and keeps the sort | Name ascending order matches SQL before and after confirmed clear; Condition status explains missing and complete values on keyboard focus | Cancel leaves conditions in place | QueryFixtures.java qryItem | pass (chromium) | [#715](https://github.com/QRun-IO/qqq/issues/715) |
| QRY-085 | Weekday criteria use the metadata-enabled date function and ISO weekday values | Selecting a weekday sends WeekdayOfDate and returns the SQL-matching rows; Date-time weekday sends WeekdayOfDateTime with configured UTC arguments and returns matching rows | The weekday list uses names rather than bare numbers | QueryFixtures.java qryItem with dated rows and Material weekday settings; AcceptanceSampleServer.java H2 date functions | pass (chromium) | [#718](https://github.com/QRun-IO/qqq/issues/718) |
| QRY-086 | Relative date presets expose their evaluated date and filter records | Seven days ago renders a live date and returns the recent SQL-backed rows; A date-time preset retains its relative expression, evaluates on focus and returns recent rows | A relative value remains an expression instead of fixed text | QueryFixtures.java qryItem with relative dates | pass (chromium) | [#718](https://github.com/QRun-IO/qqq/issues/718) |
| QRY-087 | Bulk pasted values become a multi-value filter | Two pasted record names return the matching SQL-backed rows | The dialog previews each value before adding it | QueryFixtures.java qryItem | pass (chromium) | [#718](https://github.com/QRun-IO/qqq/issues/718) |
| QRY-088 | Instance query help accompanies the bulk paste dialog | The dialog displays the configured help text | Help text comes from instance metadata | QueryFixtures.java instance-level QUERY_SCREEN help | pass (chromium) | [#718](https://github.com/QRun-IO/qqq/issues/718) |
| QRY-089 | String filter input honors field case behavior from v1 metadata | Uppercase typing is normalized and queries the lowercase stored code | The normalized value remains visible in the input | QueryFixtures.java qryCaseItem code with TO_LOWER_CASE | pass (chromium) | [#718](https://github.com/QRun-IO/qqq/issues/718) |
| QRY-008 | Phone card list: readable records, touch-sized selection, query selections and paging | One card per record with the SQL ids and names, values wrap instead of being cut off; Tapping the 44 px label around a card checkbox selects it without opening the record; A full-query selection checks every card; paging and rows per page work on cards; Tapping a card opens its record | No horizontal page scroll and no control below 44 x 44 px | sample (person, pet, petNote, carrier, petSpecies) carrier (11 rows), 412 px touch viewport | pass (chromium) | [#708](https://github.com/QRun-IO/qqq/issues/708) |
| QRY-009 | Phone filter sheet fits the viewport, scrolls inside and manages focus | Filter opens a modal sheet that takes focus; Many conditions scroll inside the sheet, which stays inside the viewport; Cards behind the sheet show the filtered records (SQL); Escape or Close closes the sheet and returns focus to Filter, which counts the conditions | No horizontal page scroll and no control below 44 x 44 px in the sheet | QueryFixtures.java qryItem, 412 px touch viewport | pass (chromium) | [#708](https://github.com/QRun-IO/qqq/issues/708) |
| QRY-024 | Column configuration is reachable and usable by touch on phones and tablets | The panel opens inside the viewport with touch-sized controls; Hiding a column removes it from the cards (phone) or the grid (tablet) and persists; Escape closes the panel and returns focus to its button | The panel never runs off the left edge of a phone | QueryFixtures.java qryItem | pass (chromium) | [#708](https://github.com/QRun-IO/qqq/issues/708) |
| QRY-036 | Bulk actions launch from a phone card selection | Checked cards show the selection banner; The Actions menu fits the phone; Bulk Edit launches with exactly the checked ids | No horizontal page scroll while the menu is open | QueryFixtures.java qryItem, 412 px touch viewport | pass (chromium) | [#708](https://github.com/QRun-IO/qqq/issues/708) |
| QRY-055 | Saved views and export menus fit the phone and are touch-sized | The saved views menu lists your view inside the viewport; Escape closes it; The export menu fits the phone and downloads a CSV | No horizontal page scroll and no control below 44 x 44 px in either menu | sample sharing demo (Alice People View), 412 px touch viewport | pass (chromium) | [#708](https://github.com/QRun-IO/qqq/issues/708) |
| REL-001 | Possible-value cells show the related record label and link to it | Owner cell shows the person label; Link opens /app/person/{id} | Enum values are not links | sample (person, pet, petNote, carrier, petSpecies) pet.personId | pass (chromium) | [#691](https://github.com/QRun-IO/qqq/issues/691) |
| REL-002 | Parent lists exactly its own children; View All opens the filtered child list | Panel rows equal SQL children; View All filters /app/pet and Back returns | Parent without children shows an empty panel | sample (person, pet, petNote, carrier, petSpecies) person -> pets | pass (chromium) | [#686](https://github.com/QRun-IO/qqq/issues/686) |
| REL-003 | Adding a child from its parent links it and shows it immediately | New pet stored with person_id of the parent; Panel refreshes | Join field not editable; no other rows changed | sample (person, pet, petNote, carrier, petSpecies) person -> pets | pass (chromium) | |
| REL-004 | Three-level associations: each pet shows only its own notes | Notes equal SQL per pet | Other pets' notes absent | sample (person, pet, petNote, carrier, petSpecies) person -> pet -> petNote | pass (chromium) | |
| REL-005 | Editing and deleting a child leaves no stale parent, list or unexpected rows | Renamed pet shown in parent panel and list; Deleting a pet removes its notes | Other rows unchanged | sample (person, pet, petNote, carrier, petSpecies) pet, petNote | pass (chromium) | |
| REL-006 | Denied child reads show an unavailable panel without data or actions | Panel says related records are unavailable | No data, View All or Create; API query 403 | noPets persona; qryShelter whose associated qryShelterPet is denied with DenyBehavior.DISABLED | pass (chromium) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| REL-007 | Aliased and composite associations bind to their widgets and list the right members | childRecordList and rowBuilder show SQL-matching members; Composite join on code + review date | Household without members shows empty | QueryFixtures.java qryHousehold/qryMember (members, care group / primary, scheduled reviews) | pass (chromium) | |
| REL-008 | Adding through a composite association stores every join value | New member stored with household_code and review_date from the parent | Aliased association unaffected; other rows unchanged | QueryFixtures.java qryHousehold/qryMember | pass (chromium) | |
| REL-009 | Deleting a parent removes associated children and leaves other parents intact | Person and its pets removed | Other pets and notes unchanged; list reflects SQL | sample (person, pet, petNote, carrier, petSpecies) person -> pets | pass (chromium) | |
## Records, fields, editors, adornments, validation and persistence
| ID | Feature | Scenarios | Negative | Fixture | Result | Issues |
|---|---|---|---|---|---|---|
| REC-001 | Record view shows the record label, tiered sections and backend-formatted values | label heading; T1 header fields; T2 section cards; currency, grouped integer, Yes/No, date | values match independent SQL readback | prime-test-database.sql (person 1) | pass (chromium, firefox, webkit) | |
| REC-002 | Empty values render a placeholder; hidden fields and hidden sections are never shown | NULL date shows an em dash; hidden field absent from view and from the GET payload; hidden section not rendered | hidden value stays stored (SQL) | person 4 (NULL birth date); recordLab hiddenCode + hidden legacy section | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) [#680](https://github.com/QRun-IO/qqq/issues/680) |
| REC-003 | Record view of a missing record shows Record Not Found and a way back | not-found panel; back to the table list | 404 read is the only failed request; no generic error toast | person 999 (absent) | pass (chromium, firefox, webkit) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| REC-004 | Record view tabs and list view expose every visible section | Overview shows all T2 sections; a section tab shows only that section; list view shows every section | | fieldLab record created in the test | pass (chromium, firefox, webkit) | |
| REC-005 | Create form validates required fields in the browser before any request | required markers and messages for each required field | no insert request, no row | person | pass (chromium, firefox, webkit) | |
| REC-006 | Create persists the entered values and opens the new record | success toast; navigates to /app/person/<new id>; view shows the entered values | non-editable fields are not offered on create; row matches SQL readback | person | pass (chromium, firefox, webkit) | |
| REC-007 | Backend enforces required fields when the UI is bypassed | direct insert without Name is rejected with the backend message | no row written | fieldLab | pass (chromium, firefox, webkit) | |
| REC-008 | Cancel from create and edit, with the unsaved-changes guard | clean cancel returns immediately; dirty cancel asks Stay/Leave; Stay keeps typed values; Leave discards | no insert or update request; no row created or changed | person | pass (chromium, firefox, webkit) | |
| REC-009 | Edit loads stored values, saves only what changed, and shows the updated record | fields prefilled from the record; Save disabled until a change; view shows the new value without reload | only the changed field is submitted; other columns unchanged in SQL | person 1 | pass (chromium, firefox, webkit) | [#672](https://github.com/QRun-IO/qqq/issues/672) [#676](https://github.com/QRun-IO/qqq/issues/676) |
| REC-010 | Edit shows non-editable fields read-only and never submits them | read-only controls with the stored values | controls are disabled; create date unchanged in SQL after save | person 1 (id, createDate, modifyDate) | pass (chromium, firefox, webkit) | [#680](https://github.com/QRun-IO/qqq/issues/680) [#537](https://github.com/QRun-IO/qqq/issues/537) |
| REC-011 | Edit of a missing record shows the load failure instead of a form | error message, no form fields | 404 is the only failed request | person 999 | pass (chromium, firefox, webkit) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| REC-012 | Delete asks for confirmation, removes the record and returns to the list without errors | dialog names the record; Cancel keeps it; Delete removes it and returns to the list; success toast | no Resource not found toast; no 404 read of the deleted record; row gone in SQL | person 5 | pass (chromium, firefox, webkit) | [#541](https://github.com/QRun-IO/qqq/issues/541) |
| REC-013 | Copy prefills a new record from the source and saves it as a separate record | source values prefilled without the key; save creates a new row and opens it; source unchanged | unique-key conflict on copy shows the backend message and keeps the form; download-URL files are not copied | person 1; recordLab 1 | pass (chromium, firefox, webkit) | [#672](https://github.com/QRun-IO/qqq/issues/672) |
| REC-014 | STRING and TEXT editors round-trip single and multi-line text | single-line input; multi-line textarea keeps line breaks in view and SQL | | fieldLab | pass (chromium, firefox, webkit) | |
| REC-015 | INTEGER, LONG and DECIMAL editors keep exact values | LONG beyond 2^53 saved and shown exactly; DECIMAL with 4 places | fractional LONG rejected in the browser; backend rejects a fractional LONG sent directly | fieldLab | pass (chromium, firefox, webkit) | [#679](https://github.com/QRun-IO/qqq/issues/679) |
| REC-016 | BOOLEAN editor applies the metadata default and saves true/false | create form starts at the default (true); switching to false persists false; view shows Yes/No | unchanged boolean is not rewritten on edit | fieldLab (booleanValue default true) | pass (chromium, firefox, webkit) | [#680](https://github.com/QRun-IO/qqq/issues/680) |
| REC-017 | DATE and TIME editors round-trip values and clearing | date input value and view; time input value and backend 12-hour display; clearing a date stores NULL | | fieldLab | pass (chromium, firefox, webkit) | |
| REC-018 | DATE_TIME is entered and shown in the viewer's time zone and stored in UTC | typed local time stored as the UTC instant; view shows yyyy-MM-dd hh:mm:ss AM EDT; edit form shows the local time | unchanged date-times are not rewritten; non-editable date-times read-only | fieldLab; browser zone America/New_York | pass (chromium, firefox, webkit) | [#679](https://github.com/QRun-IO/qqq/issues/679) |
| REC-019 | Zoned DATE_TIME display behaviors (fixed zone and per-record zone) | fixed America/Chicago display; display in the record's own timeZone; daylight-saving boundary | invalid zone falls back to UTC | fieldLab | pass (chromium, firefox, webkit) | [#679](https://github.com/QRun-IO/qqq/issues/679) |
| REC-020 | HTML field edits in the rich-text editor and renders sanitized | formatted text rendered on the view | script content is not executed | fieldLab htmlValue | pass (chromium, firefox, webkit) | [#676](https://github.com/QRun-IO/qqq/issues/676) |
| REC-021 | PASSWORD is masked on read and never overwritten by the mask | view shows the mask; typing a new password saves it | editing another field keeps the stored password (SQL) | fieldLab passwordValue | pass (chromium, firefox, webkit) | [#672](https://github.com/QRun-IO/qqq/issues/672) |
| REC-022 | BLOB upload and download without a download adornment | file chosen on create is stored byte-for-byte; view offers the bytes as a download | | fieldLab blobValue | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-023 | Dynamic default values (user id, create day, modify day, explicit NONE) | user id and dates filled on create; modify day updated on edit, create day kept | NONE leaves the value empty | fieldLab | pass (chromium, firefox, webkit) | |
| REC-024 | Max-length policies: truncate, ellipsis, pass-through and reject | truncate, ellipsis and pass-through stored as declared | reject shows the backend message, keeps the form and writes nothing | fieldLab (maxLength 8) | pass (chromium, firefox, webkit) | [#680](https://github.com/QRun-IO/qqq/issues/680) |
| REC-025 | Case and whitespace normalization on save | upper, lower, trim variants, remove-all-whitespace and unchanged stored as declared | | fieldLab | pass (chromium, firefox, webkit) | |
| REC-026 | Numeric range policies: reject, exclusive reject, inclusive clip and exclusive clip | clip to 0/100 and 0.01/99.99 | out-of-range and exclusive-boundary values show the backend message and write nothing | fieldLab | pass (chromium, firefox, webkit) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| REC-027 | Unique keys are enforced with the backend message on create and edit | distinct values save | duplicate Name on create; duplicate normalized key on edit; form keeps values, row unchanged | fieldLab (name, normalizedKey) | pass (chromium, firefox, webkit) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| REC-028 | LINK adornment: record links and URL links | toRecordFromTable opens the referenced record; URL opens in a new tab (target _blank) | a non-http value is not turned into a link | recordLab (ownerId, website) | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-029 | CHIP adornment colors and icons by value | label is the display value; color from color.<value>; icon from icon.<value> | undeclared value uses the default color | recordLab status | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-030 | SIZE adornment sets the query grid column width | column is 100px wide | undeclared columns keep the default width | recordLab shortCode (width small) | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-031 | CODE_EDITOR adornment: code view with formatting and a code editor on forms | view shows the code and its language; Format JSON / Reset Format; editor saves edited code | invalid JSON reports a formatting error | recordLab config (json) | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-032 | RENDER_HTML adornment renders sanitized HTML | markup rendered | script removed and not executed | recordLab htmlNote | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-033 | REVEAL adornment masks, reveals and copies the value | masked by default; reveal shows the exact stored value; hide masks again | | recordLab apiToken (PASSWORD + REVEAL) | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-034 | FILE_DOWNLOAD adornment links to the stored file with its name | file name from the companion field; file name from the format; open and download return the stored bytes | record without a file shows no link | recordLab attachment (heavy, fileNameField) and notesFile (fileNameFormat) | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-035 | FILE_UPLOAD adornment formats, replacing and removing a stored file | drag-and-drop zone and button formats; current file shown on edit; new file replaces bytes and file name | removing the file clears bytes and name | recordLab attachment (dragAndDrop), notesFile (button) | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) [#680](https://github.com/QRun-IO/qqq/issues/680) |
| REC-036 | TOOLTIP adornment shows its text on the value | tooltip on hover and keyboard focus | | recordLab hint (staticText) | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) [#685](https://github.com/QRun-IO/qqq/issues/685) |
| REC-037 | WIDGET adornment renders the field value as the named widget | widget content from the record | the value is not offered as an editable field | recordLab summaryWidget | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-038 | ERROR adornment highlights the value | value shown with error styling | | recordLab problem | pass (chromium, firefox, webkit) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-039 | Field help content follows screen roles and formats | view: READ_SCREENS text and VIEW_SCREEN HTML; edit: WRITE_SCREENS markdown rendered and EDIT_SCREEN text; create: INSERT_SCREEN text | help for other screens is not shown | recordLab title/website/ownerId help | pass (chromium, firefox, webkit) | [#681](https://github.com/QRun-IO/qqq/issues/681) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| REC-040 | Section help content on the record view | HTML help under the section heading | | recordLab presentation section | pass (chromium, firefox, webkit) | [#681](https://github.com/QRun-IO/qqq/issues/681) |
| REC-041 | Table help content slots are delivered in table metadata | v1 table metadata carries the slot help | | recordLab summary slot | pass (chromium, firefox, webkit) | |
| REC-042 | Audit history lists who changed what, newest first | edit then open Audit: message, user and per-field sentences; audit rows independently verified in SQL | unaudited record shows no audits | recordLab (FIELD audits) | pass (chromium, firefox, webkit) | [#678](https://github.com/QRun-IO/qqq/issues/678) |
| REC-043 | Audit history without process permission reads the audit table | same entries via the audit table query | no process request is made | recordLab; persona noProcesses | pass (chromium, firefox, webkit) | [#678](https://github.com/QRun-IO/qqq/issues/678) |
| REC-044 | Record label format is used for the record's title and dialogs | heading; delete dialog; audit title | | recordLab (Lab: %s) | pass (chromium, firefox, webkit) | [#678](https://github.com/QRun-IO/qqq/issues/678) |
| REC-045 | Possible-value editor shows stored labels, looks up defaults and selects by keyboard | edit shows the stored label, not the id; create default shows its label; keyboard search and select saves the id | | recordLab status/ownerId | pass (chromium, firefox, webkit) | [#667](https://github.com/QRun-IO/qqq/issues/667) |
| REC-046 | Read-only persona sees no write actions and the backend refuses writes | no Edit, Copy or Delete; create and edit URLs show the permission message | direct insert, update and delete are 403 and change nothing | persona viewer | pass (chromium, firefox, webkit) | |
| REC-047 | Forms are labelled and keyboard operable | every control has a label; keyboard-only create saves the record | invalid fields expose aria-invalid and a described error | person | pass (chromium, firefox, webkit) | [#676](https://github.com/QRun-IO/qqq/issues/676) |
| REC-048 | Server validation errors show the backend message once and keep the form | alert and toast show the backend message; typed values remain | no generic Something went wrong toast | fieldLab | pass (chromium, firefox, webkit) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| REC-001 | Record view shows the record label, tiered sections and backend-formatted values | label heading; T1 header fields; T2 section cards; currency, grouped integer, Yes/No, date | values match independent SQL readback | prime-test-database.sql (person 1) | pass (chromium) | |
| REC-002 | Empty values render a placeholder; hidden fields and hidden sections are never shown | NULL date shows an em dash; hidden field absent from view and from the GET payload; hidden section not rendered | hidden value stays stored (SQL) | person 4 (NULL birth date); recordLab hiddenCode + hidden legacy section | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) [#680](https://github.com/QRun-IO/qqq/issues/680) |
| REC-003 | Record view of a missing record shows Record Not Found and a way back | not-found panel; back to the table list | 404 read is the only failed request; no generic error toast | person 999 (absent) | pass (chromium) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| REC-004 | Record view tabs and list view expose every visible section | Overview shows all T2 sections; Overview field rows retain original Next typography and spacing in light and dark themes; a section tab shows only that section; list view shows every section; Arrow/Home/End keys move tab focus; Enter/Space activates a named panel with one tab stop; phone accordion supports keyboard activation | | fieldLab record created in the test | pass (chromium) | |
| REC-005 | Create form validates required fields in the browser before any request | required markers and messages for each required field | no insert request, no row | person | pass (chromium) | |
| REC-006 | Create persists the entered values and opens the new record | success toast; navigates to /app/person/<new id>; view shows the entered values | non-editable fields are not offered on create; row matches SQL readback | person | pass (chromium) | |
| REC-007 | Backend enforces required fields when the UI is bypassed | direct insert without Name is rejected with the backend message | no row written | fieldLab | pass (chromium) | |
| REC-008 | Cancel from create and edit, with the unsaved-changes guard | clean cancel returns immediately; dirty cancel asks Stay/Leave; Stay keeps typed values; Leave discards | no insert or update request; no row created or changed | person | pass (chromium) | |
| REC-009 | Edit loads stored values, saves only what changed, and shows the updated record | fields prefilled from the record; Save disabled until a change; view shows the new value without reload | only the changed field is submitted; other columns unchanged in SQL | person 1 | pass (chromium) | [#672](https://github.com/QRun-IO/qqq/issues/672) [#676](https://github.com/QRun-IO/qqq/issues/676) |
| REC-010 | Edit shows non-editable fields read-only and never submits them | read-only controls with the stored values | controls are disabled; create date unchanged in SQL after save | person 1 (id, createDate, modifyDate) | pass (chromium) | [#680](https://github.com/QRun-IO/qqq/issues/680) [#537](https://github.com/QRun-IO/qqq/issues/537) |
| REC-011 | Edit of a missing record shows the load failure instead of a form | error message, no form fields | 404 is the only failed request | person 999 | pass (chromium) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| REC-012 | Delete asks for confirmation, removes the record and returns to the list without errors | dialog names the record; Cancel keeps it; Delete removes it and returns to the list; success toast | no Resource not found toast; no 404 read of the deleted record; row gone in SQL | person 5 | pass (chromium) | [#541](https://github.com/QRun-IO/qqq/issues/541) |
| REC-013 | Copy prefills a new record from the source and saves it as a separate record | source values prefilled without the key; Full Copy loads the descendant draft on selection; returning to Base Copy preserves the base form; save creates a new row and opens it; source unchanged | unique-key conflict on copy shows the backend message and keeps the form; download-URL files are not copied | person 1; recordLab 1 | pass (chromium) | [#672](https://github.com/QRun-IO/qqq/issues/672) |
| REC-014 | STRING and TEXT editors round-trip single and multi-line text | single-line input; multi-line textarea keeps line breaks in view and SQL | | fieldLab | pass (chromium) | |
| REC-015 | INTEGER, LONG and DECIMAL editors keep exact values | LONG beyond 2^53 saved and shown exactly; DECIMAL with 4 places | fractional LONG rejected in the browser; backend rejects a fractional LONG sent directly | fieldLab | pass (chromium) | [#679](https://github.com/QRun-IO/qqq/issues/679) |
| REC-016 | BOOLEAN editor applies the metadata default and saves true/false | create form starts at the default (true); switching to false persists false; view shows Yes/No | unchanged boolean is not rewritten on edit | fieldLab (booleanValue default true) | pass (chromium) | [#680](https://github.com/QRun-IO/qqq/issues/680) |
| REC-017 | DATE and TIME editors round-trip values and clearing | date input value and view; time input value and backend 12-hour display; clearing a date stores NULL | | fieldLab | pass (chromium) | |
| REC-018 | DATE_TIME is entered and shown in the viewer's time zone and stored in UTC | typed local time stored as the UTC instant; view shows yyyy-MM-dd hh:mm:ss AM EDT; edit form shows the local time | unchanged date-times are not rewritten; non-editable date-times read-only | fieldLab; browser zone America/New_York | pass (chromium) | [#679](https://github.com/QRun-IO/qqq/issues/679) |
| REC-019 | Zoned DATE_TIME display behaviors (fixed zone and per-record zone) | fixed America/Chicago display; display in the record's own timeZone; daylight-saving boundary | invalid zone falls back to UTC | fieldLab | pass (chromium) | [#679](https://github.com/QRun-IO/qqq/issues/679) |
| REC-020 | HTML field edits in the rich-text editor and renders sanitized | formatted text rendered on the view | script content is not executed | fieldLab htmlValue | pass (chromium) | [#676](https://github.com/QRun-IO/qqq/issues/676) |
| REC-021 | PASSWORD is masked on read and never overwritten by the mask | view shows the mask; typing a new password saves it | editing another field keeps the stored password (SQL) | fieldLab passwordValue | pass (chromium) | [#672](https://github.com/QRun-IO/qqq/issues/672) |
| REC-022 | BLOB upload and download without a download adornment | file chosen on create is stored byte-for-byte; view offers the bytes as a download | | fieldLab blobValue | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-023 | Dynamic default values (user id, create day, modify day, explicit NONE) | user id and dates filled on create; modify day updated on edit, create day kept | NONE leaves the value empty | fieldLab | pass (chromium) | |
| REC-024 | Max-length policies: truncate, ellipsis, pass-through and reject | truncate, ellipsis and pass-through stored as declared | reject shows the backend message, keeps the form and writes nothing | fieldLab (maxLength 8) | pass (chromium) | [#680](https://github.com/QRun-IO/qqq/issues/680) |
| REC-025 | Case and whitespace normalization on save | upper, lower, trim variants, remove-all-whitespace and unchanged stored as declared | | fieldLab | pass (chromium) | |
| REC-026 | Numeric range policies: reject, exclusive reject, inclusive clip and exclusive clip | clip to 0/100 and 0.01/99.99 | out-of-range and exclusive-boundary values show the backend message and write nothing | fieldLab | pass (chromium) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| REC-027 | Unique keys are enforced with the backend message on create and edit | distinct values save | duplicate Name on create; duplicate normalized key on edit; form keeps values, row unchanged | fieldLab (name, normalizedKey) | pass (chromium) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| REC-028 | LINK adornment: record links and URL links | toRecordFromTable opens the referenced record; URL opens in a new tab (target _blank) | a non-http value is not turned into a link | recordLab (ownerId, website) | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-029 | CHIP adornment colors and icons by value | label is the display value; color from color.<value>; icon from icon.<value> | undeclared value uses the default color | recordLab status | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-030 | SIZE adornment sets the query grid column width | column is 100px wide | undeclared columns keep the default width | recordLab shortCode (width small) | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-031 | CODE_EDITOR adornment: code view with formatting and a code editor on forms | view shows the code and its language; Format JSON / Reset Format; editor saves edited code | invalid JSON reports a formatting error | recordLab config (json) | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-032 | RENDER_HTML adornment renders sanitized HTML | markup rendered | script removed and not executed | recordLab htmlNote | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-033 | REVEAL adornment masks, reveals and copies the value | masked by default; reveal shows the exact stored value; hide masks again | | recordLab apiToken (PASSWORD + REVEAL) | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-034 | FILE_DOWNLOAD adornment links to the stored file with its name | file name from the companion field; file name from the format; open and download return the stored bytes | record without a file shows no link | recordLab attachment (heavy, fileNameField) and notesFile (fileNameFormat) | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-035 | FILE_UPLOAD adornment formats, replacing and removing a stored file | drag-and-drop zone and button formats; current file shown on edit; new file replaces bytes and file name | removing the file clears bytes and name | recordLab attachment (dragAndDrop), notesFile (button) | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) [#680](https://github.com/QRun-IO/qqq/issues/680) |
| REC-036 | TOOLTIP adornment shows its text on the value | tooltip on hover and keyboard focus | | recordLab hint (staticText) | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) [#685](https://github.com/QRun-IO/qqq/issues/685) |
| REC-037 | WIDGET adornment renders the field value as the named widget | widget content from the record | the value is not offered as an editable field | recordLab summaryWidget | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-038 | ERROR adornment highlights the value | value shown with error styling | | recordLab problem | pass (chromium) | [#677](https://github.com/QRun-IO/qqq/issues/677) |
| REC-039 | Field help content follows screen roles and formats | view: READ_SCREENS text and VIEW_SCREEN HTML; edit: WRITE_SCREENS markdown rendered and EDIT_SCREEN text; create: INSERT_SCREEN text | help for other screens is not shown | recordLab title/website/ownerId help | pass (chromium) | [#681](https://github.com/QRun-IO/qqq/issues/681) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| REC-040 | Section help content on the record view | HTML help under the section heading | | recordLab presentation section | pass (chromium) | [#681](https://github.com/QRun-IO/qqq/issues/681) |
| REC-041 | Table help content slots are delivered in table metadata | v1 table metadata carries the slot help | | recordLab summary slot | pass (chromium) | |
| REC-042 | Audit history lists who changed what, newest first | edit then open Audit: message, user and per-field sentences; audit rows independently verified in SQL | unaudited record shows no audits | recordLab (FIELD audits) | pass (chromium) | [#678](https://github.com/QRun-IO/qqq/issues/678) |
| REC-043 | Audit history without process permission reads the audit table | same entries via the audit table query | no process request is made | recordLab; persona noProcesses | pass (chromium) | [#678](https://github.com/QRun-IO/qqq/issues/678) |
| REC-044 | Record label format is used for the record's title and dialogs | heading; delete dialog; audit title | | recordLab (Lab: %s) | pass (chromium) | [#678](https://github.com/QRun-IO/qqq/issues/678) |
| REC-045 | Possible-value editor shows stored labels, looks up defaults and selects by keyboard | edit shows the stored label, not the id; create default shows its label; keyboard search and select saves the id | | recordLab status/ownerId | pass (chromium) | [#667](https://github.com/QRun-IO/qqq/issues/667) |
| REC-046 | Read-only persona sees no write actions and the backend refuses writes | no Edit, Copy or Delete; create and edit URLs show the permission message | direct insert, update and delete are 403 and change nothing | persona viewer | pass (chromium) | |
| REC-047 | Forms are labelled and keyboard operable | every control has a label; keyboard-only create saves the record | invalid fields expose aria-invalid and a described error | person | pass (chromium) | [#676](https://github.com/QRun-IO/qqq/issues/676) |
| REC-048 | Server validation errors show the backend message once and keep the form | alert and toast show the backend message; typed values remain | no generic Something went wrong toast | fieldLab | pass (chromium) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| REC-049 | pages of a table outside the app tree use the table label in the document title and breadcrumbs | create page title is "Create Scheduled Report \| Scheduled Report \| QQQ Sample"; record view title is the record label, then Scheduled Report | the table name (scheduledReport) is not shown in the breadcrumbs | scheduledReport (memory, not in the app tree) for savedReport 1 | pass (chromium) | [#707](https://github.com/QRun-IO/qqq/issues/707) |
| REC-051 | Table-field possible values search and resolve through the v1 API | owner search sends a v1 POST with the search term and lists the exact match; ids and labels resolve to the exact option; the chosen owner persists | a source table the persona cannot read is refused (403) by the v1 route; an unknown field is 404 | recordLab | pass (chromium) | [#699](https://github.com/QRun-IO/qqq/issues/699) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| REC-050 | Record create, view, edit and delete run on the v1 record routes | the create/edit/delete flow sends POST, GET, PATCH and DELETE to /qqq/v1/table/... and persists each step; record-v1 associations are written and read back with includeAssociations | a missing required field is a 400 with the backend message and nothing is written; a read-only persona is refused (403) insert, update and delete on v1 and the row is unchanged | person, fieldLab, qryHousehold | pass (chromium) | [#699](https://github.com/QRun-IO/qqq/issues/699) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| REC-056 | Record view hash links: #audit, #/launchProcess={process} and #{section} anchors | #audit opens the audit dialog; closing it clears the hash and a reload does not reopen it; #/launchProcess=person.bulkEdit replaces the URL with the run for the record (init recordIds=2); Cancel returns to the record; #employmentInfo selects the Employment Info tab and scrolls it into view; a later hash change selects Dates | | sample person, person.bulkEdit | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| REC-057 | Create presets from links: #/createChild= opens a child create dialog over the record; /create#/defaultValues=/disabledFields= prefills and locks | the backend-format #/createChild=pet link on person 3 opens Add Pet with Person preset and disabled; Create closes the dialog, clears the hash, submits personId=3 and SQL shows the pet linked to person 3; /app/pet/create#/defaultValues={name, personId}/disabledFields={personId} prefills both, locks Person and saves them (SQL); /app/pet/create#defaultValues={speciesId} (no slash) prefills Species without locking | locked fields are disabled; unlocked preset fields stay editable | sample person -> pet (personId), petSpecies | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| REC-052 | Dependent possible values: a field whose filter uses ${input.otherField} offers only the choices matching the form's current values | every search posts the form values as the values map of the v1 JSON body; create: choices narrow to the chosen Category and change when it changes; edit: the stored Item label shows, a new Category changes the choices; the chosen id persists (SQL) and the record view shows its label | Category empty: the unset variable makes no matches for a form, shown as No options found; an item outside the filter is not offered, even when searched by name; the backend refuses an out-of-filter id lookup and returns only the filtered choices | recPvPick itemId (recPvItem source filtered on categoryId = ${input.categoryId}) | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| REC-053 | Table developer view shows the API Docs & Playground section for the table's application APIs | developer page for a table keeps its metadata sections and shows the API Docs & Playground section; with no application APIs it shows "This table is not available in any APIs." and no API or Version selectors; OpenAPI download and new-tab buttons remain fully visible before interaction at desktop, phone and 320px widths in light/dark modes; pointer/touch downloads yield the real specification and keyboard navigation opens its URL; specification labels fit wider application font metrics at desktop and phone widths without content clipping | a viewer persona sees the same empty state and cannot fetch any table spec | recordLab has no application APIs; person is exposed through the owned Acceptance API | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| REC-054 | Record developer view manages associated scripts: versions, new versions, test runs, logs, docs and script creation | versions list with CURRENT on the current revision and per-file code for the selected version; an older version offers Edit and Activate; save a new version with a commit message (SQL: new script_revision row, its file and script.current_script_revision_id; persists after reload); test run output and log lines equal the backend testScript response; a failing run shows the error the backend returns; logs list the script_log rows of the selected version with their lines; docs show the script type help text and sample code; create a script for an empty associated script field; local QQQ helper and keyword suggestions support keyboard and pointer/touch insertion, native undo/redo, both-theme accessibility, and SQL-verified revision persistence | a viewer cannot save or test a revision (no Edit or Test offered; the backend refuses storeScriptRevision and testScript); a viewer cannot create a script (no Create Script offered; the backend refuses the create route); a table without associated scripts shows none | scriptLab (RecordsFixtures: Alpha with script 101, revisions 101-102, logs 101-102; Beta without a script) on the scripts tables from WidgetsFixtures | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| REC-055 | Record view keyboard shortcuts n, e, c, d, a (Material) and their help text | e, c and n open edit, copy and create with the right values; a opens the audit history; d opens the delete confirmation, Escape keeps the row (SQL), confirming deletes it (SQL); ? lists Create a New Record, Edit/Copy/Delete/Audit the current Record with their keys | no shortcut fires while the audit or keyboard help dialog is open; keys typed in the Jump to input are text; viewer persona: n, e, c, d do nothing (a still works); the backend refuses insert and delete (403, SQL unchanged) | sample person | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| REC-058 | Record view on phones and tablets: the title stays readable, the header controls wrap onto their own row, and the phone action sheet fits, scrolls and keeps focus | The title stays on one line and never overlaps the view toggle, Audit or record actions; neither the page nor its main region scrolls sideways; The back link, value links and header controls are touch-sized (44 x 44 px) under a coarse pointer; The phone Actions sheet fits the screen, takes focus, keeps Tab and Shift+Tab inside, closes by Escape or a backdrop tap with focus back on its trigger, and runs Edit | A read-only user gets no phone Actions trigger (asserted in REC-046) | recordLab 1 (Lab: Alpha); phone (412 px touch), tablet (810 px touch) and desktop viewports | pass (chromium) | [#708](https://github.com/QRun-IO/qqq/issues/708) |
| REC-059 | Record forms on phones and tablets: every editor type fits without sideways scrolling, controls are touch-sized, possible-value lists open on screen and the phone action bar stays reachable | Create Field Lab: no sideways scroll, every control at least 44 x 44 px by touch; on a phone Save stays on screen; boolean, date, time, text and rich text values persist; Edit Record Lab: the owner list opens inside the screen with touch-sized options; the picked owner persists | | fieldLab create (every field type), recordLab 1 edit (possible values, code editor, password reveal, file uploads) | pass (chromium) | [#708](https://github.com/QRun-IO/qqq/issues/708) |
| REC-061 | An editable dynamicForm widget on a record form validates its fields and persists them in the declared JSON record field | the region field starts from stored JSON and appears in the record edit form; changing the region persists the new JSON in inputValues and survives reload | clearing the required region blocks the write; the transient region field is absent from the table multipart payload | accWidgetHost 1 with accHostRecordVariables and hidden inputValues field | pass (chromium) | [QRun-IO/qqq-frontend-next#722](https://github.com/QRun-IO/qqq-frontend-next/issues/722) |
| REC-062 | Inline enum possible values filter by label prefix without a lookup request; CHIP adornments style options and chosen IDs persist | v1 metadata exposes the inline enum and the edit form shows the held label; a label-prefix search filters options and the CHIP adornment colors and icons them; choosing High stores HIGH in SQL and shows High after reload | a substring that is not a prefix offers no options; the picker makes no possible-values request | recordLab priority field with an inline enum and CHIP adornment | pass (chromium) | [QRun-IO/qqq-frontend-next#721](https://github.com/QRun-IO/qqq-frontend-next/issues/721) |
| REC-060 | Material table and field form adjusters and field rules operate against the real QQQ sample server | v1 table and field metadata expose the Material supplemental adjuster settings; the table onLoad route supplies a note before render; the name onChange route changes a field label and clears its value; the field rule clears status | a backend lock message disables Save for a preset name | FormAdjusterFixtures.java adjusterLab table with a table onLoad adjuster, name onChange adjuster and clear-target field rule | pass (chromium) | [QRun-IO/qqq-frontend-next#720](https://github.com/QRun-IO/qqq-frontend-next/issues/720) |
| REC-064 | Record sections render qualified fields from readable exposed joins | record GET sends a selected LEFT join with qryItemJoinPerson; the joined person's first name appears in the Details section | unreadable join paths are removed from sections and GET descriptors by the same permission filter as query pages | QueryFixtures.java qryItem joins person and shows person.firstName in Details | pass (chromium) | [QRun-IO/qqq-frontend-next#722](https://github.com/QRun-IO/qqq-frontend-next/issues/722) |
| REC-063 | Record view, edit and copy load from the table variant selected on the query screen | Selecting South Store shows S-KIWI on the record view and edit/copy forms; Every South record GET includes tableVariant {type: qryStore, id: 2}; Switching to North Store shows N-APPLE for the same record ID with variant id 1 | The duplicate ID must not show North Store's N-APPLE value | QueryFixtures.java qryStock (same record ID in North and South variants) | pass (chromium) | [QRun-IO/qqq-frontend-next#722](https://github.com/QRun-IO/qqq-frontend-next/issues/722) |
| REC-065 | Unchanged DATE_TIME instants and precision survive edit and copy | Unrelated edit omits unchanged timestamp; Copy sends the original zoned instant and preserves source/copy SQL timestamps | Copy must not select the first occurrence of a repeated hour or truncate fractional seconds | fieldLab; browser America/New_York; repeated DST hour with subsecond precision | pass (chromium) | [#679](https://github.com/QRun-IO/qqq/issues/679) |
## Permissions, sessions, authentication and interaction quality
| ID | Feature | Scenarios | Negative | Fixture | Result | Issues |
|---|---|---|---|---|---|---|
| SEC-001 | Table hidden from a user (DenyBehavior.HIDDEN): absent from navigation, direct link loads nothing, backend refuses | noPets sees Person but no Pet / Pet Note in navigation; person record neither shows nor requests the hidden pet association | direct link /app/pet shows unknown resource with no data request; API query/get of pet returns 403, table metadata 404 | tests/acceptance/fixture/AcceptanceSampleServer.java personas (noPets) | pass (chromium, firefox, webkit) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-002 | Table disabled for a user (DenyBehavior.DISABLED): listed, but list/record/create/edit links explain the denial without loading data | admin lists the ledger rows; noPets sees the Security Ledger link | noPets list, record, create and edit links show the permission message and request no records; API query/get/insert/update return 403 and rows are unchanged | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityLedger, base name pet, noPets) | pass (chromium, firefox, webkit) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-003 | Insert permission denied: create not offered, create link refused, backend writes nothing | viewer list has no Create button and the dashboard has no Create quick action | /app/person/create shows the permission message with no form; POST /data/person returns 403 and the person count is unchanged | tests/acceptance/fixture/AcceptanceSampleServer.java personas (viewer) | pass (chromium, firefox, webkit) | |
| SEC-004 | Edit permission denied: edit not offered, edit link refused, backend changes nothing | viewer record view has no Edit action | /app/person/1/edit shows the permission message without loading the record; PUT /data/person/1 returns 403 and the name is unchanged | tests/acceptance/fixture/AcceptanceSampleServer.java personas (viewer) | pass (chromium, firefox, webkit) | |
| SEC-005 | Delete permission denied: delete not offered on record or bulk bar, bulk delete refused, backend deletes nothing | record view and bulk action bar have no Delete | /app/person.bulkDelete is unknown to the viewer; DELETE /data/person/1 and bulkDelete init return 403; the row remains | tests/acceptance/fixture/AcceptanceSampleServer.java personas (viewer) | pass (chromium, firefox, webkit) | |
| SEC-006 | Copy requires insert permission: copy not offered and copy link refused | viewer record view has no Copy action | /app/person/1/copy shows the permission message; nothing is inserted | tests/acceptance/fixture/AcceptanceSampleServer.java personas (viewer) | pass (chromium, firefox, webkit) | |
| SEC-007 | Process permission denied (hidden): processes not offered in navigation or launcher, links do not start them, backend refuses | viewer has no Actions menu; noProcesses' Actions menu offers only the table's bulk actions (table rights) and no standalone process; no process links; noProcesses keeps table rights (Create offered) | /app/clonePeople and /app/greetInteractive are unknown and make no process request; legacy and v1 process init return 403; no person is cloned | tests/acceptance/fixture/AcceptanceSampleServer.java personas (viewer, noProcesses) | pass (chromium, firefox, webkit) | |
| SEC-008 | Process disabled for a user (DenyBehavior.DISABLED): shown disabled, link explains the denial, backend refuses with no side effect | admin runs Security Audit and one audit row is written; viewer sees the audit tile disabled and no dashboard quick action for it | viewer direct link shows the permission message and never calls init; process init returns 403 and the audit log stays empty | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityAudit process) | pass (chromium, firefox, webkit) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-009 | Widget restrictions: hidden widgets absent, disabled widgets show a permission state without loading data, backend refuses | admin sees both pet widgets with their content; noPets sees the open bulletin widget | noPets: hidden widget absent, disabled widget shows "You do not have permission to view this data." and no /widget request; GET /widget/<denied> returns 403 | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityPetWidget, securityPetDisabledWidget) | pass (chromium, firefox, webkit) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-010 | App restrictions: hidden apps absent from navigation and unknown by link; disabled apps keep their children gated | admin sees both pet apps | noPets: Pet Vault App absent and /app/securityPetApp unknown; Pet Disabled App children stay permission-gated | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityPetApp hidden, securityPetDisabledApp disabled) | pass (chromium, firefox, webkit) | [#651](https://github.com/QRun-IO/qqq/issues/651) [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-011 | Record-level security (sharing demo): users see only their own or shared saved views/reports; direct links and writes to others are refused | alice lists and opens her own saved view | bob's list omits alice's view; bob's direct link shows not found; bob's GET/PUT/DELETE of alice's view are refused and the row is unchanged | tests/acceptance/fixture/AcceptanceSampleServer.java personas (alice, bob) | pass (chromium, firefox, webkit) | |
| SEC-012 | Record security lock on a data table: list filtered to the key, other users' records unreachable, forged owner refused | alice sees and edits only her notes (persisted) | bob's direct link to alice's note shows not found; bob's update/delete of alice's note and insert with alice as owner are refused; SQL unchanged | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityNote locked by sample user) | pass (chromium, firefox, webkit) | |
| SEC-013 | Field protection: hidden fields never shown or returned, password fields masked and never overwritten by the mask, heavy fields not loaded in lists | REVEAL password can be revealed on request; editing another field keeps the stored password | hidden value absent from list, record, edit form and API responses; masked password shows only the mask; the list query does not carry the heavy BLOB | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityVault) | pass (chromium, firefox, webkit) | [#672](https://github.com/QRun-IO/qqq/issues/672) |
| SEC-014 | Capability restrictions: tables without insert/update/delete capabilities offer none of them and refuse direct links | admin can list and view the archive | no Create/Edit/Delete offered; create and edit links explain the table cannot be changed; backend insert/update/delete are refused; SQL unchanged | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityArchive) | pass (chromium, firefox, webkit) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-015 | Permission revoked mid-session: stale UI actions are refused by the backend with a clear message, nothing is written, reload reflects the new rights | after reload the viewer rights are shown | saving an open edit form after revocation shows a permission error and the row is unchanged | tests/acceptance/fixture/AcceptanceSampleServer.java personas (admin then viewer) | pass (chromium, firefox, webkit) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| SEC-020 | MOCK sign-in: a deep link establishes the session and shows the session user from manageSession values | lands on the requested page with rows; user menu shows Alice (sample) / sample:alice | | tests/acceptance/fixture/AcceptanceSampleServer.java personas | pass (chromium, firefox, mobile, webkit) | [#332](https://github.com/QRun-IO/qqq/issues/332) [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-021 | Logout: POST /qqq/v1/logout ends the session, protected pages stay on login, Sign in returns to the page, per-user client data is cleared | Sign in returns to the original page | after logout a protected page (full load) stays on login and reads no data; a different user signing in on the same browser never sees the previous user's cached pages or recent records | tests/acceptance/fixture/AcceptanceSampleServer.java personas | pass (chromium, firefox, mobile, webkit) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-022 | Session expiry: 401 redirects to login with returnTo and re-authenticates back to the page (initial load and mid-use) | expired on first load returns to the page; expired mid-use returns to the clicked record | no hang on "Setting up anonymous session" | tests/acceptance/fixture/AcceptanceSampleServer.java personas (expired) | pass (chromium, firefox, mobile, webkit) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-023 | Denied sign-in: a 401 from manageSession keeps the user out with an explanation (no silent anonymous fallback) | | login shows "Sign-in was denied", no navigation or data is rendered | tests/acceptance/fixture/AcceptanceSampleServer.java personas | pass (chromium, firefox, webkit) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-024 | Post-login returnTo is limited to in-app paths (open-redirect guard) | in-app returnTo with query string is honoured | absolute, protocol-relative, backslash and javascript: targets land on /app | tests/acceptance/fixture/AcceptanceSampleServer.java personas | pass (chromium, firefox, webkit) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-025 | OAUTH2 authorization code + PKCE sign-in with an owned OIDC provider (discovery, authorize, callback, backend token exchange, session, reload resume) | authorize request carries S256 challenge, state, client id, scopes and /token redirect; backend exchanges code + verifier with client secret; user_session row stored; provider identity is displayed; reload resumes without the provider | | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium, firefox, webkit) | [#670](https://github.com/QRun-IO/qqq/issues/670) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| SEC-026 | OAUTH2 sign-in failures: provider denial, tampered state and failed code exchange are reported and create no session | | access_denied shows the provider denial; a state mismatch is rejected before any token exchange; a rejected code exchange shows the failure; no user_session row | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium, firefox, webkit) | [#670](https://github.com/QRun-IO/qqq/issues/670) |
| SEC-027 | OAUTH2 logout: backend session deleted, provider end-session called, next visit requires provider sign-in | user_session row deleted and provider session ended | protected page after logout returns to the provider login | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium, firefox, webkit) | [#670](https://github.com/QRun-IO/qqq/issues/670) [#674](https://github.com/QRun-IO/qqq/issues/674) |
| SEC-028 | OAUTH2 session revoked server-side mid-use: 401 sends the user through the provider and back to the page | returns to the same page after provider sign-in | revoked session gets 401, not stale data | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium, firefox, webkit) | [#670](https://github.com/QRun-IO/qqq/issues/670) |
| SEC-029 | AUTH_0 authorization code + PKCE with an owned Auth0-compatible provider (browser token exchange, backend JWT validation via JWKS) | sign-in and data access through the Auth0 module; logout returns through the provider | provider denial is reported | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=AUTH_0 | pass (chromium, firefox, webkit) | [#670](https://github.com/QRun-IO/qqq/issues/670) |
| SEC-001 | Table hidden from a user (DenyBehavior.HIDDEN): absent from navigation, direct link loads nothing, backend refuses | noPets sees Person but no Pet / Pet Note in navigation; person record neither shows nor requests the hidden pet association | direct link /app/pet shows unknown resource with no data request; API query/get of pet returns 403, table metadata 404 | tests/acceptance/fixture/AcceptanceSampleServer.java personas (noPets) | pass (chromium) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-002 | Table disabled for a user (DenyBehavior.DISABLED): listed, but list/record/create/edit links explain the denial without loading data | admin lists the ledger rows; noPets sees the Security Ledger link | noPets list, record, create and edit links show the permission message and request no records; API query/get/insert/update return 403 and rows are unchanged | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityLedger, base name pet, noPets) | pass (chromium) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-003 | Insert permission denied: create not offered, create link refused, backend writes nothing | viewer list has no Create button and the dashboard has no Create quick action | /app/person/create shows the permission message with no form; POST /data/person returns 403 and the person count is unchanged | tests/acceptance/fixture/AcceptanceSampleServer.java personas (viewer) | pass (chromium) | |
| SEC-004 | Edit permission denied: edit not offered, edit link refused, backend changes nothing | viewer record view has no Edit action | /app/person/1/edit shows the permission message without loading the record; PUT /data/person/1 returns 403 and the name is unchanged | tests/acceptance/fixture/AcceptanceSampleServer.java personas (viewer) | pass (chromium) | |
| SEC-005 | Delete permission denied: delete not offered on record or bulk bar, bulk delete refused, backend deletes nothing | record view and bulk action bar have no Delete | /app/person.bulkDelete is unknown to the viewer; DELETE /data/person/1 and bulkDelete init return 403; the row remains | tests/acceptance/fixture/AcceptanceSampleServer.java personas (viewer) | pass (chromium) | |
| SEC-006 | Copy requires insert permission: copy not offered and copy link refused | viewer record view has no Copy action | /app/person/1/copy shows the permission message; nothing is inserted | tests/acceptance/fixture/AcceptanceSampleServer.java personas (viewer) | pass (chromium) | |
| SEC-007 | Process permission denied (hidden): processes not offered in navigation or launcher, links do not start them, backend refuses | viewer has no Actions menu; noProcesses' Actions menu offers only the table's bulk actions (table rights) and no standalone process; no process links; noProcesses keeps table rights (Create offered) | /app/clonePeople and /app/greetInteractive are unknown and make no process request; legacy and v1 process init return 403; no person is cloned | tests/acceptance/fixture/AcceptanceSampleServer.java personas (viewer, noProcesses) | pass (chromium) | |
| SEC-008 | Process disabled for a user (DenyBehavior.DISABLED): shown disabled, link explains the denial, backend refuses with no side effect | admin runs Security Audit and one audit row is written; viewer sees the audit tile disabled and no dashboard quick action for it | viewer direct link shows the permission message and never calls init; process init returns 403 and the audit log stays empty | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityAudit process) | pass (chromium) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-009 | Widget restrictions: hidden widgets absent, disabled widgets show a permission state without loading data, backend refuses | admin sees both pet widgets with their content; noPets sees the open bulletin widget | noPets: hidden widget absent, disabled widget shows "You do not have permission to view this data." and no /widget request; GET /widget/<denied> returns 403 | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityPetWidget, securityPetDisabledWidget) | pass (chromium) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-010 | App restrictions: hidden apps absent from navigation and unknown by link; disabled apps keep their children gated | admin sees both pet apps | noPets: Pet Vault App absent and /app/securityPetApp unknown; Pet Disabled App children stay permission-gated | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityPetApp hidden, securityPetDisabledApp disabled) | pass (chromium) | [#651](https://github.com/QRun-IO/qqq/issues/651) [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-011 | Record-level security (sharing demo): users see only their own or shared saved views/reports; direct links and writes to others are refused | alice lists and opens her own saved view | bob's list omits alice's view; bob's direct link shows not found; bob's GET/PUT/DELETE of alice's view are refused and the row is unchanged | tests/acceptance/fixture/AcceptanceSampleServer.java personas (alice, bob) | pass (chromium) | |
| SEC-012 | Record security lock on a data table: list filtered to the key, other users' records unreachable, forged owner refused | alice sees and edits only her notes (persisted) | bob's direct link to alice's note shows not found; bob's update/delete of alice's note and insert with alice as owner are refused; SQL unchanged | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityNote locked by sample user) | pass (chromium) | |
| SEC-013 | Field protection: hidden fields never shown or returned, password fields masked and never overwritten by the mask, heavy fields not loaded in lists | REVEAL password can be revealed on request; editing another field keeps the stored password | hidden value absent from list, record, edit form and API responses; masked password shows only the mask; the list query does not carry the heavy BLOB | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityVault) | pass (chromium) | [#672](https://github.com/QRun-IO/qqq/issues/672) |
| SEC-014 | Capability restrictions: tables without insert/update/delete capabilities offer none of them and refuse direct links | admin can list and view the archive | no Create/Edit/Delete offered; create and edit links explain the table cannot be changed; backend insert/update/delete are refused; SQL unchanged | tests/acceptance/fixture/SecurityFixtures.java via SecurityAcceptanceServer (security variant) (securityArchive) | pass (chromium) | [#671](https://github.com/QRun-IO/qqq/issues/671) |
| SEC-015 | Permission revoked mid-session: stale UI actions are refused by the backend with a clear message, nothing is written, reload reflects the new rights | after reload the viewer rights are shown | saving an open edit form after revocation shows a permission error and the row is unchanged | tests/acceptance/fixture/AcceptanceSampleServer.java personas (admin then viewer) | pass (chromium) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| SEC-020 | MOCK sign-in: a deep link establishes the session and shows the session user from manageSession values | lands on the requested page with rows; user menu shows Alice (sample) / sample:alice | | tests/acceptance/fixture/AcceptanceSampleServer.java personas | pass (chromium) | [#332](https://github.com/QRun-IO/qqq/issues/332) [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-021 | Logout: POST /qqq/v1/logout ends the session, protected pages stay on login, Sign in returns to the page, per-user client data is cleared | Sign in returns to the original page | after logout a protected page (full load) stays on login and reads no data; a different user signing in on the same browser never sees the previous user's cached pages or recent records | tests/acceptance/fixture/AcceptanceSampleServer.java personas | pass (chromium) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-022 | Session expiry: 401 redirects to login with returnTo and re-authenticates back to the page (initial load and mid-use) | expired on first load returns to the page; expired mid-use returns to the clicked record | no hang on "Setting up anonymous session" | tests/acceptance/fixture/AcceptanceSampleServer.java personas (expired) | pass (chromium) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-023 | Denied sign-in: a 401 from manageSession keeps the user out with an explanation (no silent anonymous fallback) | | login shows "Sign-in was denied", no navigation or data is rendered | tests/acceptance/fixture/AcceptanceSampleServer.java personas | pass (chromium) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-024 | Post-login returnTo is limited to in-app paths (open-redirect guard) | in-app returnTo with query string is honoured | absolute, protocol-relative, backslash and javascript: targets land on /app; paths that normalize to another origin (/.//host, /app/..//host, /app/../\host) land on /app | tests/acceptance/fixture/AcceptanceSampleServer.java personas | pass (chromium) | [#669](https://github.com/QRun-IO/qqq/issues/669) [#696](https://github.com/QRun-IO/qqq/issues/696) |
| SEC-025 | OAUTH2 authorization code + PKCE sign-in with an owned OIDC provider (discovery, authorize, callback, backend token exchange, session, reload resume) | authorize request carries S256 challenge, state, client id, scopes and /token redirect; backend exchanges code + verifier with client secret; user_session row stored; provider identity is displayed; reload resumes without the provider | | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium) | [#670](https://github.com/QRun-IO/qqq/issues/670) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| SEC-026 | OAUTH2 sign-in failures: provider denial, tampered state and failed code exchange are reported and create no session | | a state-matched access_denied shows the provider description once; a state mismatch shows a generic error and is rejected before any token exchange; a rejected code exchange shows the failure; no user_session row | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium) | [#670](https://github.com/QRun-IO/qqq/issues/670) |
| SEC-027 | OAUTH2 logout: backend session deleted, provider end-session called, next visit requires provider sign-in | user_session row deleted and provider session ended | protected page after logout returns to the provider login | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium) | [#670](https://github.com/QRun-IO/qqq/issues/670) [#674](https://github.com/QRun-IO/qqq/issues/674) |
| SEC-028 | OAUTH2 session revoked server-side mid-use: 401 sends the user through the provider and back to the page | returns to the same page after provider sign-in | revoked session gets 401, not stale data | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium) | [#670](https://github.com/QRun-IO/qqq/issues/670) |
| SEC-029 | AUTH_0 authorization code + PKCE with an owned Auth0-compatible provider (browser token exchange, backend JWT validation via JWKS) | sign-in and data access through the Auth0 module; logout returns through the provider | provider denial is reported | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=AUTH_0 | pass (chromium) | [#670](https://github.com/QRun-IO/qqq/issues/670) |
| SEC-030 | AUTH_0 against a real Auth0 tenant | real tenant sign-in/logout | | Requires an Auth0 tenant QRun does not own in CI | excluded (Approved by James 2026-09-25 (QRun-IO/qqq#649): real Auth0 tenant; the AUTH_0 flow is covered against an owned Auth0-compatible provider by SEC-029/SEC-031; real-service testing is follow-up work.) | |
| SEC-031 | FULLY_ANONYMOUS authentication: session without an identity provider, anonymous identity, logout and sign-in again | deep link loads data as Anonymous | after logout the page stays signed out until Sign in | SecurityAcceptanceServer -Dqqq.security.auth=FULLY_ANONYMOUS | pass (chromium, firefox, webkit) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-032 | Unsupported authentication type (TABLE_BASED, also unsupported by Material) is reported, never silently treated as another type | | login shows "Unsupported authentication type: TABLE_BASED" and no data is requested | SecurityAcceptanceServer -Dqqq.security.auth=TABLE_BASED | pass (chromium, firefox, webkit) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-033 | Server-side logout invalidation: v1 logout expires every session cookie it issued and an ended Auth0 session cannot be replayed | logout response expires sessionUUID and sessionId | a logged-out Auth0 sessionUUID replayed against the API gets 401 | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium, firefox, webkit) | [#674](https://github.com/QRun-IO/qqq/issues/674) [#675](https://github.com/QRun-IO/qqq/issues/675) |
| INT-001 | Keyboard: navigate from the sidebar to a list, open a record, edit and save without a pointer | edit saved by keyboard persists (SQL) | | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, webkit) | [#676](https://github.com/QRun-IO/qqq/issues/676) [#682](https://github.com/QRun-IO/qqq/issues/682) |
| INT-002 | Keyboard: run a process through its steps without a pointer | greetInteractive completes and shows results | | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, webkit) | |
| INT-003 | Dialogs trap focus, close on Escape and restore focus to the trigger; confirm by keyboard | keyboard delete confirmation deletes the row (SQL) | Escape cancels without deleting; focus returns to Delete | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, mobile, webkit) | [#682](https://github.com/QRun-IO/qqq/issues/682) [#683](https://github.com/QRun-IO/qqq/issues/683) [#694](https://github.com/QRun-IO/qqq/issues/694) |
| INT-004 | Command menu, search and shortcut dialogs: keyboard open, focus inside, Escape restores focus | command menu navigates to a table | Escape closes and returns focus | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, webkit) | [#682](https://github.com/QRun-IO/qqq/issues/682) |
| INT-005 | Automated accessibility scan (axe WCAG 2.1 AA) of login, dashboard, list, record, edit form and process step | no serious or critical violations on each page | | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, mobile, webkit) | [#682](https://github.com/QRun-IO/qqq/issues/682) |
| INT-006 | Form labels and ARIA: every field has an accessible label, required fields are marked, invalid fields announce their error | labels match metadata labels | submitting empty required fields sets aria-invalid and a described error | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, webkit) | |
| INT-007 | Validation and error messages: client and backend validation name the field and nothing is saved | | missing required value is named and focused; backend rejection is shown verbatim; SQL unchanged | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, mobile, webkit) | |
| INT-008 | Responsive layout at phone width (Pixel 7): navigation drawer, list, record and form usable without horizontal scrolling | drawer navigation to a list, open and edit a record on a phone viewport | no horizontal page overflow | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, mobile, webkit) | [#676](https://github.com/QRun-IO/qqq/issues/676) |
| INT-009 | Loading states: a delayed list or record request shows a busy state, then the real data | busy indicator while the real request is held, then rows | | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, mobile, webkit) | [#694](https://github.com/QRun-IO/qqq/issues/694) |
| INT-010 | Failed request and recovery: a failed list request shows an error with retry; retry loads the data | retry succeeds | the failure is explained, not a blank page | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, webkit) | |
| INT-011 | Delayed save: the submit control is disabled while saving and double submission creates one record | exactly one row inserted (SQL) | second click while pending is ignored | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, webkit) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| INT-012 | Stale state: changes and deletions made elsewhere are shown after refresh/navigation, not stale cache | record edited through the API shows the new value on revisit | record deleted through the API shows not found | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, webkit) | [#683](https://github.com/QRun-IO/qqq/issues/683) |
| INT-013 | Empty state: a query with no results explains it and offers the create action | no-match filter shows the empty message and Create | | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, webkit) | |
| INT-014 | Backend unavailable at sign-in: the login page explains the failure and recovers with Try again | Try again after recovery signs in | network failure shows "Sign-in failed" | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium, firefox, webkit) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-031 | FULLY_ANONYMOUS authentication: session without an identity provider, anonymous identity, logout and sign-in again | deep link loads data as Anonymous | after logout the page stays signed out until Sign in | SecurityAcceptanceServer -Dqqq.security.auth=FULLY_ANONYMOUS | pass (chromium) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-032 | Unsupported authentication type (a type this UI does not know, as a newer backend could declare) is reported, never silently treated as another type | | login shows "Unsupported authentication type: ACCEPTANCE_UNKNOWN" and no data or session is requested | SecurityAcceptanceServer -Dqqq.security.auth=UNSUPPORTED (v1 authentication metadata names type ACCEPTANCE_UNKNOWN; TABLE_BASED is supported since QRun-IO/qqq#700, see SEC-034..SEC-037) | pass (chromium) | [#669](https://github.com/QRun-IO/qqq/issues/669) [#700](https://github.com/QRun-IO/qqq/issues/700) |
| SEC-033 | Server-side logout invalidation: v1 logout expires every session cookie it issued and an ended Auth0 session cannot be replayed | logout response expires sessionUUID and sessionId | a logged-out Auth0 sessionUUID replayed against the API gets 401 | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium) | [#674](https://github.com/QRun-IO/qqq/issues/674) [#675](https://github.com/QRun-IO/qqq/issues/675) |
| SEC-034 | TABLE_BASED sign-in: a deep link asks for username and password under the app branding, POST /qqq/v1/manageSession with Authorization: Basic creates a stored session, the session user is shown and a reload resumes it | login form shows the logo and app name before any session exists and reads no data; credentials travel once as Basic credentials (password with colons), never in the body or browser storage; lands on the requested page with rows; user menu shows Tess Table (table-based) / tess.table; one session row for the user, matching the sessionUUID cookie; reload keeps it | without the session cookie the backend refuses a read (401) | SecurityAcceptanceServer -Dqqq.security.auth=TABLE_BASED + SecurityFixtures.defineTableBased/primeTableBased (users tess.table, ravi.rows) | pass (chromium) | [#700](https://github.com/QRun-IO/qqq/issues/700) |
| SEC-035 | TABLE_BASED sign-in failure: a wrong password or unknown user is refused with one message, stores no session, and the form recovers | correct credentials after a refusal sign in to the requested page | empty fields are named and nothing is sent; wrong password and unknown user both show "Incorrect username or password."; no session row is stored and no data is read; the password field is cleared | SecurityAcceptanceServer -Dqqq.security.auth=TABLE_BASED + SecurityFixtures.defineTableBased/primeTableBased (users tess.table, ravi.rows) | pass (chromium) | [#700](https://github.com/QRun-IO/qqq/issues/700) |
| SEC-036 | TABLE_BASED session expiry: a session idle past the inactivity timeout gets a 401, the user signs in again and returns to the record they opened | expired mid-use returns to the clicked record after signing in again | the expired session is refused by the backend ("Session is expired.") | SecurityAcceptanceServer -Dqqq.security.auth=TABLE_BASED + SecurityFixtures.defineTableBased/primeTableBased (users tess.table, ravi.rows) + /acceptance/expire-table-sessions | pass (chromium) | [#700](https://github.com/QRun-IO/qqq/issues/700) |
| SEC-037 | TABLE_BASED logout: POST /qqq/v1/logout deletes the stored session and clears both cookies, protected pages stay on the form, and the next user signs in as themselves | a different user signs in on the same browser and is shown as themselves | the ended session cannot be replayed with either cookie (401); after logout a protected page (full load) stays on the form and reads no data | SecurityAcceptanceServer -Dqqq.security.auth=TABLE_BASED + SecurityFixtures.defineTableBased/primeTableBased (users tess.table, ravi.rows) | pass (chromium) | [#700](https://github.com/QRun-IO/qqq/issues/700) |
| SEC-038 | Dashboard documents carry a strict Content-Security-Policy (script-src self plus the hash of each inline script, object-src none, base-uri/form-action self, frame-ancestors none), X-Frame-Options DENY, Referrer-Policy and Permissions-Policy | login, token, app, list, record, edit and 404 documents send the policy and headers; the script-src hashes equal the hashes of the inline scripts in each served document; pages hydrate and navigate under the policy with no violation | another web site framing the dashboard gets no content; an injected inline script and inline event handler do not run and are reported as violations | tests/acceptance/fixture/AcceptanceSampleServer.java (javalin mode); with QQQ_ACCEPTANCE_MODE=standalone the container image server (standalone/qqq-server.mjs) | pass (chromium) | [#695](https://github.com/QRun-IO/qqq/issues/695) [#734](https://github.com/QRun-IO/qqq/issues/734) |
| SEC-039 | The policy allows the configured identity provider origin (OAUTH2 discovery, AUTH_0 browser token exchange) and no other connection target | OAUTH2: connect-src is self plus the provider origin; sign-in completes with no violation; AUTH_0: connect-src is self plus the Auth0 domain; the browser token exchange completes with no violation | a fetch to any other origin is refused and reported (connect-src) | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2\|AUTH_0 | pass (chromium) | [#695](https://github.com/QRun-IO/qqq/issues/695) |
| SEC-040 | Application override hook and metadata-derived origins: the application adds its own origins (QApplicationJavalinServer.withNextDashboardSecurityHeadersCustomizer); QuickSight and custom component bundle origins come from widget metadata | the QuickSight embed, block image and audio from the loopback service and the custom component bundle load with no violation; the policy lists exactly the configured origins | a frame from an origin the application did not configure is refused and reported (frame-src) | tests/acceptance/fixture/WidgetsFixtures.java loopback service | pass (chromium) | [#695](https://github.com/QRun-IO/qqq/issues/695) [#734](https://github.com/QRun-IO/qqq/issues/734) |
| SEC-041 | The login page explains known sign-in error codes and never displays text taken from the link | direct links with access_denied and other known codes show their safe message; other well-formed codes show the code; Try again signs in through the provider | free text in ?error= is replaced by a generic message | tests/acceptance/support/fake-oidc.ts + SecurityAcceptanceServer -Dqqq.security.auth=OAUTH2 | pass (chromium) | [#696](https://github.com/QRun-IO/qqq/issues/696) |
| SEC-042 | Rendered HTML (field values, widgets, process steps, help, banners) cannot add forms, form controls, style sheets or page-covering positioning | formatting in a stored HTML value is kept; the stored value is unchanged in the database | a stored form with a password input, a style element and a position:fixed overlay render as plain content | tests/acceptance/fixture/RecordsFixtures.java fieldLab.htmlValue | pass (chromium) | [#696](https://github.com/QRun-IO/qqq/issues/696) |
| SEC-043 | Per-user browser data (recently viewed records, stored identity) is bound to the signed-in identity | the same user keeps their recently viewed records across reloads | a different user signing in on the same browser without a logout does not see the previous user's recent records | tests/acceptance/fixture/AcceptanceSampleServer.java personas and sample users | pass (chromium) | [#696](https://github.com/QRun-IO/qqq/issues/696) |
| SEC-044 | Session cookies (sessionUUID, sessionId) are SameSite=Lax for the whole site, and Secure when the request arrived over HTTPS | manageSession and API responses set the cookies with Path=/ and SameSite=Lax; the browser stores them as Lax | over plain HTTP the cookies are not Secure (sign-in keeps working); behind X-Forwarded-Proto https they are | tests/acceptance/fixture/AcceptanceSampleServer.java | pass (chromium) | [#696](https://github.com/QRun-IO/qqq/issues/696) |
| SEC-048 | Session cookies (sessionUUID, sessionId) are HttpOnly: script in the page cannot read them, and resuming a session from the cookie never hands its token to script | after sign-in (MOCK, TABLE_BASED, OAUTH2) the browser stores both cookies HttpOnly, naming the current session; a reload resumes the session through POST /qqq/v1/manageSession with an empty body; the response carries the session values and no uuid | document.cookie never shows sessionUUID or sessionId; an in-page POST /qqq/v1/manageSession {} (what injected script could send) answers without the session token | tests/acceptance/fixture/AcceptanceSampleServer.java (MOCK); SecurityAcceptanceServer -Dqqq.security.auth=TABLE_BASED and OAUTH2 with tests/acceptance/support/fake-oidc.ts | pass (chromium) | [#733](https://github.com/QRun-IO/qqq/issues/733) |
| SEC-049 | With HttpOnly session cookies the backend ends and replaces sessions: logout expires both cookies, an idle-expired or revoked session sends the user to sign in again, and a new password or OAuth2 sign-in points both cookies at the new session | TABLE_BASED: a first visit shows the form without a session request; an idle-expired session is refused, the form returns without a resume attempt, and signing in again returns to the record; OAUTH2: a session revoked on the server sends the user through the provider and back, with new HttpOnly cookies; logout: the server deletes the session and expires both cookies; a protected page stays signed out without a session request | a logout that never reached the server leaves the earlier user's cookies; the next user's sign-in replaces both, and a reload keeps the new user | SecurityAcceptanceServer -Dqqq.security.auth=TABLE_BASED and OAUTH2 with tests/acceptance/support/fake-oidc.ts | pass (chromium) | [#733](https://github.com/QRun-IO/qqq/issues/733) |
| INT-001 | Keyboard: navigate from the sidebar to a list, open a record, edit and save without a pointer | edit saved by keyboard persists (SQL) | | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | [#676](https://github.com/QRun-IO/qqq/issues/676) [#682](https://github.com/QRun-IO/qqq/issues/682) |
| INT-002 | Keyboard: run a process through its steps without a pointer | greetInteractive completes and shows results | | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | |
| INT-003 | Dialogs trap focus, close on Escape and restore focus to the trigger; confirm by keyboard | keyboard delete confirmation deletes the row (SQL) | Escape cancels without deleting; focus returns to Delete | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | [#682](https://github.com/QRun-IO/qqq/issues/682) [#683](https://github.com/QRun-IO/qqq/issues/683) [#694](https://github.com/QRun-IO/qqq/issues/694) |
| INT-004 | Command menu, search and shortcut dialogs: keyboard open, focus inside, Escape restores focus | command menu navigates to a table | Escape closes and returns focus | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | [#682](https://github.com/QRun-IO/qqq/issues/682) |
| INT-005 | Automated accessibility scan (axe WCAG 2.1 AA) of login, dashboard, list, record, edit form and process step | no serious or critical violations on each page | | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | [#682](https://github.com/QRun-IO/qqq/issues/682) |
| INT-006 | Form labels and ARIA: every field has an accessible label, required fields are marked, invalid fields announce their error | labels match metadata labels | submitting empty required fields sets aria-invalid and a described error | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | |
| INT-007 | Validation and error messages: client and backend validation name the field and nothing is saved | | missing required value is named and focused; backend rejection is shown verbatim; SQL unchanged | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | |
| INT-008 | Responsive layout at phone width (Pixel 7): navigation drawer, list, record and form usable without horizontal scrolling | drawer navigation to a list, open and edit a record on a phone viewport | no horizontal page overflow | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | [#676](https://github.com/QRun-IO/qqq/issues/676) |
| INT-009 | Loading states: a delayed list or record request shows a busy state, then the real data | busy indicator while the real request is held, then rows | | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | [#694](https://github.com/QRun-IO/qqq/issues/694) |
| INT-010 | Failed request and recovery: a failed list request shows an error with retry; retry loads the data | retry succeeds | the failure is explained, not a blank page | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | |
| INT-011 | Delayed save: the submit control is disabled while saving and double submission creates one record | exactly one row inserted (SQL) | second click while pending is ignored | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| INT-012 | Stale state: changes and deletions made elsewhere are shown after refresh/navigation, not stale cache | record edited through the API shows the new value on revisit | record deleted through the API shows not found | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | [#683](https://github.com/QRun-IO/qqq/issues/683) |
| INT-013 | Empty state: a query with no results explains it and offers the create action | no-match filter shows the empty message and Create | | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | |
| INT-014 | Backend unavailable at sign-in: the login page explains the failure and recovers with Try again | Try again after recovery signs in | network failure shows "Sign-in failed" | qqq-sample-project (stock sample, mock auth, sharing demo) | pass (chromium) | [#669](https://github.com/QRun-IO/qqq/issues/669) |
| SEC-050 | Optional QA capture (Jam, qbit-qa-capture qa-capture extension): Jam loads only for enabled users; Report a bug opens the recorder; secure values blurred; captures sanitized | extension absent or capability disabled: no Jam chunk, meta or request; enabled: jam:team meta, one initialize with sanitizers, jam.metadata; Report a bug: POST report then Recorder.open with jam-title and jam-reference; Recording Link jam-* parameters survive the OIDC sign-in; QA mode preference refetches the capability | no Recording Link configured: toast, no recorder; Authorization/Cookie headers, secure body fields and JWTs redacted | qbit-qa-capture (not yet released); Vitest: src/lib/qa-capture/*.test.ts, src/components/qa-capture/QaCapture.test.tsx | excluded (Pending: not yet approved by the owner. No real-backend fixture until qbit-qa-capture is released; the row is covered by Vitest until then (QA capture PR, 2026-10-15).) | |
## Dashboards, widgets, reports, saved and scheduled reports, and sharing
| ID | Feature | Scenarios | Negative | Fixture | Result | Issues |
|---|---|---|---|---|---|---|
| WID-001 | alert widget: severity from alertType, sanitized HTML and bullet list | WARNING alert renders strong HTML and each bullet as sanitized HTML | hideWidget and empty alerts render nothing (WID-063) | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accAlert | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-002 | barChart widget: one bar per label in backend colors | bars carry per-point backgroundColors; accessible data table has exact values | | sample SampleWidgetsDashboard/SampleBarChartWidget | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-003 | chart output from an untyped widget renders as a bar chart | metadata type absent, payload type chart, values rendered | zero-value month has no drawn bar | sample greetingsApp/PersonsByCreateDateBarChart | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-004 | divider widget renders a rule without card chrome | hr rendered, no label or card | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accDivider | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-005 | fieldValueList widget: labels, display values, prefix icons, indentation | display value wins, zero shown, icon color, indent | empty field list shows empty state (WID-052) | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accFieldValueList | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-006 | generic widget renders its common payload fields (sublabel, footer) | sublabel and sanitized footer HTML shown | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accGeneric | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-007 | horizontalBarChart widget: horizontal bars with negative values left of zero | negative bar extends left of the zero line; height from payload | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accHorizontalBarChart | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-008 | html widget (no-code) renders sanitized user HTML | velocity output with i/b/u and centered wrapper | empty html renders an empty body (WID-052) | sample SampleWidgetsDashboard/SampleHTMLWidget | pass (chromium, firefox, webkit) | |
| WID-009 | lineChart widget: line with a point per label and description | points carry exact values | | sample SampleWidgetsDashboard/SampleLineChartWidget | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-010 | smallLineChart widget: compact line with title and HTML description | title, strong description text, 5 points | | sample SampleWidgetsDashboard/SampleSmallLineChartWidget | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-011 | location widget: image, title, description, address and footer | owned image loads from the owned service | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accLocation | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-012 | multiStatistics widget: groups with icon, header, subheader and statistics | every group and value matches the payload | empty group list shows empty state (WID-052) | sample SampleWidgetsDashboard/SampleMultiStatisticsWidget | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-013 | multiTable widget renders each table with its label and rows | two labelled tables with their rows | empty table list shows empty state (WID-052) | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accMultiTable | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-014 | pieChart widget: colored slices, legend values and subheader | slice fills, legend label: value, main number, up/good change | | sample SampleWidgetsDashboard/SamplePieChartWidget | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-015 | quickSightChart widget embeds the provider URL (real renderer, owned local provider endpoint) | iframe src is the owned embed URL and its content renders | metadata never exposes the secret key | tests/acceptance/fixture/WidgetsFixtures.java repointSampleQuickSight (greetingsApp/QuickSightChartRenderer against the owned loopback endpoint) | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-016 | statistics widget: count, context, colored percentage change and links | decrease with increaseIsGood is bad; increase with increaseIsGood=false is bad; count links to countURL | | sample SampleStatisticsWidget; tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accStatisticsGood | pass (chromium, firefox, webkit) | |
| WID-017 | stackedBarChart widget stacks datasets with colors and legend | bars of one label share x; dataset colors | | sample SampleStackedBarChartWidget; tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accStackedBars | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-018 | stepper widget: completed, current (with link) and upcoming steps | states and current link | empty steps show empty state (WID-052) | sample SampleWidgetsDashboard/SampleStepperWidget | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-019 | table widget: headers, rows, alignment and fixed totals row | rows and sticky last row match payload | no rows shows noRowsFoundHTML or default (WID-052) | sample SampleWidgetsDashboard/SampleTableWidget | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-020 | usaMap widget plots each marker with name and coordinates | 3 markers placed west to east by longitude; marker list | empty marker list shows empty state (WID-052) | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accUsaMap | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-021 | process widget runs the sample interactive process inline | form fields render, submit, result greeting shows | viewer persona: process denied contained (WID-061) | tests/acceptance/fixture/WidgetsFixtures.java widgetProcess/accProcess (greetInteractive) | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-022 | parentWidget renders its children in a 12-column grid | children render with their own data | tabs (WID-055), denied/missing children (WID-056) | tests/acceptance/fixture/WidgetsFixtures.java widgetParents/accParentGrid | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-023 | composite widget renders QQQ blocks (big numbers, up/down, links, tooltips) | every block value, direction, link and tooltip | no blocks renders an empty composite (WID-052) | sample SampleWidgetsDashboard/SampleBigNumberBlocksWidget | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-024 | childRecordList widget without association binding lists joined child records | rows link to child records, Showing 2 of 3, View All filter link | host with no children shows empty state | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost section accWidgetHostJoinChild (maxRows 2) | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#663](https://github.com/QRun-IO/qqq/issues/663) |
| WID-025 | customComponent widget loads its bundle and renders the named component | component receives widget metadata and data | missing bundle shows contained error (WID-062) | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accCustomComponent (owned bundle) | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-026 | cronUI widget shows the record's schedule expression, description and time zone | expression, backend description and time zone | record without expression shows No schedule set | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost section accHostCron | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-027 | dynamicForm widget shows labeled values from the payload | labels and values incl. 0 | | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost section accHostDynamicForm | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-028 | dataBagViewer widget lists versions and shows the selected version contents | newest version current and shown; selecting older version shows its JSON | bag without versions shows empty message | tests/acceptance/fixture/WidgetsFixtures.java dataBag/1 section accDataBagViewer (owned dataBag tables) | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-029 | pivotTableSetup widget shows the saved report's pivot definition | rows/values labels from the source table | report without pivot shows message | prime-sharing savedReport 1; tests/acceptance/fixture/WidgetsFixtures.java owned pivot saved report | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-030 | filterAndColumnsSetup widget shows the saved report's filter, sort and columns | criteria with field labels and operator phrases, columns by label | report without filter shows No filters | prime-sharing savedReport 1; tests/acceptance/fixture/WidgetsFixtures.java owned filtered saved report | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-031 | rowBuilder widget without association binding shows its rows read-only | column labels from widget fields, row values incl. 0 | | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost section accHostRows | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#663](https://github.com/QRun-IO/qqq/issues/663) |
| WID-032 | scriptViewer widget lists script revisions and shows the selected revision's files | current revision marked and its file contents shown; selecting older revision | | tests/acceptance/fixture/WidgetsFixtures.java script/1 (owned memory script with 2 revisions) | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-001 | alert widget: severity from alertType, sanitized HTML and bullet list | WARNING alert renders strong HTML and each bullet as sanitized HTML | hideWidget and empty alerts render nothing (WID-063) | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accAlert | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-002 | barChart widget: one bar per label in backend colors | bars carry per-point backgroundColors; accessible data table has exact values | | sample SampleWidgetsDashboard/SampleBarChartWidget | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-003 | chart output from an untyped widget renders as a bar chart | metadata type absent, payload type chart, values rendered | zero-value month has no drawn bar | sample greetingsApp/PersonsByCreateDateBarChart | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-004 | divider widget renders a rule without card chrome | hr rendered, no label or card | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accDivider | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-005 | fieldValueList widget: labels, display values, prefix icons, indentation | display value wins, zero shown, icon color, indent | empty field list shows empty state (WID-052) | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accFieldValueList | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-006 | generic widget renders its common payload fields (sublabel, footer) | sublabel and sanitized footer HTML shown | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accGeneric | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-007 | horizontalBarChart widget: horizontal bars with negative values left of zero | negative bar extends left of the zero line; height from payload | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accHorizontalBarChart | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-008 | html widget (no-code) renders sanitized user HTML | velocity output with i/b/u and centered wrapper | empty html renders an empty body (WID-052) | sample SampleWidgetsDashboard/SampleHTMLWidget | pass (chromium) | |
| WID-009 | lineChart widget: line with a point per label and description | points carry exact values | | sample SampleWidgetsDashboard/SampleLineChartWidget | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-010 | smallLineChart widget: compact line with title and HTML description | title, strong description text, 5 points | | sample SampleWidgetsDashboard/SampleSmallLineChartWidget | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-011 | location widget: image, title, description, address and footer | owned image loads from the owned service | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accLocation | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-012 | multiStatistics widget: groups with icon, header, subheader and statistics | every group and value matches the payload | empty group list shows empty state (WID-052) | sample SampleWidgetsDashboard/SampleMultiStatisticsWidget | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-013 | multiTable widget renders each table with its label and rows | two labelled tables with their rows | empty table list shows empty state (WID-052) | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accMultiTable | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-014 | pieChart widget: colored slices, legend values and subheader | slice fills, legend label: value, main number, up/good change | | sample SampleWidgetsDashboard/SamplePieChartWidget | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-015 | quickSightChart widget embeds the provider URL (real renderer, owned local provider endpoint) | iframe src is the owned embed URL and its content renders | metadata never exposes the secret key | tests/acceptance/fixture/WidgetsFixtures.java repointSampleQuickSight (greetingsApp/QuickSightChartRenderer against the owned loopback endpoint) | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-016 | statistics widget: count, context, colored percentage change and links | decrease with increaseIsGood is bad; increase with increaseIsGood=false is bad; count links to countURL | | sample SampleStatisticsWidget; tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accStatisticsGood | pass (chromium) | |
| WID-017 | stackedBarChart widget stacks datasets with colors and legend | bars of one label share x; dataset colors | | sample SampleStackedBarChartWidget; tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accStackedBars | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-018 | stepper widget: completed, current (with link) and upcoming steps | states and current link | empty steps show empty state (WID-052) | sample SampleWidgetsDashboard/SampleStepperWidget | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-019 | table widget: headers, rows, alignment and fixed totals row | rows and sticky last row match payload | no rows shows noRowsFoundHTML or default (WID-052) | sample SampleWidgetsDashboard/SampleTableWidget | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-020 | usaMap widget plots each marker with name and coordinates | 3 markers placed west to east by longitude; marker list | empty marker list shows empty state (WID-052) | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accUsaMap | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-021 | process widget runs the sample interactive process inline | form fields render, submit, result greeting shows | viewer persona: process denied contained (WID-061) | tests/acceptance/fixture/WidgetsFixtures.java widgetProcess/accProcess (greetInteractive) | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-022 | parentWidget renders its children in a 12-column grid | children render with their own data | tabs (WID-055), denied/missing children (WID-056) | tests/acceptance/fixture/WidgetsFixtures.java widgetParents/accParentGrid | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-023 | composite widget renders QQQ blocks (big numbers, up/down, links, tooltips) | every block value, direction, link and tooltip | no blocks renders an empty composite (WID-052) | sample SampleWidgetsDashboard/SampleBigNumberBlocksWidget | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-024 | childRecordList widget without association binding lists joined child records | rows link to child records, Showing 2 of 3, View All filter link | host with no children shows empty state | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost section accWidgetHostJoinChild (maxRows 2) | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#663](https://github.com/QRun-IO/qqq/issues/663) |
| WID-025 | customComponent widget loads its bundle and renders the named component | component receives widget metadata and data | missing bundle shows contained error (WID-062) | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accCustomComponent (owned bundle) | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-026 | cronUI widget shows the record's schedule expression, description and time zone | expression, backend description and time zone | record without expression shows No schedule set | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost section accHostCron | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-027 | dynamicForm widget shows labeled values from the payload | labels and values incl. 0 | | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost section accHostDynamicForm | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-028 | dataBagViewer widget lists versions and shows the selected version contents | newest version current and shown; selecting older version shows its JSON | bag without versions shows empty message | tests/acceptance/fixture/WidgetsFixtures.java dataBag/1 section accDataBagViewer (owned dataBag tables) | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-029 | pivotTableSetup widget shows, edits and clears the saved report's pivot definition | rows/values labels from the source table; modal OK updates only the host draft; host Save persists rows, columns and aggregate values with SQL readback and reopen; keyboard row reorder and value move preserve saved configuration; turning the pivot off clears the stored definition on host Save | report without pivot shows message; missing row fields and aggregate functions keep the editor open; Cancel discards added fields without changing the database; used group/value fields are excluded from choices; changing a numeric value to text clears an incompatible aggregate | prime-sharing savedReport 1; tests/acceptance/fixture/WidgetsFixtures.java owned pivot saved report | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-030 | filterAndColumnsSetup widget shows the saved report's filter, sort and columns | criteria with field labels and operator phrases, columns by label | report without filter shows No filters | prime-sharing savedReport 1; tests/acceptance/fixture/WidgetsFixtures.java owned filtered saved report | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-070 | filterAndColumnsSetup edits a saved report's filter, sort, columns and variable values through the host form | Edit Filters and Columns opens the report setup dialog; OK writes queryFilterJson and columnsJson to the host form, and Save persists a changed sort to the backend; assigning a filter variable persists FilterVariableExpression in the backend JSON and shows ${VARIABLE} in the editor; import a saved view, reset changes to that view or reset to a new default draft without updating the saved view | Cancel discards the draft; OK without host Save leaves the backend row unchanged; preview and Open in new window block a query while a filter variable lacks a value; resetting and cancelling leave both the report and all saved views unchanged | prime-sharing savedReport 1 (Pet Species Report, no pivot table); saved-reports-sharing.spec.ts | pass (chromium) | [#722](https://github.com/QRun-IO/qqq/issues/722) |
| WID-071 | a WIDGET-adorned process form field renders filter setup and writes its own value | the widget request names the adorned field, the editor updates its filter, and the next process screen receives the JSON | other widget output fields are not written to the adorned field's host form | ProcessesFixtures.java prcFieldWidget with a queryFilterJson WIDGET adornment | pass (chromium) | [#722](https://github.com/QRun-IO/qqq/issues/722) |
| WID-072 | API-versioned report setup loads table metadata and previews through the application API | the report editor loads person metadata and its preview rows and count through the real API-aware v1 routes | an unsupported API version returns 404 | ApiVersionFixtures.java acceptanceApi 2026.Q3; savedReport 102; saved-reports-sharing.spec.ts | pass (chromium) | [#722](https://github.com/QRun-IO/qqq/issues/722) |
| WID-073 | report setup editor uses the full server-backed grid for paging, sorting and column sizing | grid preview hides selection, changing page size changes the real backend query, header sorting changes the real backend query, and keyboard column resize persists in saved columns JSON; create a report from a query, switch to Audit, save and reopen, verify stored columns and download the CSV; filtered column statistics totals and exported value counts match SQL; closing statistics and cancelling the editor leave saved data unchanged; a table without COUNT still shows real preview rows and page copy matches SQL; variant-backed report previews choose, switch and remember the backend variant for query/count, copy and statistics on ordinary and application-API routes; complete nested criteria mark preview column headers; opening the indicator edits the existing condition and real preview rows match SQL; configured omitted joins disappear from new preview column/filter choices while saved joined values, editable criteria and column positions survive; changed results and reload match SQL; editor Cancel, Basic/Advanced, OK, host Save and reopen preserve joined criteria, sorts, order and width; hidePreview hides only the read-only summary; the editor retains real query rows, sorting and Refresh; Cancel, OK, host Save and reopen preserve the intended sort with the flag enabled and disabled; column menus fit 320px and 720px windows; keyboard End/Escape restores focus and pointer Copy/full-query and sorting remain reachable | selection pseudo-column and incompatible previous-table fields are excluded from saved reports; full-column copy refuses results over the configured limit; an empty preview offers no column copy action; cancellation preserves the saved report; no query before selecting a required variant; an old variant copy response cannot overwrite a later copy; variant changes do not persist report edits; incomplete criteria do not mark columns; opening an indicator adds no condition; read-only preview edits and editor Cancel leave stored report JSON unchanged | savedReport 1 targeting petSpecies without a pivot; saved-reports-sharing.spec.ts | pass (chromium) | [#722](https://github.com/QRun-IO/qqq/issues/722) |
| WID-031 | rowBuilder widget without association binding shows its rows read-only | column labels from widget fields, row values incl. 0 | | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost section accHostRows | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#663](https://github.com/QRun-IO/qqq/issues/663) |
| WID-032 | scriptViewer widget lists script revisions and shows the selected revision's files | current revision marked and its file contents shown; selecting older revision | | tests/acceptance/fixture/WidgetsFixtures.java script/1 (owned memory script with 2 revisions) | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-033 | quickSightChart against a real AWS QuickSight account and dashboard | real embed URL | | requires AWS credentials and a QuickSight dashboard (external service) | excluded (Approved by James 2026-09-25 (QRun-IO/qqq#649): real AWS QuickSight dashboard; the widget and renderer are covered against an owned loopback provider by WID-015; real-service testing is follow-up work.) | |
| WID-040 | dashboard renders app widgets in declared order sized by gridColumns (twelfths) | order matches metadata; 12 spans the grid; 4 is a third | | sample SampleWidgetsDashboard | pass (chromium, firefox, webkit) | [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-041 | widget chrome: card vs plain (isCard=false), sublabel, metadata and payload footer HTML | plain widget has no card border; footers render | | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accPlain, widgetGallery/accGeneric | pass (chromium, firefox, webkit) | [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-042 | header icons (topLeftInsideCard/topRightInsideCard) with metadata colors | icon names, colors and placement | | sample SampleWidgetsDashboard | pass (chromium, firefox, webkit) | [#538](https://github.com/QRun-IO/qqq/issues/538) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-043 | widget label tooltip from metadata on hover and keyboard focus | hover and focus show, Escape hides | | sample SampleWidgetsDashboard/SampleBarChartWidget | pass (chromium, firefox, webkit) | [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-044 | widget help content (HTML) on the label | help icon shows sanitized HTML help | | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accHelp | pass (chromium, firefox, webkit) | [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-045 | reload button re-renders the widget from the backend | render count increments per reload | no reload button without showReloadButton | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accReload | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-046 | export button downloads the widget's csvData as CSV | file bytes match Material quoting rules | no csvData: no-data message and no download | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accExport, accExportEmpty, accControls | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-047 | PVS dropdown from the widget payload: selection is sent as a parameter and re-renders | options from payload, choice reaches renderer, clearing removes it | non-stored selection is not persisted | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accDropdownHtml | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-048 | DATE_PICKER dropdown sends the chosen date | date reaches the child renderer | | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accControls | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-049 | required dropdowns: the parent waits with the backend message until selections are made | message then children render with selections | | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accControls | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-050 | storeDropdownSelections persists selections across reload; stale stored options are dropped | localStorage qqq.widgets.dropdownData.<widget>.<param>; reload keeps selections | removed option in storage is not selected | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accControls | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-051 | failing widget renderer shows a contained error with the backend message and retry | error message, retry re-requests, neighbor content | no error toast, no console error | tests/acceptance/fixture/WidgetsFixtures.java widgetStates/accError + accHealthy | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-052 | empty payloads show descriptive empty states for every display type | table, multiStatistics, charts, stepper, statistics 0, composite, multiTable, field values, map, html | | tests/acceptance/fixture/WidgetsFixtures.java widgetStates/accEmpty* | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-053 | malformed payloads are contained to their own widget | chart, table, composite, stepper, multiStatistics show a format notice | no crash, no console error, neighbors render | tests/acceptance/fixture/WidgetsFixtures.java widgetStates/accMalformed* | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-054 | widget permissions: a denied widget is absent from metadata, not rendered and not requested; the API denies it | admin sees it | noPets: no card, no request, GET /widget 403, renderer not called | tests/acceptance/fixture/WidgetsFixtures.java widgetPermissions/accDenied (pet.accDenied.hasAccess) with the noPets persona | pass (chromium, firefox, webkit) | |
| WID-055 | parent widget TABS layout switches tabs and remembers the selected tab | tab click, arrow keys, stored tab restored after reload | | tests/acceptance/fixture/WidgetsFixtures.java widgetParents/accParentTabs | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-056 | parent widget skips denied and unknown children without requests | allowed children render | noPets: denied child absent and not requested; unknown child skipped | tests/acceptance/fixture/WidgetsFixtures.java widgetParents/accParentDenied, accParentMissing | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-057 | composite renders every block type with its values and styles | TEXT, BIG_NUMBER, UP_OR_DOWN_NUMBER, NUMBER_ICON_BADGE, ICON, TABLE_SUB_ROW_DETAIL_ROW, PROGRESS_BAR, DIVIDER, IMAGE, AUDIO, INPUT_FIELD, BUTTON | | tests/acceptance/fixture/WidgetsFixtures.java widgetBlocks/accBlocks | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-058 | composite layouts and slot links/tooltips | FLEX_COLUMN, FLEX_ROW, BADGES_WRAPPER, TABLE_SUB_ROW_DETAILS, FLEX_ROW_SPACE_BETWEEN, FLEX_ROW_CENTER, FLEX_ROW_WRAPPED; link to /app/person with tooltip | | tests/acceptance/fixture/WidgetsFixtures.java widgetBlocks/accBlocks | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-059 | unsupported block type shows a contained warning | Unsupported block type: OWNED_UNKNOWN; neighbor healthy | | tests/acceptance/fixture/WidgetsFixtures.java widgetBlocks/accBlocksUnknown | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-060 | record-view section widgets receive the record context (id, tableName) | record 1 and 2 show their own values | renderer enforces the user's table read | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost sections accHostHtml, accHostFieldValues | pass (chromium, firefox, webkit) | |
| WID-061 | process widget for a persona without process permission is contained | error shown in the widget; neighbor healthy | process metadata 403, no process run | tests/acceptance/fixture/WidgetsFixtures.java widgetProcess/accProcess with the noProcesses persona | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-062 | custom component whose bundle cannot load shows a contained error | Error loading MissingOwnedComponent | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accCustomComponentMissing | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-063 | alerts with hideWidget or without HTML render nothing | no card rendered | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accAlertHidden, widgetStates/accEmptyAlert | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| RPT-001 | TABLE report runs through its process and downloads CSV matching the database | CSV header labels and every person row from SQL | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPersonReport | pass (chromium, firefox, webkit) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-002 | TABLE report downloads XLSX with the same rows | sheet rows match SQL | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPersonReport | pass (chromium, firefox, webkit) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-003 | TABLE report downloads JSON with the same rows | JSON objects match SQL | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPersonReport | pass (chromium, firefox, webkit) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-004 | SUMMARY report groups rows with counts and a totals row | pets per owner match SQL group by; Totals row | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPetSummaryReport | pass (chromium, firefox, webkit) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-005 | PIVOT report produces a native pivot table in XLSX | xlsx contains the pivot table and cache over the rows sheet | CSV omits the pivot view (no native pivots) | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPetPivotReport | pass (chromium, firefox, webkit) | [#664](https://github.com/QRun-IO/qqq/issues/664) [#484](https://github.com/QRun-IO/qqq/issues/484) |
| RPT-006 | report input fields: required validation and the value filters the rows | minimumId filters rows >= value | empty required input blocks submit | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPersonInputReport | pass (chromium, firefox, webkit) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-007 | report permissions: restricted report denied in UI and by the server | admin can run it | noPets: no-permission message, legacy route 403 | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accRestrictedReport with the noPets persona | pass (chromium, firefox, webkit) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-008 | reports appear in app navigation and open their run page | sidebar REPORT nodes navigate to the report page | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports app | pass (chromium, firefox, webkit) | [#664](https://github.com/QRun-IO/qqq/issues/664) [#651](https://github.com/QRun-IO/qqq/issues/651) |
| RPT-009 | saved report view shows its filter/columns and pivot setup | filter criteria, columns, pivot rows/values labels | | prime-sharing savedReport 1; tests/acceptance/fixture/WidgetsFixtures.java owned saved reports | pass (chromium, firefox, webkit) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| RPT-010 | saved report renders through renderSavedReport and downloads the saved columns and rows | format chosen, CSV contains the saved columns and every species | | prime-sharing savedReport 1 (Pet Species Report) | pass (chromium, firefox, webkit) | [#645](https://github.com/QRun-IO/qqq/issues/645) [#661](https://github.com/QRun-IO/qqq/issues/661) |
| RPT-011 | streaming report route for reports without a process | CSV streamed from /reports/{name}?format=csv matches SQL | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accStreamedReport | pass (chromium, firefox, webkit) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-012 | scheduled report: create a schedule for a saved report and view its cron schedule | schedule persisted with cron description; record view shows the schedule widget | invalid cron expression rejected with message | scheduledReport (memory) for savedReport 1 | pass (chromium, firefox, webkit) | [#673](https://github.com/QRun-IO/qqq/issues/673) |
| RPT-013 | sharing: the owner shares a saved report with a user (Read-Only) | dialog lists Bob; shared_saved_report row READ_ONLY | | prime-sharing savedReport 1 owned by alice | pass (chromium, firefox, webkit) | [#665](https://github.com/QRun-IO/qqq/issues/665) [#444](https://github.com/QRun-IO/qqq/issues/444) |
| RPT-014 | sharing: change a share's scope to Read and Edit | row scope READ_WRITE | | prime-sharing savedReport 1 | pass (chromium, firefox, webkit) | [#665](https://github.com/QRun-IO/qqq/issues/665) |
| RPT-015 | sharing: remove a share | row deleted, list empty | | prime-sharing savedReport 1 | pass (chromium, firefox, webkit) | [#665](https://github.com/QRun-IO/qqq/issues/665) |
| RPT-016 | sharing record security: another user cannot see an unshared report until it is shared | bob sees it after sharing (UI list + API) | before sharing: not in bob's list, GET 404 | prime-sharing savedReport 1 (alice), user bob | pass (chromium, firefox, webkit) | [#665](https://github.com/QRun-IO/qqq/issues/665) |
| RPT-017 | sharing: only the owner may share | Share disabled with reason for the non-owner | server rejects a non-owner share; no row | tests/acceptance/fixture/WidgetsFixtures.java savedReport owned by casey shared with alice | pass (chromium, firefox, webkit) | [#665](https://github.com/QRun-IO/qqq/issues/665) [#371](https://github.com/QRun-IO/qqq/issues/371) |
| RPT-018 | sharing a saved view through the share dialog | shared_saved_view row for Casey | | prime-sharing savedView 1 owned by alice | pass (chromium, firefox, webkit) | [#665](https://github.com/QRun-IO/qqq/issues/665) |
| WID-040 | dashboard renders app widgets in declared order sized by gridColumns (twelfths) | order matches metadata; 12 spans the grid; 4 is a third | | sample SampleWidgetsDashboard | pass (chromium) | [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-041 | widget chrome: card vs plain (isCard=false), sublabel, metadata and payload footer HTML | plain widget has no card border; footers render | | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accPlain, widgetGallery/accGeneric | pass (chromium) | [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-042 | header icons (topLeftInsideCard/topRightInsideCard) with metadata colors | icon names, colors and placement | | sample SampleWidgetsDashboard | pass (chromium) | [#538](https://github.com/QRun-IO/qqq/issues/538) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-043 | widget label tooltip from metadata on hover and keyboard focus | hover and focus show, Escape hides | | sample SampleWidgetsDashboard/SampleBarChartWidget | pass (chromium) | [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-044 | widget help content (HTML) on the label | help icon shows sanitized HTML help | | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accHelp | pass (chromium) | [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-045 | reload button re-renders the widget from the backend | render count increments per reload | no reload button without showReloadButton | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accReload | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-046 | export button downloads the widget's csvData as CSV | file bytes match Material quoting rules | no csvData: no-data message and no download | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accExport, accExportEmpty, accControls | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-047 | PVS dropdown from the widget payload: selection is sent as a parameter and re-renders | options from payload, choice reaches renderer, clearing removes it | non-stored selection is not persisted | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accDropdownHtml | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-048 | DATE_PICKER dropdown sends the chosen date | date reaches the child renderer | | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accControls | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-049 | required dropdowns: the parent waits with the backend message until selections are made | message then children render with selections | | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accControls | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-050 | storeDropdownSelections persists selections across reload; stale stored options are dropped | localStorage qqq.widgets.dropdownData.<widget>.<param>; reload keeps selections | removed option in storage is not selected | tests/acceptance/fixture/WidgetsFixtures.java widgetControls/accControls | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-051 | failing widget renderer shows a contained error with the backend message and retry | error message, retry re-requests, neighbor content | no error toast, no console error | tests/acceptance/fixture/WidgetsFixtures.java widgetStates/accError + accHealthy | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-052 | empty payloads show descriptive empty states for every display type | table, multiStatistics, charts, stepper, statistics 0, composite, multiTable, field values, map, html | | tests/acceptance/fixture/WidgetsFixtures.java widgetStates/accEmpty* | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-053 | malformed payloads are contained to their own widget | chart, table, composite, stepper, multiStatistics show a format notice | no crash, no console error, neighbors render | tests/acceptance/fixture/WidgetsFixtures.java widgetStates/accMalformed* | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-054 | widget permissions: a denied widget is absent from metadata, not rendered and not requested; the API denies it | admin sees it | noPets: no card, no request, GET /widget 403, renderer not called | tests/acceptance/fixture/WidgetsFixtures.java widgetPermissions/accDenied (pet.accDenied.hasAccess) with the noPets persona | pass (chromium) | |
| WID-055 | parent widget TABS layout switches tabs and remembers the selected tab | tab click, arrow keys, stored tab restored after reload | | tests/acceptance/fixture/WidgetsFixtures.java widgetParents/accParentTabs | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| WID-056 | parent widget skips denied and unknown children without requests | allowed children render | noPets: denied child absent and not requested; unknown child skipped | tests/acceptance/fixture/WidgetsFixtures.java widgetParents/accParentDenied, accParentMissing | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-057 | composite renders every block type with its values and styles | TEXT, BIG_NUMBER, UP_OR_DOWN_NUMBER, NUMBER_ICON_BADGE, ICON, TABLE_SUB_ROW_DETAIL_ROW, PROGRESS_BAR, DIVIDER, IMAGE, AUDIO, INPUT_FIELD, BUTTON; Unmapped legacy icon names render local glyphs in light/dark desktop/mobile modes; metadata navigation and main tiles preserve color, sizing, existing Lucide mappings and same-origin font delivery | | tests/acceptance/fixture/WidgetsFixtures.java widgetBlocks/accBlocks and accLegacyIcons | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-058 | composite layouts and slot links/tooltips | FLEX_COLUMN, FLEX_ROW, BADGES_WRAPPER, TABLE_SUB_ROW_DETAILS, FLEX_ROW_SPACE_BETWEEN, FLEX_ROW_CENTER, FLEX_ROW_WRAPPED; link to /app/person with tooltip | | tests/acceptance/fixture/WidgetsFixtures.java widgetBlocks/accBlocks | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-059 | unsupported block type shows a contained warning | Unsupported block type: OWNED_UNKNOWN; neighbor healthy | | tests/acceptance/fixture/WidgetsFixtures.java widgetBlocks/accBlocksUnknown | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-060 | record-view section widgets receive the record context (id, tableName) | record 1 and 2 show their own values | renderer enforces the user's table read | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost sections accHostHtml, accHostFieldValues | pass (chromium) | |
| WID-061 | process widget for a persona without process permission is contained | error shown in the widget; neighbor healthy | process metadata 403, no process run | tests/acceptance/fixture/WidgetsFixtures.java widgetProcess/accProcess with the noProcesses persona | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-062 | custom component whose bundle cannot load shows a contained error | Error loading MissingOwnedComponent | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accCustomComponentMissing | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| WID-063 | alerts with hideWidget or without HTML render nothing | no card rendered | | tests/acceptance/fixture/WidgetsFixtures.java widgetGallery/accAlertHidden, widgetStates/accEmptyAlert | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) [#666](https://github.com/QRun-IO/qqq/issues/666) |
| RPT-001 | TABLE report runs through its process and downloads CSV matching the database | CSV header labels and every person row from SQL | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPersonReport | pass (chromium) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-002 | TABLE report downloads XLSX with the same rows | sheet rows match SQL | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPersonReport | pass (chromium) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-003 | TABLE report downloads JSON with the same rows | JSON objects match SQL | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPersonReport | pass (chromium) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-004 | SUMMARY report groups rows with counts and a totals row | pets per owner match SQL group by; Totals row | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPetSummaryReport | pass (chromium) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-005 | PIVOT report produces a native pivot table in XLSX | xlsx contains the pivot table and cache over the rows sheet | CSV omits the pivot view (no native pivots) | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPetPivotReport | pass (chromium) | [#664](https://github.com/QRun-IO/qqq/issues/664) [#484](https://github.com/QRun-IO/qqq/issues/484) |
| RPT-006 | report input fields: required validation and the value filters the rows | minimumId filters rows >= value; Standard report inputs retain compact widths and an inline Generate Report action without a process header | empty required input blocks submit | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accPersonInputReport | pass (chromium) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-007 | report permissions: restricted report denied in UI and by the server | admin can run it | noPets: no-permission message, legacy route 403 | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accRestrictedReport with the noPets persona | pass (chromium) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-008 | reports appear in app navigation and open their run page | sidebar REPORT nodes navigate to the report page | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports app | pass (chromium) | [#664](https://github.com/QRun-IO/qqq/issues/664) [#651](https://github.com/QRun-IO/qqq/issues/651) |
| RPT-009 | saved report view shows its filter/columns and pivot setup | filter criteria, columns, pivot rows/values labels | | prime-sharing savedReport 1; tests/acceptance/fixture/WidgetsFixtures.java owned saved reports | pass (chromium) | [#550](https://github.com/QRun-IO/qqq/issues/550) |
| RPT-010 | saved report renders through renderSavedReport and downloads the saved columns and rows | format chosen, CSV contains the saved columns and every species | | prime-sharing savedReport 1 (Pet Species Report) | pass (chromium) | [#645](https://github.com/QRun-IO/qqq/issues/645) [#661](https://github.com/QRun-IO/qqq/issues/661) |
| RPT-011 | streaming report route for reports without a process | CSV streamed from /reports/{name}?format=csv matches SQL | | tests/acceptance/fixture/WidgetsFixtures.java acceptanceReports/accStreamedReport | pass (chromium) | [#664](https://github.com/QRun-IO/qqq/issues/664) |
| RPT-012 | scheduled report: create a schedule for a saved report and view its cron schedule | schedule persisted with cron description; record view shows the schedule widget; Add new on the saved report opens the Add Scheduled Report dialog over it with Saved Report preset and locked (Material #/createChild=); Create submits savedReportId=1, closes the dialog and reloads the child list | invalid cron expression rejected with message | scheduledReport (memory) for savedReport 1 | pass (chromium) | [#673](https://github.com/QRun-IO/qqq/issues/673) [#714](https://github.com/QRun-IO/qqq/issues/714) |
| RPT-013 | sharing: the owner shares a saved report with a user (Read-Only) | dialog lists Bob; shared_saved_report row READ_ONLY | | prime-sharing savedReport 1 owned by alice | pass (chromium) | [#665](https://github.com/QRun-IO/qqq/issues/665) [#444](https://github.com/QRun-IO/qqq/issues/444) |
| RPT-014 | sharing: change a share's scope to Read and Edit | row scope READ_WRITE | | prime-sharing savedReport 1 | pass (chromium) | [#665](https://github.com/QRun-IO/qqq/issues/665) |
| RPT-015 | sharing: remove a share | row deleted, list empty | | prime-sharing savedReport 1 | pass (chromium) | [#665](https://github.com/QRun-IO/qqq/issues/665) |
| RPT-016 | sharing record security: another user cannot see an unshared report until it is shared | bob sees it after sharing (UI list + API) | before sharing: not in bob's list, GET 404 | prime-sharing savedReport 1 (alice), user bob | pass (chromium) | [#665](https://github.com/QRun-IO/qqq/issues/665) |
| RPT-017 | sharing: only the owner may share | Share disabled with reason for the non-owner | server rejects a non-owner share; no row | tests/acceptance/fixture/WidgetsFixtures.java savedReport owned by casey shared with alice | pass (chromium) | [#665](https://github.com/QRun-IO/qqq/issues/665) [#371](https://github.com/QRun-IO/qqq/issues/371) |
| RPT-018 | sharing a saved view through the share dialog | shared_saved_view row for Casey | | prime-sharing savedView 1 owned by alice | pass (chromium) | [#665](https://github.com/QRun-IO/qqq/issues/665) |
| WID-064 | cronUI schedule editor on record forms: Basic days/hours/minutes, Advanced expression, live description, validation, time zone | Basic mode builds weekdays, hours and minutes; the live description equals the cronDescription QQQ stores; Basic mode builds selected dates, Clear resets the schedule, and the Advanced hint names the part under the keyboard caret; Advanced mode edits the stored expression; the live description equals the backend widget description; expression and time zone persist (SQL); an expression Basic mode cannot show opens Advanced with Basic disabled and the reason | an out-of-range expression shows the error live and marks the input invalid; saving without a schedule shows the required error in the editor, focuses it, and saves nothing | scheduledReport (memory) for savedReport 1; tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost section accHostCron with a time-zone enum possible-value source | pass (chromium) | [#702](https://github.com/QRun-IO/qqq/issues/702) |
| WID-065 | record view mounts only the active layout, so each section widget mounts and loads once | desktop tabs: every section widget is in the DOM once and requested once; no accordion is mounted; phone accordion: only open sections mount; each widget is requested once, on opening | the desktop tab layout is not mounted at phone width | tests/acceptance/fixture/WidgetsFixtures.java accWidgetHost | pass (chromium) | [#706](https://github.com/QRun-IO/qqq/issues/706) |
| WID-066 | Widget metadata and widget data come from the v1 API | v1 /metaData lists permitted widgets with hasPermission and the reports map; the dashboard renders its widgets from POST /qqq/v1/widget/{name} and makes no legacy metadata or widget request | a denied widget is absent from v1 metadata, never requested, and refused (403) by POST /qqq/v1/widget without rendering | widgetPermissions (accHealthy, accDenied), accPersonReport | pass (chromium) | [#699](https://github.com/QRun-IO/qqq/issues/699) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| WID-067 | a widget whose payload is a single leaf block (Material block widget) renders that block | The backend payload is the TEXT block itself (blockTypeName TEXT, no blocks list); The widget shows the block's text as a TEXT block | No empty COMPOSITE container is rendered in its place | WidgetsFixtures.java accLeafBlock (COMPOSITE widget whose renderer returns a TextBlockData) | pass (chromium) | [#714](https://github.com/QRun-IO/qqq/issues/714) |
| WID-068 | table widget Material extras: typed cells (numbers with separators, html, htmlAndTooltip, image, composite), hidden helper columns, fr widths, sub-rows, sticky header, column-header help, paging (10 by default, numbered pages, jump-to-page, entries-per-page), export built from columns and rows, and a multi-table widget with an export button and footer per table | Default cells show numbers with thousands separators; html cells render sanitized markup; an htmlAndTooltip cell shows the row's tooltip HTML on hover, focus or tap; an image cell shows the row's image, label and total; a composite cell renders its blocks; Columns of type hidden (tooltip, image helpers) are not shown; fr widths share the width left by fixed widths (2fr is twice 1fr; a 180px column keeps 180px); A row with sub-rows expands and collapses them (nested rows are shaded; a sub-row expands its own sub-rows); The columnHeader=name help slot shows its HTML help on the Name header; Without rowsPerPage 10 of 40 rows show, with numbered page buttons; the entries-per-page select (5-25) changes the page size; more than six pages become a jump-to-page input; the header stays at the top while the fixed-height body scrolls; Export without csvData downloads the Material CSV of every column (hidden ones too), quoted, with link text and without icon glyph text; Each table of a multi-table widget has its own label, export button (csvData or columns and rows) and footer; the widget itself has no export button | Icon glyph text (open_in_new) never reaches the CSV; Collapsed sub-rows are not rendered; A sub-table's footer is not shown under another sub-table | WidgetTableChartFixtures.java widgetTableCharts/accTableCells, accTablePaging, accMultiTableExtras | pass (chromium) | [#728](https://github.com/QRun-IO/qqq/issues/728) |
| WID-069 | chart widget Material extras: theme color names, legend toggles, single-series legends (horizontal bars, line badges), full pie with percent tooltip, stacked backgroundColor, stacked tooltip and right integer axis, rotating category labels, small line y ticks and grid, bar chart As of line | Pie slices named info and success fill with the Material theme colors; the pie is full (no inner radius); Hovering a slice shows its label, value and percent of the total; Clicking a legend entry hides that slice or series and clicking again shows it; Stacked datasets fill from their singular backgroundColor (a theme name or a CSS color); the tooltip names only the hovered dataset; the y axis sits on the right with whole-number ticks; long category labels turn; A single-series horizontal bar chart has a legend; a line chart names its series in badges above the chart; A small line chart shows y-axis values and a horizontal grid; A barChart widget ends with an As of line naming today's date | A hidden series draws no bars or points; No y-axis tick of the stacked chart is fractional | WidgetTableChartFixtures.java widgetTableCharts/accPieNamed, accStackedExtras, accLineBadges, accHorizontalLegend, accSmallLineTicks, accBarAsOf | pass (chromium) | [#728](https://github.com/QRun-IO/qqq/issues/728) |
| RPT-019 | render report input step shows only its fields; a report without variables adds no no-fields message | Report Format, Email To and Email Subject are shown; the values widget renders nothing | no "No fields" text on the input step | savedReport 1 (Pet Species Report, no variables) | pass (chromium) | [#707](https://github.com/QRun-IO/qqq/issues/707) |
| RPT-020 | Report files download from the v1 download and report routes | a process report links its file at /qqq/v1/download/... and the file has every row; a report without a process streams from /qqq/v1/reports/{name}?format=csv with every row | an unregistered server file or storage reference is refused (403); a restricted report is 403, an unknown one 404, a missing format 400 | accPersonReport, accStreamedReport, accRestrictedReport | pass (chromium) | [#699](https://github.com/QRun-IO/qqq/issues/699) [#406](https://github.com/QRun-IO/qqq/issues/406) |
| WID-074 | Standalone composite scalar controls and metadata editors | Seeded text, number, date, boolean and native file controls; currency prefix, named possible values and multiline code editor; Choice width and center-click behavior with system and monospace fonts, including a 320px viewport; Backend uppercase/lowercase metadata preserves middle-of-value edits and caret position in standalone blocks; Multiline TEXT case conversion preserves the middle-edit caret and inserted newline; Desktop wheel scrolling preserves plain and shared numeric block values; intentional ArrowUp edits still work and text inputs retain focus; Native standalone TIME controls increment by one second and retain second-level values. | Code-editor Enter leaves the widget on the same page; Multiline Enter does not submit or navigate away | widgetInputEditors / accTypedInputs | pass (chromium) | [#728](https://github.com/QRun-IO/qqq/issues/728) |
| WID-075 | Standalone DATE_TIME blocks localize seeded instants without losing seconds | UTC timestamp displays the correct local date, time and seconds | Do not slice UTC digits into a local-time field | accTypedInputs in America/New_York | pass (chromium) | [#728](https://github.com/QRun-IO/qqq/issues/728) |
## Contextual help, help search and authoring
| ID | Feature | Scenarios | Negative | Fixture | Result | Issues |
|---|---|---|---|---|---|---|
| HLP-001 | App and widget help | summary tooltip; keyboard panel; sanitized long markdown; focus restoration; light and dark; desktop and mobile | Strict diagnostics; no off-spec API calls | HelpFixtures.java | not run | |
| HLP-002 | Table, field and section help | view screen; edit screen; create screen; role selection | Strict diagnostics; no off-spec API calls | HelpFixtures.java | not run | |
| HLP-003 | Query columns and table action help | column summary; query table header; action is not invoked by help | Strict diagnostics; no off-spec API calls | HelpFixtures.java | not run | |
| HLP-004 | Process, step and record action help | process header; step header; record menu keyboard control | Strict diagnostics; no off-spec API calls | HelpFixtures.java | not run | |
| HLP-005 | Help search | static Search help command; debounced help query; result summary and panel; Go to link | Strict diagnostics; no off-spec API calls | HelpFixtures.java | not run | |
| HLP-006 | Help author affordance | key and specific role URL presets; standard create form; read-only users cannot author | Strict diagnostics; no off-spec API calls | HelpFixtures.java | not run | |
The help rows above have not been executed locally; historical results above predate this change.
## Release notes UI API extension
`release-notes` 1.0.0 (qbit-release-notes 0.1.0-RC.1+) provides What's new at
`/app/whats-new`: category groups, new badges, append paging, safe links/screenshots,
release deep links, seen tracking and the user-menu unread count. Extension discovery
hides the menu and prevents requests on unsupported servers. Covered by the release-notes
client/page and Sidebar Vitest suites; real-backend acceptance remains pending.
File diff suppressed because it is too large. Load diff
+554
View File
@@ -0,0 +1,554 @@
# Next UI visual review
> **Current status lives in GitHub:** [Next UI 1.0 roadmap](https://github.com/QRun-IO/qqq/issues/713). This document records scoped evidence and historical checkpoints; older open/closed wording and summary counts do not override current issue status. See [tracking policy](../ISSUE-TRACKING.md). Final 1.0 remains held for owner testing and explicit approval.
Status: in progress, September 30, 2026. This review is a release requirement. Captured screenshots are not approvals or evidence that a surface has passed review.
The visual reference is the working Next UI before the September 26 parity integrations, commit `e42ad2b2bcdc76311e13002a22dd70e3ec437192`. Material Dashboard remains the reference for supported workflows and metadata contracts. The user's September 27–28 correction requires preserving the earlier Next design while completing those workflows.
## Latest checkpoint — September 30, phone process states
Compared original Next `e42ad2b2bcdc76311e13002a22dd70e3ec437192` with the packaged RC8 candidate `f0f0eae98e4b6c883475956a7c59e060d2e5f0a5`, using the same fresh fixture classes and backend. All 24 phone images were inspected: light/dark, original/current, bulk-load mapping at four scroll positions, process progress and expanded internal error. All requested scroll positions were reached, with no setup errors, page errors or document horizontal overflow. Evidence: `rc8-phone-process-review/` manifests and review.
Progress and expanded-error composition retain the original arrangement. Bulk-load field cards and footer retain their arrangement, but saved-profile/reset controls, metadata help and larger touch targets make the upper section taller. Grouped Add Fields and the red removal icon also differ from the original. These are recorded differences, not owner approval. Header breadcrumb crowding remains open. This checkpoint covers these phone states only; whole-product visual and interaction approval remain open.
## Earlier checkpoint — September 30, reachable column menus
VIS-031: column menus were not constrained to available viewport height. A browser probe on the report preview measured a 352px menu starting at y=-147 in a 320px window in both Chromium and WebKit. The shared menu now uses Radix's available-height limit, an 8px collision margin and vertical scrolling, matching existing query menus. Its labels, ordering, typography and normal-height presentation are preserved.
The corrected regression first failed at 320px and passed at 720px in both engines. All 28 final Chromium/WebKit checks pass, including bounded placement, End/Escape and focus return, pointer Copy, sorting, standalone column actions and report variant workflows. Four final menu screenshots were directly inspected. Seven grid component tests, production export, types/lint/license and bundle budgets pass. Evidence: `column-menu-{probe.json,corrected-red-report.json,green-report.json,green-artifacts,unit.log,build.log,budget.log}` under local visual-review evidence. The first regression incorrectly expected a statistics action absent from Person metadata; that test assumption was corrected before the valid red run. No product change addressed that test-only error.
Hosted WebKit run36739388053 failed WID-073 before copying: the trace repeatedly reports Copy full query values outside the viewport. Its report and trace are retained in `row-appearance-ci-webkit/`. The exact workflow passed locally before the change, so this reproduced short-window defect does not by itself prove that hosted failure resolved. A fresh hosted result is still required.
The prior combined table, record-spacing and report hidePreview source `a8e4d3b` separately passed **583 Chromium tests/all 391 required rows**, zero failures/skips/flaky results and three approved external-service exclusions. That full run predates this menu correction; evidence is `report-preview-full-chromium/`. RC4 remains the published version; final 1.0 and whole-product visual approval remain held.
## Earlier checkpoint — September 30, forms, processes and dark widgets
At post-RC4 source `9fb613d`, inspected all 88 images from 64 desktop entries for 16 form/process states in both themes. These include Create, validation, Edit, Copy, Field Lab, process components/help/validation/review, route details/validation/confirmation, cancellation and widget results. All entries have export fingerprints, no setup/page errors and no document overflow. Layouts retain the original Next composition. The broken image in Lab briefing is the deliberate script/onerror sanitization fixture, not a newly broken image feature. A focused axe scan of the default process stepper reports no color-contrast violations or incomplete checks in either theme; its colors were not changed. Evidence: `post-rc4-form-process-review/`. This does not cover all process interactions, bulk-load/progress/error variants or responsive states.
Also inspected 22 fresh dark desktop images (eight entries) covering the full scrolled Widget Gallery, Blocks, Parents, and Tables and Charts pages. Every requested scroll reached its target; no setup/page errors or document overflow occurred. The existing Next card composition remains. Changes include functional maps, typed table cells, chart colors/legends, supplied footer metadata and native dark audio controls. No new visual regression was found in this inventory. This closes the current dark desktop lower-page gap for these four pages only. Evidence: `post-rc4-widget-review/` (11 directly inspected paired sheets). Other themes, responsive layouts, interactive widget states and whole-product approval remain open.
## September 30 — report summary preview flag compatibility
Source comparison found that Material's `hidePreview` hides the read-only summary preview but leaves the editing query available (`FilterAndColumnsSetupWidget.tsx:483-526`). Next incorrectly applied the flag inside the editor too. The bounded correction retains the existing Next editor and scopes this flag to the summary. It is not in published RC4.
A real-backend regression failed for the hidden-summary case while the ordinary case passed. After correction, eight Chromium report scenarios pass with zero failures/skips/flaky results, covering hidden/shown summaries, editor sorting/Refresh, Cancel, OK, host Save/reopen, query permissions, filter indicators and saved omitted joins. All 1,884 units, production export, types, changed-file lint and license checks pass. Four fresh editor images were directly inspected (both themes, flag enabled/disabled); dialog and preview bounds are identical, with no page errors or document overflow. Evidence: `report-preview-{red,green}-report.json`, `report-preview-{unit,build}.log`, and `report-preview-fixed-review/`. Broader embedded-query equivalence is still open.
## Earlier checkpoint — September 30, record detail review
Captured and inspected six desktop record states in light and dark themes against the original Next: small record, Person, selected section with keyboard focus, the field/adornment lab (including its lower scroll position), empty Audit and Delete confirmation. The 24 manifest entries contain 28 images, with no setup/page errors or document overflow. Evidence: `post-rc4-record-review/` under the local visual-review directory. Card placement and dialog composition are retained; added audit sort controls, code expansion and widget chrome remain visible. These captures do not approve populated audits, every field interaction or responsive record states.
Found VIS-030: overview field values inherit 16px/24px typography after the metadata grid migration, increasing plain rows from the original 42px to 46px. Browser measurements on both exports establish the difference (`spacing-probe.json`). Restored the original overview line height while retaining metadata widths and the selected-section layout. Both new Chromium cases failed at 24px versus 20px before the change; all nine affected record workflows pass after it. Fresh measurement matches the original five Person field heights exactly (42px for plain values, 43px for the boolean badge). All 1,884 unit tests, production export/types and changed-file lint pass. Eight fresh after-fix entries (ten images including the lab scroll states) were inspected in both themes with no setup/page errors or document overflow; evidence is `record-spacing-fixed-review/`. The initial browser launch used an inactive default backend port and is retained separately from the valid regression run. The correction is not in published RC4.
## Earlier checkpoint — September 30, query rows and filter review
Compared 14 desktop query/filter states in both themes between the original Next export and the published RC4 JAR: 56 entries, no unexpected setup/page errors or document overflow. The retained baseline export fingerprint matches the earlier review. Inspected table, Basic/Advanced, populated/nested filter, quick editor, columns, column-menu, relative-date, density, selection, Views and Export captures. Three added controls have no equivalent original state and are marked accordingly. Evidence: `rc4-query-review/` under the local visual-review directory. This is a desktop subset, not whole-query or whole-product approval.
Found VIS-029: unconditional alternating row shading added by parity commit `4c7918f` changes the original Next table, especially in dark mode. Restored the original uniform default while retaining selected-row highlighting, opaque pinned cells, theme overrides and the `data-row-parity` customization hook. The correction is on the post-RC4 branch and is not in published RC4. Both new rendered-color tests failed against unchanged RC4, then passed on the corrected export. All 1,884 unit tests, production export/types, changed-file lint and 13 existing Chromium theme/density/pinning workflows passed.
The first browser attempt had a stopped server; a subsequent temporary symlink was not discoverable by the backend classpath scanner. Those setup failures are retained separately. The new pinned-cell test initially matched nested transparent text spans as well as table cells; correcting its selector produced two passing light/dark cases without further product changes. Corrected-build screenshots from that selector-failure run were inspected and show uniform rows with opaque pinned cells; their origin remains explicit. Fresh corrected-export captures are now complete and inspected for the small table, Person table, populated filters and selected rows in both themes (eight entries). Their manifests contain no setup or page errors. Evidence: `row-appearance-fixed-review/` under the local visual-review directory. Broader visual review remains open.
## Earlier checkpoint — September 29, report omitted-join preservation
Inspected the corrected report editor's Filters and sort and Columns tabs in desktop light and dark mode. The existing dialog, tabs, typed filter controls, sort controls, column picker, preview grid and footer remain in use. Saved joined fields remain labelled and editable even when omitted from new choices; the preview still shows their values. This inspection covers the affected state and does not approve the whole report family or the broader original-Next comparison.
Four captures and geometry/runtime results are retained in the local visual-review directory: `report-editor-omission-{light,dark}.png`, `report-editor-omission-columns-{light,dark}.png`, and `report-editor-omission-visual-result.json`. At 1440×1000 the dialog is 1376×936 with 32px margins in both themes, no document overflow and no page errors. The Columns tab keeps its existing internal scrolling. The isolated visual server used a separate sample database and was stopped after capture; owner previews were untouched.
The destructive open-and-OK regression was observed failing before the correction. Current verification: 1,884 unit tests, production export, types, affected lint and three real-backend Chromium report workflows pass; independent review found no further defects. The full current Chromium gate passed 575 tests and all 391 required rows, with zero failures, skips or flaky results. RC3 does not include this follow-up.
## Earlier checkpoint — September 29, script suggestions
Added caret-adjacent suggestions inside the existing script editor: QQQ API/logger helpers, language keywords and file identifiers. The parent dialog, textarea, split panes and syntax overlay are retained. Inspected desktop light/dark captures under `test-results/acceptance/script-completion-evidence`; list bounds and both-theme axe checks pass across all five profiles. This focused review does not approve the whole developer-tools family.
The new interaction passes 1,840 unit tests, types, affected lint, license, static export and bundle budgets; 15 Chromium developer/script workflows and 10 focused cases across all five profiles. Native WebKit groups consecutive edits differently from Chromium: the initial cross-browser assertion failed after a fill plus completion, and a plain-textarea probe reproduced it. Retained failures; no production undo workaround or retry was added. The replacement check starts from a loaded revision and verifies native undo and redo, then separately tests typed-prefix completion and SQL-backed save/reload. Source/caret validation and linear prefix scanning correct both independent review findings.
Baseline full Chromium at 95847c2 is now terminal: 556 passed, all 390 required rows, no failures/skips/flaky tests. Backend PR #913 is merged. The broad original-Next visual review and final release gates remain open.
## Earlier checkpoint — September 29, report filter indicators and API cache correction
Report previews now feed complete nested criteria into the shared grid’s existing filtered-column indicator. Clicking it opens the current filter without adding a condition. The read-only preview keeps edits local. Inspected the real-backend desktop light editor and read-only captures in `report-filter-indicator-final-artifacts`; the existing grid indicator and layouts are reused. The captured editor is scrolled to its active condition. This is a focused inspection, not whole-family approval.
The previous full local Chromium export (303ba61) passed **554 tests**,390 required rows,zero failed/skipped/flaky. The subsequent indicator change passes **1,828 unit tests**,types,affected lint,license,static production export,bundle budget and **30 real-backend Chromium report workflows**. The first focused browser run mistakenly served the previous static export after a standalone build; its missing-indicator failures are retained separately and are not evidence about the corrected artifact. The proper static export was then rebuilt and verified.
Remote eeab664 CI is terminal: Chromium549pass; Firefox WID-070 widget-loading timeout; WebKit NAV-023 internal navigation error; touch WID-072 application-API500,NAV-034 redirect timeout,REL-005 internal navigation error. Logs and traces are retained. The API500 was reproduced as a concurrent static metadata-cache race in QQQ; backend62461e163 fixes atomic initialization and endpoint-test fixture isolation, with110module tests and coverage passing. PR913 remains pending new CI/merge. All40 focused rechecks of the failed workflows pass across Firefox,WebKit,phone and tablet on the corrected export/backend. The original failures remain recorded; a focused recheck does not establish that all navigation and loading failures are resolved. Full final-source/browser-matrix verification and the original Next visual review remain open.
## Earlier checkpoint — September 29, report backend variants
Report previews now reuse the existing Next variant picker for tables that require a store/tenant selection. No query runs before selection; switching starts a fresh preview page and scopes query/count, copy and statistics. The picker works nested inside the report editor. Light/dark desktop captures show it above the parent dialog and the selected variant above the preview, with no horizontal overflow or page errors. No changes were needed to the shared picker's layout or stacking. Captures: `report-variant-picker-{light,dark}.png`, `report-variant-{light,dark}.png` and `report-variant-visual-result.json` in the local visual-review directory. This does not approve the full report family.
Depth verification passes 35 real-backend Chromium scenarios, all 1,826 units, production build, types, affected lint, license and bundle budget. Both ordinary and application-API previews are covered. A review-found delayed-copy race was reproduced then corrected: a response from the previous variant cannot overwrite a later clipboard copy. Independent frontend/backend reviews have no remaining findings in this scope.
The application-API browser run exposed a backend defect: its query/count executors ignored the received variant. QQQ PR #913 applies the ordinary endpoints' session handling. Two HTTP regressions failed then passed; all 109 API middleware tests and coverage pass. Backend build succeeds under its configured static-analysis gates, with warnings. The 35 browser checks used that corrected local middleware JAR; PR CI/merge is pending. Full embedded-query parity, original-Next visual review and release acceptance remain open.
## Earlier checkpoint — September 29, report saved-view resets
Report setup now exposes Reset Changes and Reset to New View beside the selected saved view. The first restores the imported view; the second starts a default draft. Both discard unapplied Basic quick-filter values, and neither updates the saved view or report. The stale quick-filter regression failed for both reset actions before the correction, then passed. Independent review confirmed the correction with no remaining findings in this scope.
The rebuilt export passes all 25 report/sharing Chromium scenarios (zero skips/flaky tests), 1,823 unit tests, types, affected lint, license headers and the bundle budget (1,035.8 KB / 1,050 KB). New depth cases verify copy limits, missing COUNT capability and empty preview behavior against the real backend, with persisted report and saved-view comparisons. This is targeted verification, not complete embedded-query equivalence.
Inspected desktop light/dark captures show the reset links beside the view selector, with readable controls, no dialog horizontal overflow and no page errors. Existing dialog, filter and preview composition is preserved. Evidence: `report-reset-{light,dark}.png`, `report-reset-visual-result.json`, `report-reset-chromium.log` and `report-reset-final-units.log` in the local visual-review directory. The broader original-Next comparison remains open; no complete screen family is approved.
## Earlier checkpoint — September 29, process details and RC1
Fresh Chromium desktop comparisons cover process widgets, results, collapsed internal errors and expanded error details in light and dark modes: 16 original/current captures, with no setup/runtime errors or document overflow. The inspected widget, result and expanded-error pairs preserve the original Next card and action composition. Reload controls remain compact; desktop widget content is slightly shorter than the reference. These checks do not approve the complete process family.
The expanded-detail review led to **VIS-028**, a reproduced accessibility defect in the application-theme configuration: its orange error color yielded 2.8:1 contrast for 12px text. Default light/dark passed. Process details now use the same derived dark/light error-text shades as the query error panel. Text size, position and disclosure behavior are unchanged. The three-theme regression first failed on the application theme, then passed; nine real-backend Chromium error/retry/completion cases and all 1,806 units pass. Build, types and affected lint pass. Evidence: `process-detail-contrast-{red,green,build,unit}.log` and `rc1-process-visual-{light,dark}.log` in the local visual-review directory.
RC1 preparation separately passed the full 544-case Chromium gate (before this one-line contrast correction), clean JAR verification, dependency audit and focused checks. RC1 was subsequently published from `e901df9` on September 29, with Maven Central and GHCR artifact verification recorded in the release notes. See the [RC1 notes](../releases/1.0.0-RC.1.md). The original Next visual review and all remaining Material behavior parity gaps remain final 1.0 work.
## Earlier checkpoint — September 29, appearance preferences
The user-facing Preferences dialog now exposes Light and Dark choices using native radio controls within its existing layout. The choice applies immediately and persists under the existing `qqq-dark-mode` browser/origin key; a fresh profile remains light. An application theme still requires light mode, now with a visible explanation and disabled choices. The saved preference is retained under that override; Reset to Defaults clears it to light along with table and record defaults. No system-following or account synchronization is claimed.
The initial two integration tests failed because the controls were absent. The completed change passes **1,803 unit tests** and **15 real-backend browser cases across all five profiles** (NAV-056 appearance and NAV-038 shell touch/focus). The browser audit initially caught transitional colors while switching palettes; it now waits for actual CSS animation completion before checking the whole visible dialog against WCAG A/AA rules. A subsequent tablet diagnostic caught a metadata request interrupted by the test's immediate reload; the scenario now waits for the application theme to load before reloading. Failure logs/traces are retained under `appearance-transition-*` and `appearance-reload-*`. Build, types, changed-file lint, license and bundle budget pass (1,034.5 KB / 1,050 KB).
Refreshed original/current Preferences captures in both themes at desktop, tablet and phone sizes, including the dialog's own scroll region. Inspected current light desktop/phone top and dark phone bottom: controls remain within the viewport, the footer remains available, and lower layout options are reachable by scrolling. The screenshot tool was corrected to target the Preferences scroll area rather than the phone navigation drawer. Current export fingerprint: `d87518fddea70bd830c3fb10ea3ebaae41410feca77f6b6df2daf56a24370c1a`. The gallery still has 106 light / 87 dark states per viewport; this refresh adds no new state IDs. No whole screen family is approved. Full remote acceptance on the previous pushed head is still running; this targeted result is not release acceptance.
## Earlier checkpoint — September 29, widget width and acceptance setup
The remote phone failure reproduced with a wider native font: the standalone choice stayed 224px while its neighboring scalar input measured 230px. The original width assertion is retained. The metadata-editor minimum is now `min(15rem, 100%)`: 240px where available, capped to 230px at the tested 320px phone viewport. A browser-only trial preceded the one-line source correction; the final export reproduces the trial without injected styles. System, Arial and monospace measurements and 320/393/810/1440px viewports show no document overflow. Plain scalar controls are unchanged.
The Firefox dashboard trace showed its page-opening hook ran before the owned backend session and diagnostics existed. A pre-navigation session assertion failed locally. The hook now initializes those fixtures first and verifies the owned session cookie; metadata and widget reads start after the reset. This corrects a definite fixture precondition. The original 17-second render delay is not claimed independently explained; full CI must verify that it does not recur.
All **105 targeted real-backend cases** pass across Chromium, Firefox, WebKit, phone and tablet: the complete sample-dashboard group and standalone input cases, including system/monospace center clicks and a 320px viewport. All **1,801 unit tests**, production export, types, lint, license and bundle checks pass. The original width and dashboard-heading assertions remain. Logs use `widget-ci-*`, `widget-width-*` and `widget-setup-red-browser.log`; the width red trace is preserved separately.
Refreshed two widget states in both themes at three sizes: 24 original/current entries. Inspected the current desktop editors and light/dark phone open-choice menu. Current captures match the final export with no setup/runtime error or document overflow. The original has no choice menu; its new state is explicitly marked non-equivalent. Recounting all six canonical manifests gives **106 light and 87 dark states per viewport**; this corrects the prior checkpoint's dark count after form captures were added. No whole family is approved.
## Earlier checkpoint — September 29, error readability and refreshed form review
**VIS-027 corrected:** the query error panel failed at 4.24:1 in default light mode, Retry hover at 1.8:1 in dark mode, and the configured orange error theme at 2.52:1. Error text now uses the existing theme-derived red-800 in light mode and red-400 in dark mode shades, with red-300 for dark Retry hover. The visible Sonner error notification separately failed at 4.34:1; its light surface now uses red-800. The configured orange pivot validation also failed at 4.25:1 and now uses red-800 in light mode. Panel, dialog and notification geometry, placement and timing are unchanged.
The broader browser run exposed lazy-fixture leakage: default report-theme tests did not request the backend reset and inherited the preceding configured theme. Its 34 passes / 16 failures are retained as diagnostic evidence. The report spec now requests the reset before every test; a deliberate configured-orange pivot case retains that contrast regression. Query and pivot checks assert the actual root theme. The final run passes **55 real-backend cases across all five profiles**: error panel/notification contrast, Retry hover and request, dismissal, SQL-verified recovery, report-link and pivot contrast, and theme metadata. All **1,801 unit tests**, production export, types, changed-file lint, license and bundle checks pass (1,034.1 KB / 1,050 KB). This is targeted acceptance, not final release acceptance. Logs use `query-error-{complete,pivot,final}-*`; confirmed earlier failures remain in their logs and `query-error-theme-red-artifacts`.
Refreshed original/current create, edit, copy, validation and field-lab forms in both themes at all three sizes:60 entries / 120 images, including scroll sequences. Inspected desktop edit, desktop field-lab middle/bottom, phone field-lab top and edit bottom, dark phone validation, tablet create, and dark desktop field-lab bottom pairs. Their form-body section spacing, field placement and footer positions match the original; the Person currency prefix is metadata-driven. This does not approve uninspected field interactions, modal forms or the whole forms family. Original/current error and Retry-hover captures were also refreshed. Final current-export refresh covers eight states per theme/size (48 entries): five forms, two query errors and pivot validation; no setup/runtime errors or document overflow, all with matching build fingerprints.
Inventory is **105 light / 84 dark states per viewport**. Other captures may carry older export fingerprints and must be checked before approval. No whole family is approved. Current preview on port 18769 serves the final contrast build; original on port 18768 remains the original Next design.
Full remote CI 36580709453 at 30db309 is now **terminal failure**: WebKit passed; phone WID-074 measured choice width 224px versus native scalar 226px, and Firefox WID-014 timed out waiting for the dashboard heading in beforeEach. The later failure snapshot contains that heading. Downloaded logs/traces are in `ci-36580709453-*`; neither failure is waived. The Firefox trace shows backend/diagnostics fixtures were never initialized before the failed page-opening hook. A phone font-loading probe did not reproduce the width failure: system-font scalar 201px and choice 224px stayed unchanged while the downloaded font was held/released. Investigate platform font sizing and hook setup next; do not change geometry or relax assertions without evidence. No merge or publication.
## Earlier checkpoint — September 29, query evidence and column-popup focus
**VIS-026 corrected:** opening Columns with Enter left focus on its toolbar trigger; activating the popup's Close button with Enter then left focus on the page body. The nonmodal popup now receives focus without scrolling or automatically opening a phone search keyboard. Explicit close returns to Columns; Escape keeps its existing focus return, and outside clicks retain focus on the clicked control. Embedded report column editors do not take focus when mounted. The popup layout and field controls are unchanged.
The component regression failed before the fix. A real-browser probe reproduced both focus failures; the corrected Playwright regression then failed at inactive popup focus. Its first attempt used a Testing Library matcher by mistake and is not counted as product evidence. After the fix, 48 affected component tests pass. The broader five-profile backend run passed 28/30; both failures assumed WebKit tabs to a button before search. A plain native-control probe showed Chromium/Firefox tab to Close while this WebKit runner tabs directly to search. The test now verifies those observed native paths while retaining popup focus, search, keyboard close, Escape and outside-click assertions. The final focus check passes all five profiles. Production export, types, lint, license and bundle checks pass. Logs use `column-focus-*`; the confirmed pre-fix artifacts are retained separately.
**Review evidence correction:** some older current captures still showed column widths that had already been fixed. The capture tool now stores a timestamp and SHA-256 fingerprint of the exported files in each new entry. The gallery displays them, flags untracked/older builds, and avoids resetting a newer scenario selection when an earlier fetch finishes. Six theme/viewport image-pair checks and both warning checks pass (`gallery-provenance-check.log`). Captures outside the refreshed query family still require freshness verification; they must not be treated as current approval evidence.
All 27 query states now have refreshed original/current entries in light/dark at desktop/tablet/phone sizes: 324 entries. Current entries have no setup/runtime errors or document overflow and match the final export fingerprint. Three added states cover column-popup keyboard focus, search and no matches. The original has no search control; those baseline captures explicitly state they are not equivalent interaction states. Inspected fresh desktop column configuration/menu/empty/error/loading states, desktop keyboard focus, tablet filtered columns and dark phone no-match presentation. Inventory is now 104 light and 83 dark states per viewport. No full family is approved.
**VIS-027 remains open:** the light query error panel fails a browser contrast check at 4.24:1 for its heading, message and Retry text (required 4.5:1). The dark panel passes. `query-error-contrast-probe.log` contains the measured foreground/background colors and affected nodes. Correct the text contrast while preserving configured status colors and the original panel layout, then verify both default and application themes.
Remote full CI36580709453 at30db309: WebKit passed; Chromium/phone/tablet and Firefox remain running. Keep local corrections unpushed until that run finishes. Full visual review, functional gaps and final release gates remain open.
## Earlier checkpoint — September 29, compact filter controls restored
**VIS-002 corrected for the reproduced desktop height regression:** the populated filter table started at y579, versus y535 in the original Next capture. The unsaved-view status forced a separate full-width row (24px), and the desktop filter header used 44px controls plus padding (20px above the original header height). A browser-only trial isolated both causes before the source change.
The status can now share available toolbar space at tablet/desktop widths; phone wrapping remains. Filter mode and sort controls use a compact 32px minimum on devices without coarse pointers and retain 44px minimums when any coarse pointer is available. The desktop panel header is41px, down from61px; the resulting panel and table positions match the original populated-filter capture. Basic/Advanced, sorting, ownership, save/reset actions and the single editable condition list remain intact. Tablet/phone measurements retain44px targets and no document overflow.
Verification: production export/typecheck, standalone typecheck, changed-file lint, license and bundle checks pass (1034KB/1050KB). All34 affected component tests and50 real-backend cases across Chromium/Firefox/WebKit/phone/tablet pass (QRY-080/083/084/051/093). These are targeted checks, not a whole-suite acceptance result. `filter-layout-red.log` preserves the pre-fix61px header failure; `filter-layout-trial.log` proves the browser-only correction; `filter-layout-green.log` checks the actual final export without injected styles. Other logs use `filter-layout-{unit,build,browser,types,license,budget}`.
Refreshed six current query states in both themes at three sizes:36 entries, no setup/runtime errors or document horizontal overflow. Inspected desktop light/dark populated filters, tablet populated filters and dark phone sheet. Original captures remain unchanged. Inventory stays101light/80dark states per viewport; no whole family is approved. Current preview includes this build. The full remote run36580709453 at30db309 is still running; general and image CI passed. Keep local corrections unpushed until the full run finishes. Full visual review and release gates remain open.
## Earlier checkpoint — September 29, copied-record timestamp persistence
The follow-up found ordinary record edits already protect unchanged timestamps by omitting them from PATCH. Base Copy and both child-row editor hosts submit complete values, however, and were discarding fractional seconds. The real-backend copy regression in America/New_York also reproduced a one-hour shift during the repeated autumn hour: `06:30:07.123456Z` became `05:30:07Z`. Copies and child edits now pass original record values through the existing wire converter; edited values retain normal local-to-UTC conversion.
Verification: **1,799 unit tests and 30 real-backend cases across five profiles pass** (REC-065/018/013/060 and both PRC-064 cases). REC-065 checks unchanged PATCH omission, the copy POST instant and source/copy SQL precision. Separate component regressions prove association and process child-row preservation; these are not claimed as new child-specific real-backend coverage. Production export, types, changed-file lint, license, whitespace and bundle checks pass (1033.9KB/1050KB). Logs use `record-datetime-*`; the confirmed pre-fix trace is retained in `record-datetime-red-artifacts`. The first browser attempt stopped at the existing unreadable-password copy guard; supplying a new fixture password allowed the timestamp defect itself to reproduce.
No layout or style changed in this correction, so existing visual captures were not repeated. Current preview includes the corrected export. Visual inventory remains101light/80dark states per size; no whole family approved. Full CI36580709453 at30db309 remains running across all browser jobs; general CI36580709451 and image36580709514 passed. Keep the new correction local until that full run finishes. Final visual review, complete release verification and publication remain open.
## Earlier checkpoint — September 29, date-time input review
**VIS-025 corrected:** zoned process values were assigned directly to datetime-local controls, which displayed blank fields. Plain standalone blocks sliced UTC digits into a local-time control and discarded seconds. Shared standalone editors displayed local values but sent unzoned text through their action callback. New component regressions and a fixed America/New_York browser context reproduced the failures.
Process fields and standalone blocks now reuse the existing date-time conversion helpers, retaining their native controls and seconds. Process payload conversion occurs after contributors. The converter can retain an original instant when its displayed wall time is unchanged, preserving the second occurrence of a repeated DST hour and hidden subsecond precision. Edited local times become UTC instants. Material's unadorned TEXT/HTML block mapping was inspected and confirmed to use ordinary text controls; no speculative editor redesign was made.
Verification: **1,796 unit tests and 30 real-backend cases across all five profiles pass** (PRC-064 unchanged/edited, WID-075, PRC-063/062 and REC-018). Backend SQL proves unchanged spring and autumn timestamps retain their instants; edits across the spring transition use the correct offset. A unit regression proves unchanged subsecond precision survives. Production export, standalone types, changed-file lint, license, whitespace and bundle checks pass (1033.9KB/1050KB). Logs: `block-datetime-{red-unit,callback-red-unit,precision-red-unit,red-browser,green-unit,unit,build,browser,types,lint,license,budget}.log`; pre-fix browser traces remain in `block-datetime-red-artifacts`.
Paired process timestamp and standalone editor captures were added/refreshed in light/dark at three sizes:24entries, no setup/runtime errors or document overflow. Inspected desktop process pairs, phone process inputs and dark phone standalone controls. The original process blocks show raw text and its regular form timestamps are blank; current controls show dates, seconds and the existing shared editor timezone hint. That hint adds height relative to the original block renderer and remains part of the broader composition review. Captures use UTC; separate acceptance uses America/New_York. Inventory101light/80dark states per size; no whole family approved.
Full CI36573705464 at121468b is now terminal: Firefox and WebKit passed, Chromium/touch failed dependency download before tests. This satisfies the hold on pushing local commits. Final full verification on the new source and the complete release audit remain required; no merge or publication.
## Earlier checkpoint — September 29, input configuration review
**VIS-024 corrected:** advancing a process moved focus from a configured autofocus input to the screen heading. A real-browser regression reproduced the inactive Plain Code field on screen two. The process now preserves focus already inside a marked autofocus editor; ordinary result screens still receive heading focus. Plain and shared metadata editors both pass the transition check.
Enter also inspected trimmed text while submitting the untrimmed process form. PRC-044 reproduced surrounding spaces in the actual SQL record. Plain/shared text blocks now normalize their own value before validation and submission. Required whitespace cannot advance; standalone shared blocks validate the normalized value before invoking their callback. Typed numeric/date/checkbox values and multiline, file and picker keyboard behavior retain their existing paths.
Verification: **1,791 unit tests and 25 real-backend cases across all five profiles pass** (PRC-037/044/062/063 and WID-074). Production export, types, changed-file lint, license, whitespace and bundle checks pass; total JavaScript is 1033.6KB gzip against 1050KB. PRC-063 checks placeholders, retained process values, first blur without premature validation, required whitespace, both autofocus paths, result heading focus and stored SQL text. Pre-fix traces remain in `block-enter-red-artifacts` and `block-focus-red-artifacts`; logs use `block-{enter,focus}-red-*` and `block-input-config-*`.
Added paired plain/formatted input focus captures in both themes at three sizes:24entries, no runtime/setup errors or document overflow. Inspected desktop plain pairs, phone formatted pairs and dark tablet formatted focus. These process cards preserve the original geometry; current required markers and focus rings are visible. Inventory is100light/79dark states per size. This closes the reproduced focus/Enter defects, not the full widget/process configuration audit or any whole-family visual approval.
Both previews use the updated fixtures; the original export is unchanged and the current export includes these fixes. Full remote run36573705464 at121468b: WebKit passed, Firefox running, Chromium/touch failed dependency download before tests. The final full release gate remains open; no merge or publication.
## Earlier checkpoint — September 29, record section keyboard review
**VIS-023 corrected:** record tabs had tab roles but no arrow navigation, single tab stop or tab/panel associations. The real-backend regression failed when Right Arrow left focus on Overview. Tabs now use manual activation: Left/Right/Home/End move focus; Enter/Space opens the section through the existing URL callback. This avoids mounting section widgets merely while moving focus. Tab enters the named panel, with a visible focus indicator. Phone accordion behavior is preserved. The approach follows the [WAI tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/).
Verification:1786unit tests, production export/typecheck, final types/lint/license/whitespace and bundle1033.5KB/1050KB pass. The related five-profile run passed32cases and found one ambiguous phone test selector (outer accordion and nested section share a region name). The test now follows the trigger’s actual aria-controls and retains name/value assertions; all five keyboard rechecks pass. Existing section/list/deep-link/joined-field checks passed. Logs: `record-tabs-{red-browser,green-browser,keyboard-green,unit,build,types,lint,final-lint,budget}.log`. The pre-fix trace is retained in `record-tabs-red-artifacts`.
Added record-section-focus and refreshed record-small pairs in light/dark at desktop/tablet/phone:24entries, no runtime/setup errors or page overflow. Inspected desktop original/current record card, dark tablet focus and phone accordion focus. Current layout retains the recovered original record composition; focus styling adds no layout spacing. Inventory is98light states/size and77dark. These are reviewed states, not whole-family approval. Configured/denied/error/loading variants and remaining form/widget review continue.
Local changes remain unpushed while fullCI36573705464 at121468b is active: Firefox/WebKit running; Chromium/touch failed during Corepack network setup before tests. General CI and image build passed. No merge or publication.
## Earlier checkpoint — September 29, compact report inputs restored
**VIS-020 corrected for standard report input screens:** restored the compact card, 384px ordinary field maximum, and left-aligned Generate Report action below the inputs. Removed the added visible Input/Step header and divided footer. Cancel remains a secondary inline action. The accessible form name, shared dynamic form, validation, typed values, possible values and process cancellation remain. Configured grid widths and rich editors retain their layout; longer or custom report processes retain their step navigation and components.
Before the change, two unit regressions failed because Generate Report was absent, and the real-browser layout regression failed because the extra Step header was visible. Final verification: **1786unit tests**, production export/typecheck, changed-file lint, license, whitespace and bundle checks pass. **30 real-backend cases across all five profiles pass**, covering report layout/required validation/database-matched output and shared mixed/filtered/required/composite process forms. Logs: `report-compact-{red-unit,red-browser,green-unit,full-unit,build,green-browser,lint,budget}.log`; pre-fix browser artifacts are retained in `report-compact-red-artifacts`.
Paired input, validation and result captures were refreshed in both themes at desktop, tablet and phone:36entries, no runtime/setup errors or document overflow. Inspected desktop input, phone input pairs, dark phone validation pairs, and tablet result. The normal card geometry now matches the original; inline Cancel and shared validation focus/message treatment remain visible differences. No whole report family is approved. Canonical inventory remains97light/75dark states per size. Current preview18769 includes this correction.
Final diff review kept rich/file/code/widget editors on their existing grid rules, including configured file widths. The final export,1786units and changed-file lint pass; RPT-006 passes again in all five profiles. Final logs are `report-compact-final-{build,unit,browser,lint,budget}.log`. Both current dependency audits report zero known advisories (316production dependencies;1038in the full audit); this does not replace the remaining application security checks.
Remote full acceptance36573705464 at121468b remains active: Firefox/WebKit running; Chromium/mobile/tablet failed before tests in Corepack dependency setup with network ETIMEDOUT/ENETUNREACH. Its log is retained as `ci-121468b-chromium-mobile-tablet.log`. General CI36573705788 and image36573705430 pass. Do not push the local report correction while the full run is live; final full verification and release remain open.
## Earlier checkpoint — September 29, saved-view reload correction
Full run36564701143 is terminal: Chromium/mobile/tablet and WebKit pass; Firefox failed QRY-093. The trace showed new filtered data before asynchronous page navigation updated the URL. Query URL changes now replace the current history entry synchronously through Next-integrated native history, preserving other parameters and the hash. No query layout was intentionally changed.
A deterministic Firefox regression failed before the fix with the old filter still in the URL. The corrected export passes25 saved-view, delayed-navigation, back/forward, quick-view and column-menu cases across five profiles, plus20 pagination, sorting, quick-search and return-navigation cases. The current tree passes1784unit tests, production export/typecheck, final standalone types, changed-file lint, license and whitespace checks; bundle1033KB gzip remains within1050KB. Logs: `query-url-{red-unit,red-browser,green-browser,related-browser,build,unit,types,lint}.log`. The final full run with this correction remains pending; these are focused checks.
The current preview includes the correction. Desktop report-input pairs were re-inspected against original source: original compact card has384px maximum-width inputs and an inline Generate Report action; current shared process screen adds header and footer dividers, step count and wider fields. VIS-020 remains open. Preserve the shared dynamic form/process lifecycle while restoring the report presentation. Full visual review and all family approvals remain open.
## Earlier checkpoint — September 29, composite metadata inputs
The block INPUT_FIELD integration now reuses Next's field editors for choices, formatting and supported adornments, retaining the process form and multipart upload path. Plain scalar input layout remains the reference. The richer code/filter/upload editors add genuine capabilities absent from the original block renderer; their additional height and composition are still under visual review. This does not approve the widget or process family.
**VIS-021, corrected in source:** the dispatcher initially treated the backend's default `%s` format as custom formatting. This changed ordinary scalar controls into the shared form layout. A fixture-based browser failure and unit regression exposed it; neutral formatting now preserves the original scalar path. Hidden and read-only metadata are respected in both hosts.
**VIS-022, corrected in source:** a standalone choice editor collapsed to approximately101px, placing its Clear button at the center of the control. A native hit test confirmed that a center click cleared the value instead of opening the choices. The original scalar input is approximately200px wide; standalone metadata editors now use a14rem minimum, also accommodating Firefox's approximately217px native input. The browser assertion compares their widths and retains the center click.
A typed-input fixture existed but was not registered, so the first new capture showed Page not found. It now has a dedicated `widgetInputEditors` app. The capture script treats unexpected not-found screens as failed evidence. Paired captures also cover `prcBlockEditors`, whose original renderer lacks the rich editors, and the unchanged `prcWidgets` route. These comparisons distinguish new capability from restored composition. Final verification and refreshed captures are recorded below when complete.
The remote full run36564701143 has WebKit success, Firefox520passes/1failure(QRY-093), and Chromium/mobile/tablet still running. The Firefox trace shows a concrete reload race: B-filter data appeared before asynchronous router replacement updated the address bar, so reload used the previous Avery filter. Fix remains open; no assertion has been weakened. Artifact11033840508 and its log are retained locally.
Final input-editor checkpoint: **1,783 unit tests pass**, production export/typecheck, changed-file lint, license and whitespace checks pass, and the bundle remains within1050KB gzip. **35 real-backend cases pass across five profiles**, comprising30 widget/process checks and five unchanged QRY-093 rechecks. Those rechecks do not resolve the proven CI URL race. Final logs are `block-editors-width-{build,acceptance,visual-light,visual-dark}.log`, `block-editors-final-{unit,types,lint,license,budget}.log`. The earlier width run's failures and traces are retained, including the aborted local run after repeated diagnosed failures; no remote job was canceled.
Canonical inventory:97light states/size(194original/current entries),75dark(150entries). All36 entries for the three compared scenarios have no setup/runtime errors or page overflow. Inspected final light desktop/phone standalone controls, light phone process lower controls, dark phone standalone controls, dark tablet process lower controls, and existing Widget Lab. Plain fields retain their compact treatment; actual adorned editors are larger than the original scalar controls. Complete configuration/autofocus and visual composition review remain open in the parity ledger. The two new acceptance rows are PRC-062 and WID-074; the generated whole-suite matrix still requires final release reconciliation.
## Earlier checkpoint — September 29, report interaction review
**Release not ready; full visual review incomplete.** Recovery head `06cf1b0` is now pushed to PR #14. General CI `36564701134` and image build `36564701149` pass; full acceptance `36564701143` remains running. The preceding full run ended with tablet/WebKit Export failures and Firefox saved-report opening delay. Local report review changes are verified separately and will stay local while the new full run is active.
Expanded the review with report inputs, required validation, completed output, filter variables, pivot editing/validation, schedule creation/day selection, sharing, and keyboard tab focus. The capture script now measures dialog-body scroll positions. Query/report dark coverage is paired at all three sizes; unavailable original editors remain explicitly non-equivalent. Other families still have dark/theme/interaction gaps. No whole family is approved.
**VIS-018 (medium), corrected:** the report filter/column tabs declared tab semantics but had no arrow-key behavior or labeled panel. Added automatic activation with Left/Right/Home/End, a single tab stop, a linked panel and visible keyboard focus using the existing script-tab pattern. Filter and column drafts persist across switches; hidden Columns metadata remains respected. Unit regression failed before the correction. The first browser run exposed Firefox's native tab stop on a scrolling panel at 1280×720; a native probe proved that an explicit `tabIndex=-1` moves Tab into the column search field. The assertion was preserved.
**VIS-019 (medium), corrected:** pivot validation text on its tinted background measured 4.12:1 in light mode and 4.21:1 in dark mode. Axe independently rejected both. The alert now uses darker light-mode red text and lighter dark-mode red text, preserving its border, tint, layout and validation behavior. Final native measurements are 5.48:1 in both themes, and all ten light/dark pivot contrast cases pass across the five browser profiles.
**VIS-020 (review open):** report inputs now use the shared process screen. The original compact input card and Generate Report button have gained an Input/Step header, wider desktop field, and Cancel/Submit footer. The full process lifecycle and dynamic fields must remain, but the additional height and action placement are not visually approved. Compare `report-inputs` and `report-input-validation` on desktop/phone before deciding the final presentation. Scheduling and sharing retain their original dialog layout in the inspected pairs; scheduling adds the metadata-driven Variable Values section. Its lower controls are reachable by scrolling.
Inspected pairs: populated desktop/phone filters; dark phone quick editor, columns and export; dark tablet relative-date menu; desktop/phone report inputs; phone schedule scrolls and sharing; desktop pivot; phone pivot validation including its lower controls; dark phone variables/pivot validation/sharing; dark tablet schedule Days popover. This list names reviewed evidence, not whole-family approval. Reports still need configured variants, denied/error/loading states, populated schedule/share interactions, complete keyboard closure, and review of the added editor composition. The functional embedded-query equivalence row remains Partial.
Evidence: `query-report-dark-completion.log`, `report-interactions-{initial,light,dark}.log`, `report-tabs-{red,green,focus-probe}.log`, `pivot-contrast-{light,dark,red}.log`; final verification and corrected screenshots follow this checkpoint. Guides now document report-tab keyboard behavior and the Firefox scroll-container distinction.
Final checkpoint verification: **1,773 unit tests and 45 real-backend report cases pass across five profiles**. Export/build typecheck, final standalone TypeScript check, changed-file lint, license headers, whitespace and bundle budget pass (1032.1 KB gzip / 1050 KB). Logs: `report-review-final-{unit,build,acceptance,budget}.log`, `report-tabs-focus-green.log`, `pivot-contrast-{light,dark}-green.log`. Corrected tab-focus and pivot-validation captures were refreshed in both themes at all three sizes and inspected on phone. Canonical inventory is now **95 light states and 73 dark states per size**, with 190/146 original/current entries. Report captures have no runtime/setup errors or document horizontal overflow. This remains partial release evidence; full visual review and the complete final release gate are open.
## Earlier checkpoint — September 29, inline profile naming restored
**Release not ready; full visual review incomplete.** PR #14 remains open at `4748b6c`; local recovery is verified separately. Full real-backend run `36557085746` now has Firefox519passes/1failure (saved-report record opening) and WebKit519passes/1failure (Export). Chromium/mobile/tablet remains running. WebKit's Export failure is corrected locally; Firefox is under diagnosis. Nothing is merged or published. No whole visual family is signed off.
The light inventory contains **85 scenarios at each of three sizes**. Dark coverage remains partial (47 desktop, 45 tablet, 47 phone scenarios, with some unpaired entries). Unavailable original interactions are explicitly marked. Captures are evidence to inspect, not approval.
New, Save As and Rename now use the original Next inline naming layout inside the profile section. Compact field/button styling, Enter behavior and focus return are restored while ownership, backend errors, copying and overwrite/delete confirmations remain. Paired light/dark captures at all three sizes show the recovered editor; added reset controls still increase the surrounding section height. This is not full process-family approval.
Current verification: **1,771 unit tests and 40 bulk-load real-backend cases across five profiles pass**. The earlier menu/Preferences checkpoint passed50/50 affected cases. Production export, final typecheck/lint, license and bundle budget pass (1031.9 KB gzip / 1050 KB). The PRC046 lifecycle test now targets the portaled menu correctly and verifies duplicate-name rejection. Parity recount is728Done,22DifferentUX,5Partial,2Missing,38N/A across795existing rows; the previous summary omitted one supplemental metadata row.
## Evidence and method
- Current source: `feature/next-1.0`; restoration, report editor and guide commits are integrated. The live current preview includes the verified recovery changes described below. Both comparison exports were refreshed on September 29.
- Both builds run the same current sample JAR and acceptance fixture sources, with fresh admin browser contexts and the same Inter font bytes. Historical-reference type checking is skipped only in its isolated build; the current export completes its normal type check.
- Local comparison gallery: <http://127.0.0.1:18770>. Earlier UI: <http://127.0.0.1:18768/app>. Current UI: <http://127.0.0.1:18769/app>.
- Evidence is retained under `../qqq-frontend-next/test-results/visual-review/`: source/build provenance, capture scripts, manifests, screenshot sequences, and server/build logs. It is ignored generated output, not a published artifact.
- Review dimensions: hierarchy, typography and spacing, color and icon treatment, control grouping, responsive layout, and interaction states. Confirm findings against source before changing defaults; preserve explicitly configured metadata behavior.
## Coverage
Current inventory: **106 light and 87 dark states per viewport**, each with original/current entries. The latest checkpoint refreshes standalone widget inputs and the open choice menu against the final export. Other states include older captures and need current-build verification before approval. The table below describes the initial inventory and its remaining review scope; later checkpoints document additional coverage, not whole-family approvals.
The initial pass captured 38 scenarios per build at desktop (1440×1000), tablet (810×1080), and phone (393×851): 228 build/state combinations and 447 screenshots, including long-page scroll sequences. Every entry viewport has received a paired visual scan. Detailed review of all scroll sequences, dark/configured themes, keyboard focus, and additional interaction states remains. No whole family is signed off yet. The baseline report-filter editor is unavailable because that functionality did not exist there; its capture is not an equivalent editor state. The desktop baseline delete capture was corrected during the populated-filter follow-up.
| Family | Captured desktop scenarios | Remaining review scope |
|---|---|---|
| Shell and navigation | Dashboard, app home, nested navigation, not found | Search, command menu, notifications, user menu, help, login/session, phone navigation, branding/theme variants |
| Query | Small and wide table, filters, advanced filters, columns, views, export | Populated criteria and groups, quick-filter editing, column menus, density, cards, selection/bulk controls, saved-view states, pagination, empty/error/loading |
| Records | Compact record, person, record lab, audit, delete | Every tab/list/card variant, associations, metadata menus, configured sidebar, collapse, sharing, warnings/errors |
| Forms | Create, edit, copy, field lab | Each field editor, validation, disabled/read-only, dirty state, full copy, file/association editors, modal forms |
| Processes | Wizard, component lab, widget step, bulk load | Subsequent steps, selection, validation review, progress, results, errors, cancellation, upload mapping |
| Reports | Run inputs, saved report, edit, filter/column editor | Filter variables, columns, pivot editor, scheduling, sharing, results/download, validation |
| Widgets | Sample dashboard, gallery, blocks, parents, controls, states, tables/charts, record widgets/editors | Map all 38 dispatcher labels to evidence; inspect every scroll capture, dropdown, tooltip, menu, loading/error/empty state |
| Developer tools | Table and record developer pages | Script editor/viewer, code tabs, dialogs, ESB metadata/tools, responsive layouts |
## Findings
| ID | Severity | Observed difference | Evidence | Status |
|---|---|---|---|---|
| VIS-001 | High | Wide query tables now use very narrow numeric/date/boolean defaults. Header labels are reduced to fragments, dates wrap across lines, and monetary values clip. The reference keeps readable columns and scrolls horizontally. | `desktop/query-person`, `desktop/query-advanced`; `DataGrid.tsx` and `query-columns.ts` | Restored 150px defaults; configured/user widths preserved. Desktop dates/currency verified after rebuild. Long heading truncation remains under review. |
| VIS-002 | Medium | Switching an empty query to Advanced adds a long unsaved-view status and links to the toolbar, moving Export and view controls to another row. The filter panel also grows substantially with separate mode and sort controls. | `desktop/query-advanced` | Corrected for the reproduced desktop regression: status shares available toolbar space and compact mode/sort controls restore original panel/table positions; 44px touch targets remain. See latest checkpoint. Whole query-family review remains open. |
| VIS-003 | Medium | A record with process actions now has separate Copy and Delete buttons plus an Actions menu. The reference groups secondary actions in the menu. | `desktop/record-person`, `tablet/record-person`; `RecordActions.tsx` | Corrected and verified in desktop/tablet/phone record captures. Enabled menu actions suppress duplicate direct buttons, including nested menus. NAV-055 passes all five profiles. |
| VIS-004 | Medium | Record tabs crowd the identity header and field groups have more vertical spacing. The earlier header has a clear gap above tabs. | `desktop/record-person`, `desktop/record-small`; `RecordViewHeader.tsx` display-contents wrapper and field layout | Normal wrapper and 16px field gap restored; desktop/tablet/phone record captures reviewed. |
| VIS-005 | Medium | Widget header icons changed from colored outline glyphs to filled square tiles; the sample pie chart changed from a doughnut to a solid pie. | `desktop/widgets-sample`; `WidgetIconTile` and `QqqChartWidget` | Header glyph colors and doughnut restored; image paths, main-icon tiles, explicit colors, slice links, tooltips and legend toggles retained. Fresh desktop/phone captures reviewed; 30/30 affected five-profile widget cases pass. |
| VIS-006 | High | Phone query pages always expose mode, sort, and quick-filter controls beneath the toolbar, pushing the first Person card from approximately y=474 to y=708. Opening the Filter sheet also adds unsaved-view links behind it. | `phone/query-person`, `phone/query-small`, `phone/query-filters`; `RecordQuery.tsx` unconditional phone controls | Corrected: shared controls now live in the phone Filter sheet; opening preserves mode. Fresh phone captures confirm first card returns to approximately y=474. Affected filtering/focus/record acceptance passes 83/83 across five profiles. |
| VIS-007 | Medium | Phone app-page titles are reduced to a few characters after adding a home breadcrumb and a separate command-palette control. The earlier header preserves substantially more of the page name. | `phone/app-home`, `phone/widgets-sample`, `phone/not-found`; `Header.tsx`, `Breadcrumbs.tsx` | Corrected: compact mobile gaps, current crumb constrained to available width, and scrolling after metadata arrives or the trail resizes. Fresh phone app/widget captures reviewed; all header controls retained, navigation/focus/touch checks pass in five profiles. |
| VIS-008 | Medium | Process widget buttons changed from compact primary buttons to full-width slate bars, with extra margins pushing the footer below the first phone viewport. | `process-widgets` on all sizes; process migration to shared `QqqComposite` inherited `ButtonBlock` defaults | Partially corrected: hosted process buttons use compact primary styling and disable during work; metadata colors/formats and shared action handling preserved. Desktop/phone captures reviewed; five-profile PRC-035–037 pass. Process text/label/header spacing still exceeds the earlier Next layout and remains under review. |
Existing defects also found in the reference: Views menus run off the right edge on phone/tablet; the phone Export menu opens partly off the left edge; records with many tabs have overlapping tablet labels; sample Big Number context overflows on phone. These are production-quality issues even though they predate the parity changes. They require focused interaction checks and fixes, not preservation as design intent.
Views and Export overflow is now corrected using the existing Radix dropdown pattern: collision-aware placement, available-height scrolling, arrow-key selection and Escape focus return. Existing item actions, native disabled buttons and permissions are preserved. Fresh desktop/phone captures reviewed; 45/45 saved-view/export real-server cases pass across all five profiles, including full viewport intersection assertions. Tablet tab labels and Big Number context are corrected in the responsive checkpoint below.
Dashboard, app-home content, wizard entry, basic process components, centered Person create/edit/copy forms, blocks/parent widgets, and developer-page structure retain the earlier overall layout in the captured states. Form headings and currency adornments differ. Reports and record-widget forms gained previously missing filter, pivot, dynamic-form, and variable sections; their additional length is functional work that must survive restoration. Typed widget table cells now render values instead of raw markup/object strings. These observations cover entry states only and do not approve the full workflows.
## Historical release checkpoint — September 28
- Next UI 1.0.0 remains unreleased. PR #14 remains open at `93b7b5a`; full real-backend run `36507768282` is in progress. The local query-menu follow-up described below is verified separately.
- Remote source/typecheck/lint/unit/coverage, mocked E2E, Storybook, bundle, CircleCI and both architecture image builds pass. Full real-backend run `36501934828` completed with failures in every job. Chromium/mobile/tablet reported 143 failures and 1343 passes; Firefox reported 52 failures and 460 passes. This is not a green release gate.
- A missing middleware API dependency in the separate security fixture caused many failures. Both fixture launch paths now share classpath resolution. The local security/no-search/process-field-widget rerun passes **124/124 in Firefox and WebKit**.
- Report preview/editor verification passes **15/15 across all five profiles**. Read-only previews support temporary paging, sorting and resizing without writing to the saved report. Column widths from metadata are retained.
- Current full unit checkpoint: **1765/1765**. Production export/typecheck, license headers and bundle budget pass (1032 KB gzip / 1050 KB). The remaining CI regression set passed 140 cases across all five profiles; its five script author/date selector failures were corrected and rerun successfully (5/5). This does not replace the final full gate.
- Functional ledger remains **734 Done, 15 Done with different UX, 5 Partial, 2 Missing, 38 N/A** (794 rows). Remaining groups include base-path hosting, Google Drive picker, embedded query/report controls, complex block input adornments, unmapped icon names, and script autocomplete.
- Developer, agent and CSS-hook guides are integrated; the developer/agent report-preview descriptions now reflect the paged grid. Final browser workflow and visual approval remain open.
## Release sequence
1. Complete the screen/state inventory and paired desktop, tablet, and phone evidence, plus theme and accessibility states.
2. Review every capture and record regressions with source causes. Correct shared defaults and verify the affected families together.
3. Reconcile the developer/agent guides and functional parity ledger with the chosen Next interactions.
4. Run the final five-profile real-server acceptance, source, accessibility, security, performance, and packaging gates on the resulting source.
5. Merge and publish Next UI 1.0.0 only after the complete release evidence is green. QQQ 4.1 remains unreleased.
September 28 follow-up: phone filter controls and restored widget styling are in the current preview on port 18769. The initial capture remains archived for comparison; manifests contain refreshed captures only for the scenarios explicitly recaptured. Remaining priority regressions are VIS-002 (dirty-view toolbar), VIS-007 (phone header identity), VIS-008 (process widget button defaults), plus responsive defects and detailed visual states.
Later September 28 checkpoint: VIS-007 and process button defaults are corrected in the refreshed current preview. Full unit suite: 174 files / 1763 tests pass. Production webpack export/typecheck, source lint (0 errors, 5 existing warnings), license and bundle budget pass. NAV-026/037/038 and PRC-035/036/037 pass 30/30 across the five profiles. Header metadata-arrival and process disabled-action regressions have red-to-green unit evidence. Process spacing, VIS-002, responsive defects, and detailed states remain open; these focused checks do not replace the final full release gate.
Query-menu checkpoint: full unit suite 174 files / 1765 tests passes. Static export and standalone webpack builds/typecheck, lint (0 errors, 5 existing warnings), license and bundle checks pass. Mocked E2E passes 102 tests with two existing skips; its advanced-filter helper now explicitly selects Advanced after opening Filter, matching the restored mode-preserving behavior. This corrects the 13 stale-helper failures in CI run 36501112971 at pushed head `75044c1`. Real-backend CI run 36501113002 on that head was still running at this checkpoint. Final all-surface visual review and release verification remain incomplete.
Mocked-suite follow-up: the two shortcut-suppression checks were skipping when the lazy-loaded query input had not appeared yet. They now wait for the input, verify the typed character, and assert the global dialog stays closed. Full mocked E2E passes **104/104, no skips** (`query-menus-mocked-e2e-final.log`). Menu checkpoint `5cb2f5a` is pushed; real-backend run 36501934828 and CI run 36501934819 are in progress. The previous real-backend run was cancelled by the new push, so it is not completion evidence. The test-only follow-up stays local until that run finishes or another necessary source checkpoint is ready.
Spacing review checkpoint: redundant desktop filter heading and nested padding removed; mode/sort controls share the header, while the phone sheet retains its accessible title. Process-hosted labels, text and non-card widget headers use compact spacing. Existing explicit metadata styling and coarse-pointer targets remain. Preview refreshed on port18769; screenshot evidence and `spacing-*.log` files are in the root evidence directory. The populated query still has both a preview and editable conditions; further hierarchy/mode-state review remains open. Tablet tab labels and phone Big Number context defects were reproduced with DOM measurements, and browser-only CSS trials confirmed their causes. Source corrections and regression assertions are the next checkpoint.
Responsive checkpoint: record tabs now use content-based flex sizing, preventing labels from drawing outside their buttons; the tab bar retains horizontal scrolling for larger sets of sections. Composite rows with space-between alignment wrap when needed, and Big Number widths stay within their available container. Explicit style overrides remain effective. `responsive-red.log` proves the new bounds assertions fail for clipped phone number context and tablet tab labels before the correction; `responsive-green.log` passes all18 applicable cases across5profiles (WID023/WID040/REC004). Production export/typecheck, affected ESLint and diffcheck pass. Fresh desktop/tablet/phone record, sample-widget, block and parent captures are stored in the manifests. Desktop sample-widget arrangement is preserved; phone context now wraps within the card. Both image architecture builds are green remotely; full real-backend CI36501934828 remains in progress.
Review inventory now has41scenarios at3sizes; the historical report-filter and quick-filter editors are unavailable and are explicitly recorded as non-equivalent captures. Populated condition and nested-group states were inspected at all3sizes, plus current quick-filter editing. Phone block/parent scroll viewports1 were compared with the reference and retain their content layout. Remaining work still includes all other scroll sequences, additional interaction states, themes/focus, mode/preview hierarchy, long table headings, remaining functional gaps, and final release gates.
## Report preview and CI diagnosis checkpoint — September 28
The report view now shares the editor's paged, sortable, resizable grid. Preview state stays local; saved column widths are preserved. The report tests pass **15/15 across all five profiles**, including database before/after comparison proving that preview interactions do not change the saved report. The latest phone report capture shows contained pagination with an unbroken page indicator. Selected Basic/Advanced controls use solid theme primary/foreground tokens; measured dark-theme contrast is **6.70:1**. These changes do not close the remaining embedded-report control gaps.
The remote full Firefox/WebKit jobs failed. A shared security-fixture classpath correction resolves missing middleware API classes: **124/124** security, no-search, and process-field-widget checks now pass locally across those two browsers. Other failures include stale control-location assertions, fixture catalog changes, and a real column-filter defect that adds a condition while leaving its editor hidden in Basic mode. The latter now opens Advanced directly; the rebuilt export/typecheck passes and affected browser verification is running. General Filter opening still preserves mode. Final whole-suite release verification and the full visual review remain open.
Visual state follow-up: paired form-validation and empty-query captures were added and inspected on desktop, tablet and phone, increasing the inventory to 43 scenarios. Empty-query composition and validation-field layout retain the earlier Next design. Form heading copy differs (Creating New Person versus Create Person), and configured currency adornments remain visible. Phone Field Lab scroll captures 1–6 were compared: field spacing, rich text, password/file controls and later metadata sections retain the original layout. These captures do not approve picker/editor interactions or all form states.
CI diagnosis follow-up: the column Filter action now opens Advanced so its newly added condition is visible; general Filter and keyboard opening preserve mode. Acceptance assertions follow the restored section headings, secondary action grouping, 150px default widths and statistics menus while retaining metadata-width and permission checks. Script revision tests now select required API metadata and assert the API line separately from author/date. The phone menu test uses actual taps: Firefox with touch emulation emitted no pointerdown for a synthetic mouse click, while four diagnostic touch taps opened the menu successfully.
Checkpoint verification: `recovery-five-profile.log` records 140 passes and five failures at the outdated author/date selector; `script-api-author-green.log` passes those five corrected cases. `recovery-full-unit.log` has 1765 passes. Final TypeScript and changed-file lint pass; source lint has zero errors and five existing warnings. License headers, diff check and bundle budget pass. Current preview port 18769 was refreshed with this export and responds 200.
Final old-run diagnosis: the Chromium/mobile/tablet log contains the same corrected failures plus two mobile export assertions receiving an intersection ratio of 0.9999999403953552 instead of exactly 1, and one WebKit internal navigation error. Viewport checks now measure all four edges with a half-CSS-pixel rounding tolerance; the remaining three-profile security/export rerun passed **184/184**, including the previously failed session-switch case. Together with the Firefox/WebKit run, the security classpath fix is verified on all five profiles. Validation error text was measured at 4.58:1 in light mode and 5.01:1 in dark mode on the sample form background.
Dark validation follow-up: phone and tablet comparisons retain the baseline field hierarchy, but both builds had nearly invisible native date-picker icons because no browser color-scheme was selected. The stylesheet now assigns light/dark color-scheme with the existing theme selectors; rendered verification passes in Chromium, Firefox and WebKit for both light and dark, with the OS preference deliberately opposite. A fresh tablet capture confirms the calendar icon is visible. Saved-view/export/metadata-theme checks pass **40/40 across all five profiles** after the viewport assertion correction.
## Query controls and gallery checkpoint — September 28
The light capture inventory now has **51 scenarios at each of three sizes**. New states cover density menu/compact/comfortable, cards, Selection menu, subset dialog, selected rows, and Save View As. Selection/density and both dialogs also have paired dark captures at all three sizes. Inspected evidence includes desktop card layout, phone selected cards, phone/tablet Save View As, and light/dark phone menus. These preserve the reference composition; review of the remaining states continues. Captures alone do not approve the entire query family.
**VIS-009 (medium):** Selection extends beyond the phone viewport; Selection and Density lack arrow-key navigation, and Density ignores Escape. Browser diagnostics reproduce these failures before the change. Both now use the existing Radix dropdown primitive with the original control styling and actions, bounded placement, keyboard selection, dismissal, and focus return. The selected Density label had 2.43:1 dark-mode contrast; it now uses the same primary/foreground pair as Basic/Advanced. The subset dialog restores focus to Selection.
Verification: 1765/1765 unit tests; 30/30 real-backend density/selection/bulk/card cases across Chromium, Firefox, WebKit, phone and tablet; production export/typecheck and targeted lint; license and bundle budget (1032 KB gzip). The first acceptance run passed29 and failed the Firefox dedicated phone case because synthetic mouse input did not open the Radix trigger; the corrected test uses actual taps and asserts menu bounds. Exact diagnostics are query-menu-red.log/query-menu-green.log; final acceptance is query-controls-acceptance-green.log.
The gallery previously missed multi-profile capture manifests. They are now merged into canonical per-profile manifests, future captures update those manifests, and a theme selector exposes light/dark evidence. Browser checks verify both comparison images load at all six theme/size combinations. Save-dialog capture setup now uses the visible Save As action (the separate save-as hook only exists for an existing saved view).
**Still open:** tablet headings truncate more than the baseline because a hidden sort glyph, touch-sized column menu, and cell padding consume label space. Compare tablet query-save-dialog background and desktop query-density-menu. Preserve configured widths and touch targets when correcting this. Full query state review, all other families, remaining functional ledger gaps and final release gates remain open.
Final menu evidence: selected Density contrast is **6.70:1** in dark mode (`density-contrast-green.log`), with the final phone capture inspected. The final export/typecheck, changed-file lint, license and bundle checks pass. A further **10/10** density/selection checks pass across all five profiles on the final build, including explicit focus return after the subset dialog closes (`query-controls-final-acceptance.log`). Current preview was refreshed from this export.
## Query headings, states and WebKit checkpoint — September 28
**VIS-010 (medium), corrected:** hidden unsorted-arrow space plus menu targets and cell padding clipped First Name/Last Name on touch layouts and Annual Salary on desktop. The header now reserves an arrow only for an active sort, reclaims trailing padding next to its menu, and permits two-line touch labels within the existing header height. Default widths remain150px; explicit SIZE widths and resize remain effective; sort/menu targets stay separate and touch menus remain44px. Browser measurements confirm the four tested Person labels fit without increasing the header height (37.5px desktop,60.5px touch). The new assertion failed on all3 initial profiles before the change; **45/45** affected checks pass on all5 profiles afterward, including sorted headings, sorting, resize, column menus, statistics and explicit metadata widths.
Added paired query statistics, loading and backend-error captures at desktop/tablet/phone. Loading deliberately holds the query response; it is a layout check, not evidence of backend latency. Desktop statistics retains the original arrangement with an added Export button. Phone statistics has a more crowded title beside the added action and remains a visual follow-up. Error-state composition is retained; the corrected dirty-view toolbar grouping remains contained. Column menus and relative-date controls did not exist in the original reference, so those baseline captures are marked non-equivalent. Inventory reaches56 light scenarios with the relative-date menu follow-up.
**VIS-011 (high), corrected:** full CI and local WebKit reproduced the relative-date menu extending above the viewport, leaving7daysago unreachable. The menu now caps its height by both70dvh and Radix's available placement height, with a viewport width bound. Acceptance asserts the menu's actual bounds before choosing a date. **25/25** date, phone saved-view/export, restriction and report-sharing cases pass across all5profiles after the correction.
Full remote WebKit job109212960890 finished **509passed/4failed** at93b7b5a. Besides the reproduced relative-date issue, failures were a saved-view touch trigger that stayed closed and two document-navigation diagnostic cases (SEC010 andRPT017). Traces place those API errors39–54ms after a full navigation began while initial reads were still starting; those two tests now let initial reads settle before deliberate document replacement, with API/security assertions unchanged. Saved-view opening now has an immediate expanded-state assertion, but its intermittent CI failure is **not declared resolved** by the focused pass. Other full-CI jobs remain in progress at last check. Artifacts/logs retained under ci-webkit-36507768282 and ci-36507768282-webkit.log.
Verification: query-heading-red.log then query-heading-green.log45passes; query-heading-measurements.log; query-heading-date-unit.log1765passes; relative-date-and-ci-green.log25passes. Production export/typecheck, changed-file lint, license, bundle anddiff checks pass. Current preview contains the heading/date fixes. Final full acceptance and the complete visual review remain open.
## September 29 — report controls and statistics checkpoint
The current preview has been refreshed from the production export including report Basic/Advanced modes, saved-view selection, refresh, density, column configuration and column actions. Existing editor tabs and Cancel → OK → host Save remain. Read-only preview interactions stay local. These additions use the existing Next controls. The original report filter editor was absent, so it has no equivalent baseline state. Light inventory is now 60 scenarios at each of three sizes; captures are not full-family approval.
Paired statistics screenshots revealed that adding Export squeezed the phone title into three lines. A wrapping header now gives the title its own phone row while keeping desktop actions alongside it. The rendered check changed from 84px (three lines) to 28px (one line) at 393px width. Fresh original/current captures exist at all three sizes. The inspected dark report states include desktop Advanced, tablet Basic, and phone Columns/Density/column menu: popups fit the viewport and the footer remains visible; phone preview pagination requires scrolling the modal body. Dark blue links remain a contrast investigation. No whole visual family is signed off.
Evidence: report-controls-corrected-acceptance.log has 90/90 cases across five profiles, including draft save/cancel, saved-view import, API-versioned refresh, paging/sort/resize/pins, copy full query, nested statistics, headings and previously intermittent menu/schedule cases. The earlier expanded run used a Person fixture without QUERY_STATS and failed those five statistics cases; corrected qryItem fixtures passed in the clean run. Source build/type checking passed. Latest remote full acceptance at6439944 remains failed; local focused success does not replace that release gate. Touch-menu tests now attach native input/state diagnostics on failure, and schedule tests assert CSV selection before submission.
## September 29 — dark-mode and form-title recovery
The report link contrast defect reproduced at 2.58:1 in a browser accessibility scan. Separating primary text from branded fills raised the measured sample link contrast to 8.83:1. Broader tests with OS preference opposite the selected UI mode exposed three more defects: unbranded dark primary buttons at 3.23:1, a record badge following OS dark mode on a light page, and dark table boolean values using a dark green. Tailwind dark variants now follow ThemeProvider's class; unbranded dark fills use the readable default blue, while true boolean values use a brighter green on dark surfaces. Light styling and explicit application theme rules remain. 53 checks across five profiles passed, covering both UI modes, default/brand themes, the six main accessibility routes, grids, record layouts and widgets.
Compared dark phone record/form entry states, all seven Field Lab phone captures, tablet process widgets, and desktop widget overview against the original Next build. Form geometry remains aligned; native date/time icons and link contrast are corrected. The Create heading still said Creating New: restored the original Create wording and retained CSS hooks, heading overrides and accessible Edit/Copy record labels. The parity row now accurately records this intentional Next presentation (733Done,16DifferentUX,5Partial,2Missing,38NA).
Evidence correction: widget long-page captures revealed duplicate near-top images because document smooth scrolling had not completed. The capture script now targets the actual main content scroller, uses instant scrolling and verifies actual versus requested position before each screenshot, recording positions in manifests. Scrolling the document could move the application out of view and produce blank evidence. Earlier numbered scroll captures without verified positions must not be counted as complete scroll review; affected long-page evidence is being regenerated. No whole visual family is signed off.
## September 29 — chart restoration verified
Long-page comparison confirmed two presentation changes: stacked charts moved their value axis to the right, and small line charts gained an axis and grid. The original left axis and compact small-line presentation are restored. Integer ticks, dataset tooltips, metadata colors, legend toggles, navigation and accessible data remain. WID-069 and WID-010 pass 25/25 across Chromium, Firefox, WebKit, phone and tablet; visual-recovery unit tests pass 1768/1768, production export/typecheck succeeds and lint reports zero errors/five existing warnings. The ledger now records these intentional Next differences (731 Done, 18 Different UX, 5 Partial, 2 Missing, 38 N/A).
The current preview was refreshed from that export. Dark form-field and sample-widget captures were regenerated against the original at three sizes with verified main scroll positions. Phone lower-page comparisons confirm the compact line presentation, HTML/Stepper layout and chart card structure; current legend controls and the As of date add content/height. Remaining label-rotation differences need review. No whole family is signed off.
Remote c442613 WebKit completed with 513 passes and three failures: Annual Salary header clipping, Selection touch opening and Views touch opening. Its native input diagnostics are being retrieved. General CI and image builds pass; the other full acceptance jobs are still running. Focused local passes do not close these remote failures.
## September 29 — CI menu and header diagnosis
The completed c442613 runs report Firefox 515 passes/one heading failure and WebKit 513 passes/three failures (heading plus Selection/Views). The Chromium/phone/tablet job remains in progress. Native WebKit input attachments prove the two menus open on pointer-down and close after the same tap's compatibility click. A local probe reproduces dismissal when focus returns to the trigger; the regression unit test failed before the correction. Selection and Views now exclude their own trigger from outside-interaction dismissal, while outside clicks, Escape, selection and focus return remain supported. The regression unit passes and 30 real-backend menu/basic/saved-view checks pass across five profiles, including explicit trigger-focus retention. The full remote suite must still verify this correction.
The heading failure is font-dependent: the application renders with its system/configured font, and the original two-pixel spacing adjustment did not fit the wider Linux font. A local Verdana probe reproduced 13px of clipping at the preserved150px width; allowing two lines removed clipping. Header labels now use the existing two-line limit on desktop as well as touch. The wider-font browser regression failed before the change;20/20 heading/sorting/shared-view checks now pass across five profiles. Affected grid/menu unit tests17/17, production export/typecheck, changed-file lint and diff checks pass; the full unit suite immediately before the CSS-only header correction passed1769/1769. Bundle budget passes at1031.5KB gzip of1050KB. Logs: menu-trigger-red/green/unit/acceptance, header-font-probe/red/acceptance/build/unit/lint, visual-recovery-final-bundle.
Current preview includes theme, Create-title, chart, menu and header corrections. Dark tablet Field Lab entry and phone record-person lower viewport were compared with the original and retain field/section geometry; date icons and primary-link contrast are corrected. Desktop/tablet sample-widget lower viewports retain card layout and the compact small chart; legend controls and conditional category rotation still account for visible differences. Complete chart-label review and all other open family/state reviews remain required.
## September 29 — chart labels and single-list filter editing
**VIS-012, corrected:** the chart rotation heuristic treated every character as 6.5px wide and divided plot width by label count, turning month/fruit labels that fit horizontally in the original Next charts. SVG measurements show neighboring labels fit at desktop, tablet and phone widths. The category axis now measures actual glyph widths and tick positions, remeasuring on chart updates and font loading; long labels retain rotation and bounded axis space. The new WID-009 browser assertion failed before the correction; 40/40 chart data/legend/tooltip/rotation cases pass across all five profiles. Live measurements in chart-label-red.log/chart-label-green.log demonstrate horizontal labels with positive gaps afterward.
**VIS-013, corrected:** Advanced conditions appeared twice (preview chips plus editable rows), with two Clear actions opening the same dialog. The earlier Next single editable list is restored. Each row still deletes its own condition; one footer Clear action still confirms and retains sorting. Basic mode retains its quick filters and header Clear action. The now-unused duplicate component/test were removed; the replacement unit checks the single-list layout, and QRY-083 now compares SQL results for two conditions before deleting one and proving the second remains active. This is recorded as Different UX in the parity ledger (730 Done, 19 Different UX, 5 Partial, 2 Missing, 38 N/A). Required acceptance coverage remains enabled.
The new layout unit failed before the correction. Full unit verification passes 1768/1768 in 173 files (one duplicate-component test removed); source lint has zero errors/five existing warnings and license headers pass. The first build caught a Testing Library assertion using Playwright's exact option; that test-only typing error was corrected and the final production export/typecheck passes. Filter acceptance passes 55/55 across five profiles, covering mode switching, quick filters, saved views, per-condition deletion, confirmed clear, sort retention, field validation, dates, boolean/blob and long-text operators.
Fresh original/current captures cover populated/nested filters and sample widgets in both themes at all three sizes. Inspected light desktop/phone populated filters, tablet nested filters, dark phone populated filters, and restored desktop/phone chart labels. Single-list geometry is recovered; Basic/Advanced and sort controls remain necessary additions. Current quick-filter editing is captured; its baseline explicitly states that the control did not exist. All captured long-page positions are verified. No complete family is signed off yet.
Full c442613 acceptance 36549979322 is now terminal/failing: Chromium 515/1, phone 490/1, tablet 486/5, Firefox 515/1, WebKit 513/3. Besides heading/menu failures corrected in 5077c78, phone/tablet RPT-009 reproduced an undersized standalone report link (146x20); it now has a 44px target. The two tablet navigation traces show deliberate document replacements overlapping sidebar prefetch requests; those tests now wait for initial reads to finish. No diagnostics or assertions were waived, and their intermittent CI outcome still requires the next full run. Exact artifacts and logs remain in the root visual-review evidence directory.
Final follow-up verification: nested-and-ci-acceptance.log reproduced the report touch failure on phone and tablet (26 passes/two failures). After correction, nested-and-ci-green.log passes 28/28 across five profiles, including nested AND/OR filtering, lookups, owned/shared records and report touch targets. Affected report/filter units pass 21/21. Final production export/typecheck, changed-file lint, diff check and bundle budget pass (1031.3 KB gzip / 1050 KB). The completed old CI run remains failed; these local passes do not substitute for the next full run.
## September 29 — expanded process review and saved-profile restoration
Added paired light/dark captures at desktop, tablet and phone for expanded help, required-field errors, component review/download, wizard details/validation/confirmation, cancel dialog, running progress, user-facing error, scanner, state-machine entry, widget result, bulk file mapping, value mapping, review and completed validation. All 192 new combinations have clean setup/runtime capture status and no document-wide horizontal overflow. Scroll captures record and verify main-content positions. The first required-field selectors were too strict about labels or matched a containing section; the final captures use the actual field roles. Completed bulk validation is identified by its result text, since the original did not expose the newer status hook.
Inspected pairs include desktop component review and both bulk-mapping viewports; phone required-field error and footer, wizard confirmation, cancellation, progress, scanner, error, loop and widget result; dark desktop bulk mapping, dark tablet value mapping and wizard validation, and dark phone bulk-mapping footer and cancellation. These process compositions retain the original Next arrangement. Added controls and readable dark primary text remain. More process variants, saved-profile menu/dialog states, internal-error detail, bulk selection/results and composite modal states still need visual review; this is not full process-family approval.
**VIS-014 (medium), corrected:** the original bordered Saved Bulk Load Profiles section, heading and visible current-profile status were removed during the profile-action expansion. Restored that section in the shared component. Restored the original `showCurrent` option so file/value mapping identifies the applied profile while the final review summary avoids duplicate status. Current menu, owner permissions, reset/rename/save/delete behavior, help, typed value mapping and searchable fields remain. The extra actions can increase phone height; verified scrolling reaches the footer. Unit assertions first failed for the missing named region and for missing value-step status, then passed after correction.
**VIS-008 clarification:** browser measurements show Reload occupies the same header row as the title. It increases a compact widget header to 44px on touch devices (24px on desktop); this is the touch-target requirement, not an accidental separate row. The hosted ad hoc body is 2.5px shorter than the original in the fixture. Preserve the usable controls; further composite-specific states remain open. Evidence: `process-layout-probe.mjs` and `process-layout-before.log`.
Verification: `bulk-profile-panel-acceptance.log` passes45/45 across five profiles; after the final value-step correction, `bulk-profile-final-acceptance.log` passes25/25 including upload/value mapping/review and profile actions. Final full units1768/1768 in173files (`bulk-profile-final-unit.log`), affected units10/10, export/typecheck, changed-file lint, license headers, diff check and production bundle budget pass (1031.4KB gzip/1050KB). Capture logs: `process-states-light`, `process-states-followup`, `process-states-dark-baseline`, `process-states-dark-current`, `process-bulk-restored-light`, and `process-values-final-{light,dark}`. Canonical manifests supersede failed interim captures and orphaned images.
## September 29 — profile menus, shell overlays and Preferences
**VIS-015, corrected:** the expanded profile menu lacked arrow-key navigation and overflowed the tablet viewport with eight profiles (bottom1105px in a1080px viewport). It now uses the existing Radix dropdown primitive with collision placement and available-height scrolling. Existing menu contents, ownership restrictions, confirmations, status and backend behavior remain. Dialog focus returns to the actual opener, including WebKit clicks that activate without focusing; menu actions return to the persistent menu trigger. Native probes verify desktop/tablet/phone bounds and focus. Export now guards focus returning to its own trigger, fixing the native WebKit tap/focus dismissal reproduced by CI. Outside dismissal and download assertions remain.
The initial profile/browser follow-up produced27passes/3focus failures; actual-opener handling corrected those. A later34pass/1fail run exposed a separate Home/Enter test race. Trace shows Home at77867.082ms and Enter at77869.872ms; the resulting page had selected Review Profile8. Installed Radix roving-focus source defers focus with setTimeout. Waiting for the expected first menu item preserves and strengthens the keyboard check. `menu-home-focus-acceptance.log` passes35/35 across all profiles.
Added seven shell states in both themes at three sizes:84 clean captures. Inspected desktop Preferences/search results/command, phone navigation/user menu/Preferences/help/search results/command, tablet help, dark tablet Preferences and dark phone user menu/search results/command. Original navigation, help, user-menu and search geometry is retained; the command palette includes new table actions within its original shell. Preferences' default selection changes from25 to50 records through current metadata/default behavior. These states do not approve notifications, login/session, all branding variants, internal dialog scroll positions or the whole shell family.
**VIS-016, corrected:** Preferences used z-50 behind the phone sidebar's z-100; its controls were visually covered. It now uses `--qqq-z-overlay` and `--qqq-z-modal`. Closing returns focus to the user-menu button because the Preferences menu item unmounts on selection. The new phone hit-test assertion failed before correction; a unit focus-return assertion also failed. Both pass after correction, and fresh light/dark phone screenshots show the full dialog above the drawer without changing its internal layout.
Final verification: `preferences-menu-final-acceptance.log` passes50/50 across Chromium, Firefox, WebKit, mobile and tablet, including drawer navigation/focus, profile actions and Export. `preferences-final-unit.log` passes1771/1771 in173files. Final production export/typecheck, changed-file lint, license headers, diff check and bundle1031.7KB/1050KB pass. A test-only Playwright `exact` option was removed from Testing Library before the final build. Capture logs are `shell-states-{light,dark}` and `preferences-restored-{light,dark}`. No whole family is approved and the full remote gate remains incomplete.
## September 29 — inline profile naming recovery
**VIS-017, corrected:** Save New, Save As and Rename had replaced the earlier inline Profile Name field with a centered modal. Restored the editor inside the named, bordered profile section using the original compact input and bordered Save Profile/Cancel buttons. No nested form is introduced: Enter saves the profile and stops propagation so the enclosing bulk-load process is not submitted. Escape/Cancel close the editor; saving restores focus to the actual opener or the persistent menu trigger if the opener disappears when the current profile changes. Ownership checks, backend name limits/errors, copy semantics, update confirmation and explicit delete behavior remain.
The inline-region and keyboard regression assertions first failed2/11, then passed11/11. Full units pass1771/1771. The final success-focus assertion also passes in the affected11tests. `profile-inline-acceptance.log` passes40/40 across all5profiles (PRC031/046/058/059/060), covering required/file/value mapping, profile CRUD, duplicate-name server validation, shared ownership, menu bounds and focus. PRC046 retained stale local-panel locators after the menu moved to a portal; those now find the portaled menu while all SQL and behavioral assertions remain. Production export, final typecheck/lint, license and bundle1031.9KB/1050KB pass. The first budget-report write was denied by the sandbox; the authorized rerun completed successfully.
Fresh `profile-inline-{light,dark}` captures replace the modal snapshots at desktop/tablet/phone. All12entries have clean setup/runtime status and no body horizontal overflow. Inspected light desktop, light/dark phone and dark tablet pairs: editor input/buttons match the original arrangement within the restored section. Expanded profile actions/reset controls still add height and remain part of the broader review. No full family is approved. The ledger explicitly records inline naming as Different UX; its existing-row recount corrects795total/51supplemental rows and the stale OS-driven dark-mode description.
## September 29 — Copy alignment, selected sections and touch selection
The bounded record/form review inspected 210 existing images against original Next commit `e42ad2b2`. It found two concrete regressions: **RF-001**, Copy scope controls aligned to a wider container than the editable form; **RF-002**, a selected record section inherited Overview's half-width card and full-width field defaults. The Copy container now shares `max-w-4xl` with EntityForm. Selected tabs use a full-width fallback card and two field columns from `sm`; explicit metadata spans retain precedence. Overview and phone accordion defaults are preserved.
Both browser layout regressions failed on the old export, then passed 10/10 across Chromium, Firefox, WebKit, phone and tablet. Existing record view and create/edit/copy workflows passed 17/17 in Chromium. Full units 1,840/1,840, production export, types, changed-source lint, license headers and bundle budget 1,038 KB/1,050 KB pass. Independent code review found no additional issues.
**Touch selection:** the 44px selection label exceeded the 44px pinned selection cell because density padding was still applied. Removing horizontal selection-cell padding only for coarse pointers exposes both target edges. Browser geometry reproduced the overlap before the correction; 35 focused checks pass across all 5 profiles, covering both edges, every density and existing selection workflows. The first post-fix test attempt used coordinates from before the selection banner moved the grid; the corrected test remeasures after selection and uses native touch events for touch menus. Failed traces remain preserved.
Refreshed current captures for Copy, selected section, record lab, audit and populated query in both themes at all 3 sizes: 30 states/40 images, no capture/runtime errors or body overflow. This also replaces the stale light record lab/audit evidence. Root inspection confirms desktop light Copy/selectedsection and tablet touch query. All 34 refreshed record/form images have now been directly reviewed, closing RF-001, RF-002 and the stale-capture gap. The exact-path ledger is `record-form-review-d937507.md` in the ignored evidence directory. The fixed 210-image record/form inventory is closed; other visual families are still open. Logs/evidence: `record-default-layout-{red,green,build,units}.log`, `record-layout-workflows.log`, `query-selection-targets-final.log`, `layout-restored-{light,dark}.log`, and release-worktree `test-results/acceptance/{selection-target-evidence,record-default-layout-evidence}`.
**Publication hold:** the owner is testing immutable RC1 in real applications for a couple of weeks. These corrections are post-RC1 branch changes. Final publication requires renewed explicit owner approval; see [testing period](../releases/1.0-testing.md).
## September 29 — query and shell fixed inventories reviewed
The query inventory is fully inspected: **348 images**, covering all 28 existing states in light/dark at desktop/tablet/phone sizes, including scroll positions. The reviewer directly inspected 314 images and retained 34 explicitly attributed root inspections. No new unresolved visual regression was found. Five original controls are unavailable for equivalent comparison; the ledger distinguishes those context images. Older selection-padding appearances are historical; refreshed populated-query images and the separate 35-case interaction gate cover the correction.
The seven-state shell inventory is fully inspected: **92 images**, comprising 84 initial-position images and eight Preferences scroll captures. Root directly inspected 64 images and retained 28 prior inspections. No new unresolved shell visual regression was found. All six current Preferences scroll captures expose the lower table/record layout choices, with the footer separate. Original search/help/navigation geometry is retained; added theme choices and command actions remain inside the existing layouts. Shell source has not changed between d937507 and e87fe90. Screenshots do not establish persistence for every preference.
Exact-path ledgers are `query-review-e87fe90.md` and `shell-review-e87fe90.md` in the ignored `test-results/visual-review` evidence directory. This closes those fixed inventories, alongside the previously closed record/form inventory. Process, report and widget review remains open; notifications, login/session and branding variants are not approved by this shell review. RC2 is in preparation and final 1.0 remains held for owner testing.
## September 29 — report fixed inventory inspected
All **262 report images** in the six per-profile canonical manifests were directly inspected: 131 in each theme (desktop 40, tablet 41, phone 50). This corrects the older combined light manifest, which omitted later states. The exact-path ledger `report-review-defb3ab.md` has no missing or extra images and records no confirmed new visual regression. An initial concern about blank output-format text was withdrawn after direct focused reinspection.
This is inspection of the existing images, not a claim that all captures represent defb3ab: only 10 of 216 state entries have timestamps/export fingerprints. Fifty-four original editor-state entries lack an equivalent control, and the initial phone Advanced/Columns captures do not show the lower pager. Those comparison and provenance limits remain explicit. Embedded query equivalence and whole-product visual review remain open; final 1.0 is held.
## September 29 — remaining fixed inventories inspected; child export corrected
All **1,621 unique images** in the six canonical per-profile manifests are now accounted for by completed inspection ledgers: record/forms 210, query 348, shell 92, process 402, report 262, widgets/dashboard 269 and developer/miscellaneous 38. Process review found no additional confirmed regression; explicit full-width upload metadata explains the wide upload control. Developer/miscellaneous content layout retains the original composition, with historical header truncation already addressed by VIS-007. Widget/dashboard review found the child export issue below and no other confirmed unresolved regression. Root independently reconciled the final 269 and 38 paths against the manifests and verified every file exists. Exact ledgers are `process-review-e87fe90.md`, `widgets-dashboard-review-2221999.md` and `developer-misc-review-2221999.md` in ignored evidence.
**Child-record export, corrected:** current child lists had a generic header icon unable to read the child payload plus a working Export control in a new toolbar row. Two component regressions and the real-backend fixture reproduced the duplicate. The first correction removed the generic icon but retained the extra row; fresh screenshots showed that still differed from original Next. The final correction reuses the child export control in the existing header position. Bare hosts retain inline export. Permission-filtered join columns, shown-row CSV, empty disabled state and the partial-result tooltip remain. Widget export controls follow the same loading/error/permission/required-selection gates as the body. The existing error boundary now covers header and body; Retry requests fresh data and a replacement payload clears the error. Review regressions reproduced and verified denied exports, malformed metadata containment and successful recovery.
Verification: **1,857 unit tests**, 41 focused widget tests, 10 real-backend child-list scenarios across five profiles, production export/types/lint/license/diff checks and 1038.4KB/1050KB bundle budget pass. Final browser checks verify one header export aligned with Reload, CSV contents against independent SQL, exclusion of the undisplayed third row, record navigation and empty lists. Desktop and phone captures were directly inspected: the extra toolbar row is gone. The first geometry check compared title and button centers and failed on touch because native targets are 44px and the header uses items-start; the final assertion requires Export inside the header and aligned with Reload. Failed evidence is retained; no CSS or target size was changed to pass it. Independent review has no remaining blocker. Logs use `child-export-*`; final evidence is `child-export-final-evidence` in the ignored root directory.
Inventory completion is **not** whole-product visual approval. Fifty-two gallery/table images repeat near-top content and cannot establish lower-page coverage. Six original choice-open states are unsupported. Most historical images lack build fingerprints; dark widget coverage only includes sample/typed-input/choice states, and developer/miscellaneous has no dark captures. Previously recorded uncaptured shell variants, report pager and provenance limitations remain. The original Next reference remains `e42ad2b2`; Material supplies behavior only. The owner preview remains unchanged while the owner validates it. Final 1.0 is explicitly held.
## September 30: unpublished legacy icon compatibility follow-up
Existing Next Lucide mappings, widget overrides and image priority are preserved. Additional legacy names use the pinned local Material Icons inventory. Six formerly unmapped block icons, navigation and the raised main tile were checked in light/dark desktop/mobile views. Four workflows passed against the actual development JAR; its font and exported files were verified byte-for-byte. Saved desktop/mobile tile regions contain identical light/dark glyph pixels; the earlier suspected blank tile was an inspection error, and no repaint workaround was introduced.
The full local Chromium suite passed 589 tests and all 391 required rows, with zero failures, skips, flaky outcomes or report errors and three approved external-service exclusions. Independent code review approved the change. Evidence is retained under `test-results/visual-review/icon-compatibility-investigation/` in the original checkout. This checkpoint covers the icon change; whole-product visual approval remains open. RC5 is immutable and does not include this follow-up.
## September 30: fresh RC6 dark desktop developer and miscellaneous review
Twelve original/current captures across app home, table developer, record developer and not-found pages were directly reviewed, including lower scroll positions. The fresh manifest records capture times and export fingerprints. No new layout regression was found in those states. Evidence: `test-results/visual-review/rc6-dark-developer-review/review.md` in the original checkout.
An inherited RapiDoc dark-link contrast defect is confirmed at 2.12:1 in both original and RC6; subsequent browser checks also find light-mode contact/authentication text below 4.5:1. A separate correction is being tested. This closes the missing desktop dark capture evidence for the four entry states; phone/tablet dark coverage, expanded endpoint interactions and whole-product approval remain open.
## September 30 — phone developer view comparison
Eight fresh original/RC6 phone screenshots in light/dark were directly inspected (initial and lower scroll positions). Four capture entries had no runtime/state errors or body horizontal overflow. Evidence: `test-results/visual-review/rc6-phone-developer-review/` in the original checkout, including export fingerprints and provenance.
The main composition matches the original. RapiDoc's inner clipping is inherited: its 360px minimum exceeds the 343px phone panel, and hidden overflow clips controls. This remains open for a separate responsive correction. The added command button also reduces breadcrumb space; the trail scrolls to the current page, but header crowding remains a visual-review consideration. This is scoped evidence, not full visual approval.
## September 30 — API playground contrast follow-up
Original Next and RC6 both have an inherited dark playground link contrast defect (2.12:1). Focused browser checks also reproduced light contact/status contrast defects. An isolated follow-up applies existing theme tokens without changing layout. Four final desktop/mobile light/dark cases pass, including hovered controls and Preferences changes, with no failures, skips, flaky outcomes or report errors. All four final screenshots were directly inspected after transitions settled. Evidence is retained under `test-results/visual-review/api-playground-contrast/` in the original checkout.
This is scoped color verification, not whole-product visual approval. Narrow-screen inner clipping, expanded endpoint states and additional application palettes remain open. RC6 is unchanged.
## September 30 — API specification controls in narrow panels
Reproduced clipped specification buttons against the packaged RC7 candidate before any focus/click could conceal overflow by scrolling hidden ancestors. The follow-up lets RapiDoc fit its panel and uses a named overview container plus public CSS parts to wrap only the specification actions at narrow widths. It adds no dependency and does not mutate the shadow DOM.
Twelve real-backend desktop/mobile workflows pass: default device and 320px widths, light/dark themes, complete visibility, actual specification download, keyboard new-tab action, contrast and live Preferences changes. Final phone screenshots were directly inspected. At 1440px, the full playground screenshot is byte-identical to RC7 (SHA256 `ec6d108d5d632d1ee0b3a3ecd6c61ae9279bee705facd2a3b9e422d2e0f1ae37`), and overview/button geometry matches exactly.
All 1,904 units, types, lint/license, default production export and unchanged bundle budgets pass. Evidence, including earlier test/setup failures, is retained under original-checkout `test-results/visual-review/api-playground-width/`. This is a separate follow-up, not part of frozen RC7. Expanded endpoint states, additional application palettes and whole-product visual approval remain open.
## September 30 — desktop and tablet process comparison
Compared original Next `e42ad2b2` with the actual RC8 candidate JAR in 32 light/dark desktop/tablet screenshots: bulk file mapping at two scroll positions, running process progress, and expanded internal-error details. Both builds used fresh isolated instances of the same fixture/backend. Capture manifests include export fingerprints and report zero runtime/state errors or body/main horizontal overflow.
Progress and error panel composition are retained. Bulk mapping visibly changes the saved-profile actions and Add Fields control and introduces explanatory text. Main scroll height increases from 1330 to 1390px on desktop and 1556 to 1748px on tablet. This added density/height remains an owner-review item. Tablet breadcrumb ancestors are more crowded with the added command control; the current page remains visible. Dark mode retains the existing composition with revised readable accent colors. No screen redesign was applied during this review.
Evidence: original-checkout `test-results/visual-review/rc8-desktop-tablet-process-review/`, including four inspected contact sheets and all 32 source images. This is scoped process evidence, not whole-product approval; the existing uncaptured states and compatibility gaps remain open.
## RC8 lower widget review: desktop, tablet and phone
Original Next `e42ad2b2` and corrected RC8 `cd3d473` were compared on Widget Gallery and Widget Tables And Charts using the actual candidate JAR, matching fixtures and backend `e0d57dd31`. Evidence: `rc8-widget-lower-review/` in the visual-review archive. Desktop supplied 28 images; tablet/phone supplied 86 more, for 114 images across 24 entries in both themes. Twelve contact sheets were directly inspected. Every lower capture records and verifies its actual scroll position. All entries had zero runtime/state errors and no body/main horizontal overflow. This closes the earlier repeated-near-top capture gap for these two entry states across all three viewport sizes.
The card structure is retained. Recorded changes include map geography, formatted table cells, per-table export/footers, paging controls, metadata chart colors, readable long category labels, legends, connected line data and As Of timestamps. These additions increase page height, including an extra phone viewport on Tables And Charts. Long phone header text now truncates alongside the added command control. Dark map/plot contrast remains a review concern; this comparison did not measure contrast ratios or verify horizontally scrolled table columns. Expanded interaction states, other widget surfaces and owner visual approval remain open. No product code changed during this review.
+88
View File
@@ -0,0 +1,88 @@
# Performance budgets (QRun-IO/qqq#710)
Two automated checks. A breach fails CI. To fix one, make the change smaller or faster. If a
budget really has to rise, put the new measurement and the reason in the commit.
| Check | What it measures | Budget file | Runs in |
|---|---|---|---|
| Bundle budget | First-load JS per route of the static export, total JS, largest chunk, CSS | `perf-budget.json` | `ci.yml` job "Bundle budget (static export)": `pnpm build:export && pnpm perf:budget` |
| Runtime budgets | Large table, heavy dashboard, long process in the production export against the real sample backend | `tests/acceptance/specs/performance/budgets.ts` | `acceptance.yml`: matrix rows PRF-001 to PRF-005 in every browser |
```bash
pnpm build:export && pnpm perf:budget # bundle; report in test-results/performance/bundle.json
QQQ_ACCEPTANCE_BROWSERS=chromium,firefox,webkit node scripts/acceptance.mjs --skip-build specs/performance
```
## Bundle
First-load JS for a route is every script and stylesheet that the route's exported HTML loads
before hydration, gzip level 9. Next 16 no longer prints these sizes, so the script measures
them from `out/`. `_` stands for a dynamic segment: `/app/_` is `/app/[slug]` (app home, record
query, process, report) and `/app/_/_` is the record view.
| Route | Before (KB gzip) | 1.0 (KB gzip) | Budget |
|---|---|---|---|
| `/`, `/login`, `/callback`, `/token`, `/404` | 218–219 | 218–219 | 230 |
| `/app`, `/app/developer`, `/app/search`, `/app/_/key`, `/app/_/dev`, `/app/_/_/dev` | 309–311 | 309–311 | 330 |
| `/app/_` (app home, record query, process, report) | **562.9** | **448.2** | 470 |
| `/app/_/_` (record view) | 431.6 | 431.5 | 455 |
| `/app/_/create`, `/app/_/_/edit`, `/app/_/_/copy` | 351–356 | 351–356 | 370–375 |
| `/app/_/savedView/_` | 371.2 | 371.1 | 390 |
| Total JS (all chunks) | 689.2 in 54 chunks | 675.0 in 51 chunks | 710 |
| Largest chunk (Recharts + d3, loaded only by chart widgets) | 93.8 | 93.8 | 100 |
| CSS | 16 | 16 | 18 |
Regression fixed: `/app/[slug]` imported `AppHome` from the widgets barrel, and that barrel
re-exports the chart widgets. Recharts was therefore in the first load of every table and
process page, even though `WidgetRenderer` loads charts lazily. The page now imports its
components by module path.
## Runtime
Fixture: `tests/acceptance/fixture/PerformanceFixtures.java`, all synthetic data.
- `prfWide`: 10,000 rows, 40 columns of mixed types, read-only.
- `prfDashboard`: 24 widgets: 6 statistics, 6 bar, 4 line (60 points) and 4 pie charts, and
4 tables of 100 rows.
- `prfLongRun`: a job that walks all 10,000 rows in 40 pages with a 250 ms pause per page.
Each test measures wall time from the user action until the exact expected content is on
screen, and records it as a `timing` annotation in `test-results/acceptance/report.json`.
| Row | Measurement | Budget (ms) | chromium | firefox | webkit |
|---|---|---|---|---|---|
| PRF-001 | Open the 10,000-row table until the first 25 rows × 40 columns show | 5,000 | 535 | 353 | 602 |
| PRF-002 | Page size 25 → 250 (10,000 cells) | 10,000 | 462 | 665 | 738 |
| PRF-002 | Next page at 250 rows | 10,000 | 636 | 969 | 1,188 |
| PRF-002 | Last page (page 40) at 250 rows | 10,000 | 612 | 1,158 | 1,064 |
| PRF-003 | Sort by Name over 10,000 rows | 3,000 | 155 | 234 | 299 |
| PRF-003 | Filter Count 04 = 0 (40 matches) | 3,000 | 173 | 157 | 150 |
| PRF-004 | Open the 24-widget dashboard until every widget has loaded | 14,000 | 1,086 | 1,567 | 1,760 |
| PRF-005 | Job finished until its result screen shows (10.15 s job, 5 status polls) | 5,000 | 415 | 838 | 521 |
These numbers come from an otherwise idle run of the performance area on an Apple M3 Max
(macOS, Darwin 27), Playwright 1.58.2. Each budget is about 8× the slowest browser's time.
In a full four-project run with other acceptance suites on the same machine, the same steps
took up to about 4× longer: Firefox next page 4,191 ms, dashboard 8,841 ms. Retries are 0,
so a tighter budget would flake. The budgets catch order-of-magnitude regressions. The
structural checks below catch smaller ones, and don't depend on machine load.
The rows also check behavior besides time:
- Only one page of rows is in the DOM: 25 or 250 rows × 40 cells, not the whole table.
- The backend is asked for `skip`/`limit` 250 on each page.
- Each dashboard widget's data is requested exactly once.
- Status polls never overlap, and each poll waits at least 1.5 s (`POLL_INITIAL_MILLIS`)
after the previous answer. The job never gets more polls than its length allows.
Grid rendering fixed for large pages: body rows are memoized. The grid used to re-render all
10,000 cells before the page request was sent, and again for the URL sync. Date cells no
longer build an `Intl.DateTimeFormat` per cell. A 250-row page turn in chromium, from click to
rendered rows: 659–669 ms → 518–561 ms.
Observed, not changed here:
- Grid columns follow the field order of the metadata map, not the section order, and not
Material's alphabetical-by-label order. For a 40-column table this looks unordered.
- The pagination range is not locale-formatted, while the total is ("Showing 9751–10000 of
10,000").
+241
View File
@@ -0,0 +1,241 @@
# Next UI agent guide
> **This checkout targets RC36.** Confirm availability in the [RC36 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.36) before installing. [RC36 notes](../releases/1.0.0-RC.36.md) record the preparation checkpoint and verification scope; RC35 was the previously verified public candidate at that checkpoint. Earlier versioned receipts remain historical evidence; final 1.0 remains held.
> **Live roadmap and known issues:** [QRun-IO/qqq#713](https://github.com/QRun-IO/qqq/issues/713). GitHub issues own current status and remaining work. Follow the [issue-tracking policy](../ISSUE-TRACKING.md) when reporting or resolving findings; keep this guide focused on usage and evidence.
This is an implementation playbook for coding agents working on the QQQ Next UI. Read the [developer guide](./next-ui-developer-guide.md) for user and integrator behavior, the repository [AGENTS.md](../../AGENTS.md) and [CLAUDE.md](../../CLAUDE.md) for scoped conventions, and the [real-server feature matrix](../acceptance/feature-matrix.md) plus [Material parity ledger](../acceptance/material-parity.md) before claiming compatibility. This guide reflects the `feature/next-1.0` release worktree as of 2026-10-01; the previously verified public candidate at the RC13 preparation checkpoint was [1.0.0-RC.12](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.12) at `e333223`, and final `1.0.0` remains on hold. Later branch changes must not be attributed to that immutable release. Some ledger entries predate integrated changes, so reconcile them with source and real-server evidence. A green subset of tests is evidence for that subset, not proof of complete Material parity or a published 1.0 release.
## Operating rule: metadata is the product boundary
QQQ's Java backend owns tables, fields, processes, apps, navigation, widgets, permissions, and human labels. The browser adapts to the metadata of the current user. Never make a QQQ application work by hardcoding its table names, field names, routes, or widget data in a React component. Add general rendering behavior and a real backend fixture, or change the backend metadata producer where the contract originates. Do not invent JSON keys because a frontend type accepts `Record<string, unknown>`; inspect the actual producer, versioned API response, and acceptance fixture.
The UI supports a **versioned** API at `/qqq/v1`. The acceptance diagnostics reject browser calls to legacy unversioned paths. All browser requests should use typed functions in [`src/lib/api`](../../src/lib/api); TanStack Query keys come from [`src/lib/query-client.ts`](../../src/lib/query-client.ts). Treat permission flags as display controls only; the backend must refuse the same forbidden operation.
## Find the right source before editing
| Need | Start here | Why |
|---|---|
| Instance, app, table, field, process, report, widget shapes | [`src/types/metadata.ts`](../../src/types/metadata.ts), [`src/types/enums.ts`](../../src/types/enums.ts) | Frontend contract and enumerated types. |
| Backend HTTP contract | [`src/lib/api`](../../src/lib/api), [`src/lib/api/client.ts`](../../src/lib/api/client.ts) | Versioned routes, typed request/response handling, 401 behavior. |
| Route and sidebar dispatch | [`src/app/(dashboard)/app/[slug]/page.tsx`](../../src/app/%28dashboard%29/app/%5Bslug%5D/page.tsx), [`use-routes.ts`](../../src/lib/hooks/use-routes.ts), [`use-route-params.ts`](../../src/lib/hooks/use-route-params.ts), [`legacy-paths.ts`](../../src/lib/utils/legacy-paths.ts) | Slug precedence, hidden entries, nested nav, static-export and Material deep links. |
| Query and saved views | [`src/components/query`](../../src/components/query), [`use-record-query.ts`](../../src/lib/hooks/use-record-query.ts), [`use-saved-views.ts`](../../src/lib/hooks/use-saved-views.ts) | URL state, filters, variant, selection, backend saved views. |
| Record display and forms | [`src/components/records`](../../src/components/records), [`src/components/forms`](../../src/components/forms), [`FormWidgetSection.tsx`](../../src/components/forms/FormWidgetSection.tsx), [`zod-from-metadata.ts`](../../src/lib/utils/zod-from-metadata.ts) | Sections, fields, form widget context, validation, adornments. |
| Processes and reports | [`src/components/process`](../../src/components/process), [`ReportRun.tsx`](../../src/components/reports/ReportRun.tsx), [`src/lib/api/processes.ts`](../../src/lib/api/processes.ts) | Step components, state transitions, polling, files. |
| Widget entry and dispatch | [`ConnectedWidget.tsx`](../../src/components/widgets/ConnectedWidget.tsx), [`WidgetBlock.tsx`](../../src/components/widgets/WidgetBlock.tsx), [`WidgetRenderer.tsx`](../../src/components/widgets/WidgetRenderer.tsx) | Fetch/params, chrome, data renderer. |
| Widget payload details | [`src/components/widgets`](../../src/components/widgets), [`widget-types.ts`](../../src/components/widgets/widget-types.ts), [`src/mocks/fixtures/widgets`](../../src/mocks/fixtures/widgets) | Concrete canonical and demo shapes; demo fixtures are not production guarantees. |
| Branding, analytics and CSS | [`material-theme.ts`](../../src/lib/theme/material-theme.ts), [`src/lib/theme`](../../src/lib/theme), [`src/lib/analytics`](../../src/lib/analytics), [`src/styles`](../../src/styles) | Supplemental Material theme, implemented tokens, analytics privacy and `data-qqq-id` customization hooks. |
| Owned backend acceptance | [`tests/acceptance`](../../tests/acceptance), [`tests/acceptance/README.md`](../../tests/acceptance/README.md) | QQQ sample fixtures, browser projects, SQL assertions, diagnostics gate. |
| Current gaps and risk | [Material parity ledger](../acceptance/material-parity.md), [security review](../security/next-ui-review.md), [browser matrix](../acceptance/browser-matrix.md) | What has actually been verified and what remains open. |
The repo's [implementation plans](../implementation-plans) explain intended architecture, but some examples predate the current code. Prefer source and executable acceptance evidence. In particular, current `package.json` uses Next.js 16 even though older `CLAUDE.md` and plans mention 15.
## Preserve the Next visual baseline
Use the September 25 Next design (`e42ad2b2`) as the visual reference. Use Material source to establish functional behavior and backend contracts. Changes to parity behavior must preserve the Next layout unless the user requests a visual change.
- Desktop query mode controls, sort and chips belong inside the Filter panel. On phones, Filter opens a sheet containing those same controls and preserves the mode; Advanced shows the nested builder inside it. Check [`RecordQuery.tsx`](../../src/components/query/RecordQuery.tsx).
- Query column widths default to 150 px. Preserve `SIZE` adornment widths and saved/user-resized widths.
- The record section sidebar requires literal `showRecordSidebar: true` in `supplementalMetaData.materialDashboard` (legacy fallback: `supplementalTableMetaData.materialDashboard`). An absent setting keeps it off.
- T1 identity fields omit the primary key and values already contained in `recordLabel`, checking display values before raw values. Preserve the compact header and card field spacing in [`RecordViewHeader.tsx`](../../src/components/records/RecordViewHeader.tsx) and [`RecordView.tsx`](../../src/components/records/RecordView.tsx).
- Desktop Copy/Delete remain direct actions unless the Actions menu offers that enabled action, including in a nested menu. Preserve accessibility when a custom menu omits or disables the action; see [`RecordActions.tsx`](../../src/components/records/RecordActions.tsx).
- Preserve manual activation in `RecordViewTabs`: Left/Right/Home/End move focus, Enter/Space activates through the existing URL callback. A focused but inactive tab must not mount widgets. Keep one selected tab stop, unique tab/panel links and a focusable named panel. Phone accordion assertions should follow the trigger’s `aria-controls`; the nested section can legitimately have the same accessible name.
- Create, edit and copy share the centered `max-w-4xl` container in [`EntityForm.tsx`](../../src/components/forms/EntityForm.tsx).
- A fresh profile starts in light mode. A saved dark preference applies only without an application theme; an application theme keeps light mode active. Check [`theme-provider.tsx`](../../src/lib/theme/theme-provider.tsx).
- Preserve inline naming in `SavedBulkLoadProfiles`: New, Save As and Rename belong inside the original profile section. Prevent Enter from submitting the enclosing process form; retain ownership rules, backend validation and overwrite/delete confirmations.
- Dialogs launched from the phone sidebar must use the existing `--qqq-z-overlay` and `--qqq-z-modal` layers. A hardcoded `z-50` puts them behind the drawer. Return focus to a persistent control when the opening menu item unmounts.
- For Radix menus, check keyboard navigation, collision placement, available-height scrolling and native touch focus. Returning focus to the same trigger must not dismiss its newly opened menu; outside interactions must still dismiss it. Record a dialog's actual event opener because WebKit can activate buttons without focusing them.
- Search-dialog checks must exercise empty and populated Tab cycles, Clear removing its own button, Escape and backdrop dismissal, keyboard reopening, pointer openers that browsers do not automatically focus, and close-before-autofocus cleanup. Build `pnpm build:export` before Javalin acceptance; `pnpm build` produces a separate Node artifact.
- Browser keyboard checks must await the resulting focus before sending the next dependent key; Radix defers roving focus. Testing Library `getByRole` uses an exact string name by default and does not accept Playwright's `exact` option.
- Standard report input-then-download flows use `compactReportInputs` in `ProcessStepScreen`: preserve the compact card, inline Generate Report action, and accessible form name. Keep the shared form and cancellation; do not replace metadata-driven fields with primitive inputs. Custom components, section labels, process fields and longer flows retain normal step presentation. `DynamicForm.compactInputs` changes ordinary flat-list defaults while preserving explicit grid widths and rich editors.
- Test report editor tabs at a viewport short enough to make their panel scroll. Firefox can add a native focus stop on an overflowing container unless it has an explicit `tabIndex`; the report panel uses `-1` so Tab enters the first control. Verify draft preservation and metadata that hides Columns. Check validation contrast on the actual tinted alert background in both themes.
- A saved-view reset must discard both the applied report filter and any unapplied quick-filter input. `FilterAndColumnsSetupEditor` changes the quick-filter session key on view selection/reset; do not reset it on ordinary typing. Report view selection/reset must not call saved-view store/delete APIs. Verify Cancel against persisted report and saved-view rows.
- Report preview column indicators derive from complete criteria at every nesting level. Opening an indicator reveals the existing filter without appending an empty condition. Preserve read-only preview isolation and editor Cancel behavior; `report-filter-indicators.spec.ts` checks preview rows against SQL and stored report JSON.
- Treat table variants as part of the request and cache identity. Report previews reuse `VariantPicker` and the shared per-table storage key; do not query before selection or display rows from the preceding variant while loading. Scope copy and statistics too. Remounting a grid does not cancel an awaiting copy function: guard it against unmount before further requests and clipboard writes. Preserve the delayed-response regression in `report-variants.spec.ts`.
- Error text must be checked against its rendered background, including Retry hover and visible notifications. The error palette follows configured `errorColor`; Sonner currently uses a light notification surface in both page modes. Request the backend fixture before navigating in theme tests so instance metadata is reset. Assert the actual root theme as well as setting its preference.
Verify affected screens with paired desktop and phone captures, including a fresh preference state and explicit metadata overrides. Source inspection and passing behavioral tests do not establish full visual review.
Query URL synchronization in `use-record-query.ts` uses Next-integrated `window.history.replaceState(null, '', url)`. Keep updates synchronous with displayed query state, preserve unrelated parameters/hash, and replace the current entry. Do not reintroduce asynchronous page navigation for filter changes: QRY-093 holds an RSC navigation while checking immediate reload; QRY-017 covers back/forward and legacy JSON links.
## Workflow for a metadata-driven feature
1. **Find the Material behavior and acceptance row.** Search the parity ledger by screen or widget type and identify the owning issue and matrix ID. Read the current Material implementation when the behavior is ambiguous. Preserve negative cases and permissions.
2. **Trace the QQQ producer.** Locate the Java metadata or renderer that emits the relevant response. Record the exact versioned route and response, including whether data is omitted for unauthorized users. Check the real sample fixture. Do not assume the TypeScript interface is a complete backend specification.
For field choices, inspect `inlinePossibleValueSource` in the actual `/qqq/v1/metaData/table/{name}` response. The core Java frontend model can have a property that the v1 response wrapper omits; `REC-062` caught and closed this gap for inline enums.
3. **Pick the existing frontend seam.** Add an API function for a new endpoint, a query key for server state, a renderer case for a new widget type, or a metadata-driven component for a new field behavior. Keep application identifiers in metadata/fixtures only.
4. **Define behavior for loading, empty, invalid, denied, and failed states.** A malformed widget must not crash neighboring widgets. A denied action must be hidden or disabled and refused by the server. Preserve record identity, selected variant, return path, and unsaved form changes across interactions where applicable.
5. **Add focused unit tests for logic and an acceptance scenario for user behavior.** Acceptance tests use the owned QQQ server and check values or SQL state, not only visibility. Give every acceptance test a matrix ID, and use the `diagnostics` fixture. New rows live in the owning `tests/acceptance/matrix/*.json`; the Markdown matrix is generated from those files.
6. **Run the affected quality gates, then the full release gate when warranted.** Record commands, build/backend revisions, browsers, and results. Do not write “parity complete” while open Partial/Missing rows remain.
7. **Update documentation with verified behavior.** Explain the metadata producer, request/response, UI behavior, and any remaining limitation. Revise this guide's catalog if a widget type changes.
For code changes, follow import order and file naming in [CLAUDE.md](../../CLAUDE.md): React/Next, external packages, types, library, components; PascalCase component filenames, kebab-case utilities, co-located `*.test.ts(x)`. Client components with interactivity use `'use client'`. User text comes from `label` rather than internal `name`. Interactive elements need a stable `data-qqq-id`, an accessible name, keyboard operation, and appropriate focus management. Inspect existing IDs and [`material-theme.ts`](../../src/lib/theme/material-theme.ts) before promising Material selector or token compatibility.
## Configuration decision map
| Desired behavior | Correct source | Verify |
|---|---|---|
| Add a sidebar entry or nested app | Backend `QInstance.appTree` with `QAppTreeNode.type`; corresponding object in `apps`, `tables`, `processes`, or `reports` | Visible route and `useAppTreeRoutes`; hidden objects omitted from nav but authorized deep link still works; denied user's metadata. |
| Add an app home section | `QAppMetaData.sections` (`tables`, `processes`, `reports` by name) | Labels, order, links and permissions in app home. |
| Place dashboard widgets | `QAppMetaData.widgets` ordered names; matching `QInstance.widgets` metadata | `WidgetGrid`, widget POST, grid span, denied cases. |
| Place a record widget | `QTableSection.widgetName` and compatible record context; [`FormWidgetSection.tsx`](../../src/components/forms/FormWidgetSection.tsx) for editing | Two records render their own values; form widget changes join host validation/save; no cross-record cache leak. |
| Configure record layout | `QTableSection.alternatives.RECORD_VIEW`, field/section/widget `gridColumns`, section `collapsible`; Material supplemental table `showRecordSidebar` and `recordViewActionsPlacement`; instance `recordViewActionsPlacement` override | Use [`record-layout-utils.ts`](../../src/lib/utils/record-layout-utils.ts); sidebar defaults off and requires explicit `true`. Preserve the original Next defaults for unset widths: Overview half-width cards, selected section full-width card and two field columns (one on phones); explicit metadata spans win. Verify scroll, saved collapse state, and action location in desktop and phone layouts. |
| Configure record action menus | `QTableMetaData.menus` slots `VIEW_SCREEN_ACTIONS` and `VIEW_SCREEN_ADDITIONAL`; [`record-menu-utils.ts`](../../src/lib/utils/record-menu-utils.ts) | Permissions, nested menus, process deduplication, file downloads, and direct Copy/Delete availability. |
| Choose basic filter fields | Table dashboard `defaultQuickFilterFieldNames`, otherwise T1 section fields | Read [`quick-filter-utils.ts`](../../src/lib/utils/quick-filter-utils.ts); confirm resolved names, deduplication, basic/advanced conversion and phone sheet. |
| Add virtual query columns | Backend `virtualFields` with `isQuerySelectable`; filtering/sorting also requires `isQueryCriteria` | Query payload selects the virtual field; display-only virtual fields cannot become filter or sort criteria. |
| Add fields or editors | `QTableMetaData.fields`, sections, adornments, possible-value source | Form, view, validation, server save, keyboard and phone. |
| Show a joined field in a record section | Put `joinTable.field` in `QTableSection.fieldNames` and declare a readable `exposedJoins` path | The record GET includes that join; the qualified value renders with joined field metadata. REC-064 covers the real sample in five profiles; permission-denied paths stay absent. |
| Read a variant table record | `QTableMetaData.usesVariants` and the query screen's `qqq.tableVariant.<table>` choice | Pass [`storedRecordVariantJson`](../../src/lib/utils/table-variant.ts) to each view/edit/copy record GET; verify the same ID resolves to the selected variant's values ([REC-063](../acceptance/feature-matrix.md)). |
| Add a process screen | `QProcessMetaData.frontendSteps` and a supported `QFrontendComponent.type` | Init, submit, async status, cancel/retry, output and server mutations. |
| Add report | `QInstance.reports`, optional `processName` | CSV/XLSX/JSON and required inputs; actual downloaded bytes. |
| Customize identity | `QInstance.branding` for app/company/logo/banners; `supplementalInstanceMetaData.materialDashboardTheme` for the active application theme | Login subset, footer, header, colors, density, icons, contrast, CSP. `QInstance.theme` is not the active theme input. |
| Customize widget chrome | `QWidgetMetaData` fields such as `gridColumns`, `isCard`, `icon`, `collapsible`, `dropdowns`, `showReloadButton`, `showExportButton`, `helpContent`, `icons`, `footerHTML` | Widget response's labels, lists/CSV and interaction; remembered collapse and dropdown state. |
| Customize application CSS | `supplementalInstanceMetaData.materialDashboardTheme.customCss` against observed `data-qqq-id`/implemented CSS tokens | Browser rendering, contrast, and actual Material selector/token mapping. |
| Configure analytics | `QInstance.environmentValues` with provider settings and privacy opt-ins | No provider initializes without settings; IDs/search/labels withheld by default; CSP for scripts. |
| Switch host mode | `QQQ_NEXT_OUTPUT=export` for Javalin jar, or standalone build with `QQQ_BACKEND_URL` | Root deep link, `/qqq/v1` calls, asset paths, browser refresh. |
Table dashboard settings use `supplementalMetaData.materialDashboard` in v1; `supplementalTableMetaData.materialDashboard` is the legacy fallback. When both objects exist, the v1 object wins. Use [`materialDashboardTableMetaData`](../../src/lib/utils/goto-utils.ts) rather than reading only one spelling.
The exact type names and optional fields are in [`metadata.ts`](../../src/types/metadata.ts). `supplementalInstanceMetaData`, `supplementalTableMetaData`, and widget `defaultValues` are extension maps, not licenses to invent arbitrary frontend behavior. Inspect each consumer and QQQ backend producer first.
### A safe widget change, end to end
For an existing `statistics` widget, the backend can declare metadata equivalent to this TypeScript value:
```ts
import type { QWidgetMetaData } from '@/types'
const metadata = {
name: 'openOrders',
label: 'Open orders',
type: 'statistics',
hasPermission: true,
gridColumns: 4,
showReloadButton: true,
} satisfies QWidgetMetaData
```
The canonical QQQ renderer then returns a payload such as `{ "type": "statistics", "count": 7, "countContext": "orders awaiting review" }`. `ConnectedWidget` calls the typed widget API, `WidgetBlock` renders the frame, and `WidgetRenderer` selects `QqqStatisticsWidget` because `count` is present. The local demo statistic instead uses `value`; do not copy that shape into a QQQ Java renderer without verifying it. To add a new renderer type, add the dispatcher branch, a focused component, a malformed/empty case, real backend fixture, and a WID acceptance row. For existing types, do not add a duplicate dispatcher path.
For a dropdown, metadata alone is insufficient. The backend payload supplies parallel `dropdownNameList`, `dropdownLabelList`, `dropdownDataList`, and `dropdownDefaultValueList`. The selected option ID is sent back as a widget request parameter. A PVS dropdown uses `possibleValueSourceName` as that parameter name; a `DATE_PICKER` uses its `name`. `storeDropdownSelections` writes a local preference only. Test a stale stored choice and a required selection ([WID-047–WID-050](../acceptance/feature-matrix.md)).
For `customComponent`, metadata `defaultValues.componentName` and `componentSourceUrl` select a trusted external bundle. The browser loads `window[componentName][componentName]`, exposes `window.React`/`window.ReactDOM`, and passes `qfmdBridge` and `qContext` to the component ([`QqqContainerWidgets.tsx`](../../src/components/widgets/QqqContainerWidgets.tsx), [`qfmd-bridge.tsx`](../../src/components/widgets/qfmd-bridge.tsx)). Verify an existing application's real bundle and CSP before claiming compatibility; the in-repo bridge test alone does not prove it.
### Widget editors and host forms
[`WidgetRenderer.tsx`](../../src/components/widgets/WidgetRenderer.tsx) dispatches all 38 renderer labels cataloged in the [developer guide](./next-ui-developer-guide.md#7-widgets-placement-configuration-and-data). Pass the host's `formContext`, `dataVersion`, and `onWidgetData` through the existing connected boundary when the widget edits data. A record form's [`FormWidgetSection.tsx`](../../src/components/forms/FormWidgetSection.tsx) selects editable widgets; a process `WIDGET` step has its own host ([`WidgetComponent.tsx`](../../src/components/process/WidgetComponent.tsx)). Do not make a display widget write directly to the backend or silently mutate a neighboring form field.
For a row builder, inspect [`row-builder-model.ts`](../../src/components/widgets/row-builder-model.ts) and the backend producer before setting `defaultValues`. It reads `frontendFields` (else `fields`), `associationName` or `outputFieldName`, `orderByFieldName`, `mayReorderRows`, `isForRecordViewAndEditScreen`, `isEditable`, `useModalEditor`, `inlineHeading`, `modalTitle`, and `defaultValuesForNewRowsFromParentRecord`. A record form uses association values when `associationName` is set; a process step uses the named output field. Verify add/delete/reorder, validation, save/cancel and reload with real data. The metadata flags accept booleans or the strings `"true"`/`"false"` where the model explicitly parses them.
For `pivotTableSetup`, the record view reads `pivotTableJson`; a form supplies the editor context. The editor needs `tableName` and `columnsJson` in the host form and writes serialized `pivotTableJson`. Verify switching the pivot on/off through the actual host form and database readback: a widget-only callback assertion does not prove that clearing survives form validation. Also check modal cancel/save, invalid or missing columns and a persisted reload ([`PivotTableSetupWidget.tsx`](../../src/components/widgets/PivotTableSetupWidget.tsx)). For `dynamicForm`, inspect the payload's `fieldList`/`recordOfFieldValues` and `defaultValues.isEditable`; its editable fields join the process or record form's validation and submission. For a record widget that merges to JSON, verify the named table field exists in a hidden section (rather than marking the field `isHidden`), the renderer returns stored values in `recordOfFieldValues`, required fields block Save, and only the JSON field reaches the record API. [REC-061](../acceptance/feature-matrix.md) proves the full round trip across five browser profiles. `childRecordList` can manage an association when `defaultValues.manageAssociationName` is set. `dataBagViewer` can edit through `storeDataBagVersion` when that process exists.
`filterAndColumnsSetup` renders a summary and paged, sortable, resizable preview on record view, and an editor when a form context is present, unless the payload sets `overrideIsEditable: false`. [`FilterAndColumnsSetupWidget.tsx`](../../src/components/widgets/FilterAndColumnsSetupWidget.tsx) hands the form to [`FilterAndColumnsSetupEditor.tsx`](../../src/components/widgets/FilterAndColumnsSetupEditor.tsx). The editor loads the selected table's metadata, uses `FilterBuilder` and `ColumnConfig`, offers ordered sort levels and a draft preview inside the modal, and writes `queryFilterJson`/`columnsJson` (or payload-selected field names) to host values on `OK`. Edit opens the filter tab; + Add Filters and + Add Columns appear when the respective saved content is absent and open the matching tab. `Cancel` and closing the dialog discard the draft; persistence still requires saving the host record or advancing a process. A `WIDGET` field adornment with `values.widgetName` uses [`WidgetAdornmentField.tsx`](../../src/components/forms/WidgetAdornmentField.tsx): it sends the host form values and `__formFieldAsWidget_FieldName` to the widget request, overrides `filterFieldName` with the actual field name (including a row index), and writes only that field back. Material supports `filterAndColumnsSetup` in this field path; other widget types show a contained unsupported message. Inspect [`filter-and-columns-utils.ts`](../../src/components/widgets/filter-and-columns-utils.ts) and the backend producer for `hideColumns`, `hideSortBy`, `hidePreview`, `allowVariables`, API-version settings, default filter fields, omitted joins, and custom labels. A missing table disables editing; missing required default-field values produce an alert when an editor control is pressed. A variable saves as a `FilterVariableExpression`; unresolved variables suppress preview and a query opened in a new window until a value is supplied. WID-030, WID-070, WID-071 and WID-072 cover the summary, preview, variable assignment, editor save, field-adornment process path and API-versioned metadata/query/count across five browser profiles. WID-072 supplies version flags to the widget response while the metadata and data requests go to real QQQ API-aware routes. The editor now shares Basic/Advanced controls, saved-view selection, density, refresh, column menus, and statistics with the query components. Saved-view selection is intentionally read-only in this host: it imports the view into the report draft. Read-only preview changes remain local; edited widths/pins serialize into columnsJson on OK and host Save. Starting in RC4, `omitExposedJoins` limits new filter/column choices in both the read-only preview and editor. Existing saved joined columns, criteria and sorts remain usable, and reordering available columns preserves omitted columns in place. Opening the editor and pressing OK preserves those saved fields; Basic/Advanced changes and host Save/reopen retain their values and widths. This is a choice restriction, not a backend permission boundary; existing joined data still uses the normal query permissions. WID-073 checks the configured and unconfigured cases against SQL, including local edits and reload. WID-073 verifies pin/width persistence, full-query copying, and nested statistics. Check the remaining embedded-query parity ledger before claiming complete equivalence; variable assignment currently uses Advanced mode.
### Composite input editors
For complete record submissions, call `wireValuesFromForm(table, values, originalValues)`: EntityForm passes `record.values`, association editors pass the existing row values, and process child editors pass the original dialog defaults. Ordinary updates still omit unchanged DATE_TIME fields. Preserve this distinction for copies and child-row edits; omitting the original can shift a repeated DST hour and truncate fractional seconds. REC-065 verifies the copy wire value and SQL source/copy; both child-editor paths have component regressions.
Use `toLocalDateTimeInput` for seeded process/standalone DATE_TIME values and `fromLocalDateTimeInput(value, originalValue)` at submission. Supplying the original preserves an unchanged instant in the repeated DST hour and its hidden subsecond precision. Normalize the final process payload after contributors have added values, so bulk-edit fields use the same contract. Do not slice UTC text into datetime-local controls or send local text as an instant. PRC-064 and WID-075 exercise America/New_York dates and seconds. Published RC7 sets a one-second step on plain TIME blocks, matching regular/shared fields. Keep TIME values as local clock strings; do not apply instant/timezone conversion. PRC-064 checks unchanged and edited seconds with SQL readback, and WID-074 checks standalone native stepping. This correction is published in RC7 at `f65cf6b`; RC6 remains unchanged.
`ProcessRun` preserves focus already held inside a configured `[data-qqq-autofocus="true"]` editor; other later screens still focus their heading. Preserve this precedence when changing screen lifecycle. On scalar Enter, normalize text in the hosting form before validation/submission; do not trim unrelated fields or convert typed values to display strings. PRC-063 covers later-screen focus, placeholders, prior values, first blur, whitespace validation and backend-stored text.
Keep plain block controls visually stable. Backend field metadata supplies the neutral `displayFormat: "%s"`; do not treat that as a request for a different editor. Reuse `BlockInputEditor` and `DynamicFormField` for choices, formatting, behaviors and supported field adornments. Process blocks must bind to the existing screen form and send file contents as multipart files. Never create nested forms or serialize a file as its name. Surrounding process values belong in a field widget's request context, but widget changes must update only its own field.
Verify hidden/read-only flags, typed choice IDs, numeric conversion, multiline Enter, required uploads and uploaded bytes. For uppercase/lowercase behaviors, replace part of mixed-case text with an uppercase character and check the caret plus stored values. Include Unicode expansion (`ß` to `SS`, `İ` to `i` plus combining dot), then type another character to verify the insertion point; retain backward selection direction. Cover both `STRING` and `TEXT`: textarea Enter must insert a newline without submitting, including with `submitOnEnter: true` (PRC-066/WID-074). For explicitly configured `PASSWORD` fields, verify masked editing, Unicode expansion, show/hide value preservation, Enter/Submit, and independent database readback. Also prove that passwords without a case behavior retain mixed case. The standalone choice field must not collapse below the neighboring plain input's width: a narrow control can place its Clear button under an ordinary center click. Check system and wider native fonts, plus a 320px viewport. The metadata-editor minimum is capped by available width. Sample-dashboard acceptance must initialize the owned backend session and diagnostics before its page-opening hook. PRC-062 exercises the backend round trip, and WID-074 checks the standalone controls. Screenshot comparisons must include the richer editors' height and scroll states; functional success does not approve a changed layout.
## Navigation and route rules
`/app/{slug}` resolves an app, table, process, or report from loaded instance metadata, in that order. Do not create a static page for each QQQ object. `isHidden` removes a table/process/report from navigation, while an authorized direct route still works. New dynamic route shapes must work in both standalone and Javalin static export. The export prerenders `_` placeholder paths; [`useRouteParams`](../../src/lib/hooks/use-route-params.ts) translates them from the browser path. Preserve `trailingSlash` handling and avoid constructing deep links that a static host cannot serve. Use [`material-links.ts`](../../src/lib/utils/material-links.ts) for generated links. The not-found route's [`LegacyPathRedirect`](../../src/components/layout/LegacyPathRedirect.tsx) recognizes nested Material paths and `QInstance.redirects` exact or trailing `/*` rules while preserving query/hash. Test deny/not-found loops, a child create URL (`/app/{table}/{id}/createChild/{childTable}`), and refresh of each route shape. Root-path hosting is the documented target; the parity ledger lists base-path support as Missing.
The [developer route catalog](./next-ui-developer-guide.md#4-navigation-and-deep-links) includes authentication callbacks, table/process developer views, record actions and child creation. `/app/{table}/{id}/{process}` resolves registered processes (table exact name, table suffix, then instance exact name) and redirects with record and return context; the runner enforces permission; preserve that route alongside the reserved edit/copy/dev pages.
The query URL owns `page`, `pageSize`, `filter`, and `q`; do not move shareable filter state into local storage. Saved views have `/app/{table}/savedView/{id}` and backend persistence. Local storage is for preferences, such as density, column configuration, and widget selections. See [state management](../STATE-MANAGEMENT.md). Put app shell labels, icons, and nested nav in metadata and verify breadcrumb titles against the same tree.
### Keep configuration coverage executable
The developer guide's [widget configuration recipes](./next-ui-developer-guide.md#widget-configuration-recipes), [link parameters](./next-ui-developer-guide.md#link-parameters-and-launch-examples), [theme properties](./next-ui-developer-guide.md#theme-property-reference), and [analytics setup](./next-ui-developer-guide.md#analytics-configuration-example) cover the supported keys and copyable response fragments. Update the matching reference whenever a consumer changes; a catalog row with only a widget name is insufficient documentation for new configuration.
- Compare all `WidgetRenderer` cases with the catalog, including canonical/demo payload branches. Keep `QqqComposite` leaf types and layouts covered separately; they are not top-level widget dispatch labels.
- Compare theme properties with `THEME_PROPERTY_KINDS`, including generated typography and radius keys. Keep browser-public `environmentValues` separate from build variables and backend secrets.
- Trace dropdown behavior through `ConnectedWidget`: lists pair by position, payload names are request keys, response defaults must be offered options, and `dropdownNeedsSelectedText` gates the body. The interface's `isRequired` and `defaultValue` fields do not by themselves implement those behaviors.
- Preserve `gridCols:sizeClass:*` overrides in widget metadata defaults. Their Material breakpoint widths differ from ordinary Tailwind defaults; explicit overrides can keep widgets side by side on smaller screens.
- Document `table` cell helper values and `subRows`, chart dataset/URL arrays, and host-specific composite behavior when changing these families. Modal control codes work locally; process `conditional` and interpolation depend on the composite host. RC6 icon compatibility preserves existing Lucide mappings, then lazily resolves valid legacy names through `LegacyMaterialIcon` and the pinned local font. Unknown names retain their fallback. Keep the codepoint inventory, font and license together; verify real JAR font delivery, both themes, existing mapped icons, image precedence, and bundle budgets before integration. Do not attribute this behavior to RC5 or claim support for every Material Symbols name/style.
- Check both table-scoped aliases (`/app/{table}/{process}` and `/app/{table}/{report}`) before record GET behavior. Process aliases take precedence over reports; matching names can shadow record IDs. Preserve form hash presets, record tab/view/return parameters, process selection/default inputs, and safe return-path validation.
- Preserve the distinction between a displayed preview, a host-form draft, and persisted data. A payload example demonstrates the contract shape; it does not establish backend acceptance for every combination of options.
## Security and accessibility review points
- Use the typed API client and same-origin cookie session. Do not put access tokens, passwords, or server secrets in browser storage, public environment variables, widget metadata, or tests.
- Session resume now calls `manageSession` with an empty request so browser code can use HttpOnly session cookies; do not restore a `document.cookie` dependency. Test sign-in, refresh, expiry and sign-out against cookie attributes and the real server. Authentication metadata's `?clearAuthenticationMetaDataLocalStorage` switch is a cache reset, not a permission bypass.
- Preserve existing identity-provider callback registrations: OAUTH2 uses `/token`, AUTH_0 uses `/`. Provider descriptions may reach login only through the state-matched callback and a one-time browser session value; never render free text from `?error_description=` on a direct link.
- Check permission at the UI and the backend. Exercise a denied persona in acceptance; a hidden button alone is insufficient. Do not call widget, process, or record endpoints for an omitted or disabled object.
- For exposed query joins, check every target and intermediate table named in `joinPath`. The query screen filters its metadata before rendering columns or filters, and the request hook applies the same rule before sending a join. Keep both paths aligned when changing join UI.
- Sanitize HTML. Review CSS, external links, iframes, custom bundle URLs, and CSP when adding a new embed. The Javalin host and standalone server supply security headers; test the deployed response. The [security review](../security/next-ui-review.md) predates some integrated fixes, so reconcile each follow-up with source and current acceptance evidence.
- Give forms real labels, `aria-required`/`aria-invalid` where relevant, keyboard operation, error text, and a sensible focus target. Dialogs and phone sheets must trap focus and restore it. Check both desktop keyboard and phone/tablet touch interactions.
- The nonmodal Columns popup focuses its named container on open and returns focus to its trigger on explicit close/Escape. Outside clicks keep focus on the clicked control. Embedded report column editors must not take focus when mounted. Verify keyboard search and native browser Tab behavior with QRY-092.
- Keep errors local when possible: widget failures show a contained retry and leave neighboring widgets usable; route errors and missing records have dedicated states. Never swallow an API failure into a blank page.
## Quality gates and evidence
| Command | Evidence it supplies |
|---|---|
| `pnpm typecheck` | TypeScript contract check. |
| `pnpm lint` | ESLint and license headers. |
| `pnpm test` | Vitest unit/regression behavior. |
| `pnpm test:e2e` | Mocked Playwright UI paths, useful but not real backend compatibility. |
| `pnpm build:export` | Javalin static-export build and dynamic placeholder output. |
| `pnpm perf:budget` | Static-export JS/CSS budget after `build:export`; see [performance guide](../acceptance/performance.md). |
| `QQQ_ACCEPTANCE_BROWSERS=chromium,firefox,webkit,mobile,tablet pnpm test:acceptance` | Full real-sample browser and backend gate when `QQQ_SAMPLE_JAR` points to a compatible sample jar. |
For RC1, prioritize workflow depth in Chromium: normal entry/defaults, validation, edit/cancel, save/reopen, dependent selections, permissions/recovery and persisted records or downloads. Preserve broader tests and report their status; do not treat scenario counts as proof that everything works.
Read [acceptance setup](../../tests/acceptance/README.md) and [browser matrix](../acceptance/browser-matrix.md) before running the final gate. Default acceptance runs only Chromium; the five-project command is necessary to claim the stated browser scope. Phone and tablet run tagged tests, with matrix coverage enforced by the gate. Every test title needs its matrix ID; use fixtures with a reset database, verify persisted effects through SQL, and include `diagnostics` for console, network, and CSP failures. Do not loosen a matrix row, skip a failing browser, or declare an external service excluded without the documented approval path.
## Common mistakes to avoid
- **Inventing config from a plan or TypeScript escape hatch.** Read the actual QQQ producer and renderer; `Record<string, unknown>` does not prove a key is honored.
- **Treating mocked widgets as a canonical backend contract.** `statistics.value` and several demo aliases are fixtures; canonical QQQ statistics uses `count`.
- **Editing a route for one table.** The slug route and app tree are generic; change backend metadata or general dispatch behavior.
- **Fetching inside a presentational metadata component.** Keep metadata as a prop where the component pattern expects it; use hooks/API modules at the connected boundary.
- **Calling raw `fetch` for QQQ endpoints.** It bypasses versioned base URL, credentials, and centralized error/session behavior.
- **Using `name` as display text.** It is an internal identifier; use `label` and a deliberate fallback only when metadata is genuinely absent.
- **Claiming Material theme, custom bundle, URL, or workflow parity from a happy path.** Check current code, the parity ledger, negative/permission rows, and actual browser/backend acceptance evidence. The filter/column editor has real-server coverage in WID-070–WID-072, while the embedded full query screen remains parity work.
- **Testing a static deep link on a generic file server.** QQQ Javalin serves placeholder pages for dynamic paths; standalone Next has its own routing path.
## Completion checklist for an agent change
1. The backend metadata or widget/process producer and the frontend consumer agree on names, types, and payload fields.
2. An authorized user can complete the workflow, while an unauthorized user cannot perform it through the API.
3. Loading, empty, malformed, missing, failed, and retry states are addressed where they apply.
4. Keyboard, focus, labels, responsive phone/tablet behavior, and contrast have been checked for changed UI.
5. Focused tests and the applicable real-server matrix row pass. The final browser claim names the projects actually run.
6. Static export or standalone build, as applicable, works on refresh and direct deep link.
7. Docs and the parity ledger reflect the observed behavior; any open Partial/Missing rows remain visible until proven closed.
### Appearance preferences
Use the real ThemeProvider when rendering UserPreferencesDialog or a sidebar with its user footer. The saved `darkModePreference` is distinct from effective `isDarkMode`: an application theme overrides dark mode without erasing the choice. Preferences exposes Light/Dark, explains a theme lock, and resets both saved appearance and layout defaults. Preserve `qqq-dark-mode` storage compatibility. Verify changes through the actual Preferences flow and reload (NAV-056), including phone drawer layering/focus (NAV-038); injecting localStorage alone does not exercise the user control. There is currently no system appearance choice or account/device synchronization.
### Google Drive integration boundary
Google Drive picker changes span the frontend component/SDK loader and Javalin's CSP
and v1 public-environment allow-list (#704; backend PR #908 merged to develop as `bac663044`). The frontend needs those backend changes to receive its public configuration and load under the configured CSP. PRC-065 tests controlled SDK responses with
real process execution and SQL; PRC-039 is the approved real-account exclusion. Preserve
the shared-drive view, folder-only validation, cancellation and token expiry handling.
Never log or persist the access token, and never publish OAuth client secrets in metadata. Javalin applies conditional CSP to its static export; standalone Node/container deployments derive the same sources from backend `dashboardCspSources` authentication metadata. Older backends that omit that optional field require runtime `QQQ_DASHBOARD_CSP_SOURCES` using the [documented Google directives](./next-ui-developer-guide.md#google-drive-process-configuration). Check the actual dashboard HTML response from the selected host. RC1 remains immutable and retains its historical placeholder; these changes belong to the ongoing final 1.0 work.
### Script editor suggestions
`CodeEditor` autocomplete is opt-in from `ScriptEditorDialog`; keep other metadata code fields unchanged unless their contract calls for it. QQQ helpers, language keywords and file identifiers are local suggestions. Preserve native undo/redo, composition input, read-only behavior, caret/selection validation, and Escape-then-Tab focus escape. `records/script-completions.spec.ts` verifies loaded-revision undo/redo, prefix and pointer/touch insertion, both-theme accessibility, and SQL-backed save/reload. Native browsers group consecutive edits differently; do not claim every insertion creates a separate undo transaction.
### RC8 implementation notes
Public JAR checksum/signature/fresh-consumer checks and 28 affected desktop/mobile workflows passed. Full tag Chromium passed 602 and mobile 563; Firefox, WebKit and tablet retain failures, including tablet Clear-search focus. Consult the release notes before claiming cross-browser completion.
- Search must contain the entire Tab/Shift+Tab cycle, handle Escape from every dialog control, restore the opener after dismissal, and move focus to the input before Clear removes its own button. Explicitly focus a phone pointer opener because WebKit does not necessarily focus clicked buttons. Cancel delayed autofocus when closing. `navigation/search-focus.spec.ts` covers light/dark and pointer/keyboard openings; preserve the existing layout.
- Keep RapiDoc width handling on its public CSS parts. The named `qqq-api-overview` and `qqq-api-overview-wide` containers restrict narrow wrapping and wide content sizing to overview specification actions; do not apply it to authentication or endpoint controls. Test full visibility before focus can scroll a clipped control into view, real JSON downloads, keyboard new-tab behavior, both themes, and label fit with wider application fonts at desktop and phone widths.
- LONG range validation must run only after integer syntax succeeds. Preserve integer-string bounds as `BigInt` values; converting through `Number` rounds large bounds. Keep the mixed numeric comparison for fractional numeric bounds. Test `safeParse` error results without exceptions, exact positive/negative boundaries, optional blanks, and correction/submission through block and shared process forms. These changes do not introduce a backend range-metadata contract.
+518
View File
@@ -0,0 +1,518 @@
# Next UI developer guide
> **This checkout targets RC36.** Confirm availability in the [RC36 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.36) before installing. [RC36 notes](../releases/1.0.0-RC.36.md) record the preparation checkpoint and verification scope; RC35 was the previously verified public candidate at that checkpoint. Earlier versioned receipts remain historical evidence; final 1.0 remains held.
> **RC9:** application code is unchanged from RC8. See [RC9 notes](../releases/1.0.0-RC.9.md) and [published releases](https://github.com/QRun-IO/qqq-frontend-next/releases) for versioned verification and public delivery status. Earlier RC8 receipts below remain historical evidence.
> **Live roadmap and known issues:** [QRun-IO/qqq#713](https://github.com/QRun-IO/qqq/issues/713). GitHub issues own current status and remaining work. Follow the [issue-tracking policy](../ISSUE-TRACKING.md) when reporting or resolving findings; keep this guide focused on usage and evidence.
This guide describes the `feature/next-1.0` release worktree as of 2026-10-01. It explains the current frontend and backend metadata contract. The previously verified public candidate at the RC36 preparation checkpoint was [1.0.0-RC.35](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.35) (commit `55c1501`); final `1.0.0` remains on hold. Later branch changes are not retroactive changes to that immutable release. For behavior proven against a real server use the [feature matrix](../acceptance/feature-matrix.md); for the full Material inventory use the [parity ledger](../acceptance/material-parity.md). Some ledger rows predate recent work, so verify an individual status against current source and acceptance results before making a release claim.
The visual baseline is the September 25 Next UI design (`e42ad2b2`). Material remains the reference for backend contracts and functional behavior. The layout defaults below describe current source; they do not certify a completed visual review.
## 1. What an application supplies
QQQ supplies an instance metadata document with `apps`, ordered `appTree`, `tables`, `processes`, `reports`, `widgets`, and `branding`; help content, environment values, supplemental metadata, and redirect rules may also be present. The UI renders those objects for the signed-in user's permissions. A table, widget, or route name is an internal identifier; its `label` is the text users see. Define names and behavior on the backend, then verify the versioned metadata response. Do not edit the frontend to add an application-specific table or menu item.
The source of truth for the frontend shape is [`src/types/metadata.ts`](../../src/types/metadata.ts). A small, **partial response fragment** illustrates the relationships; it is not a complete `QInstance` response:
```json
{
"appTree": [{ "name": "operations", "label": "Operations", "type": "APP", "children": [{ "name": "orders", "label": "Orders", "type": "TABLE" }] }],
"apps": { "operations": { "name": "operations", "label": "Operations", "widgets": ["ordersToday"], "sections": [{ "name": "work", "label": "Work", "tables": ["orders"] }] } },
"widgets": { "ordersToday": { "name": "ordersToday", "label": "Orders today", "type": "statistics", "hasPermission": true, "gridColumns": 4 } },
"branding": { "appName": "Operations", "accentColor": "#1d4ed8" }
}
```
`appTree` controls the sidebar; `apps[name].widgets` controls ordered dashboard widgets; `apps[name].sections` controls the app home cards. The server may omit denied objects or send disabled permission flags; the frontend also checks flags before requesting data. The server must still enforce authorization. A widget can also be placed in a table section through `QTableSection.widgetName`, or embedded in a process step of type `WIDGET`. `QInstance.redirects` can map old exact or wildcard Material paths to new targets. `QInstance.homeAppName` (QQQ [#1059](https://github.com/QRun-IO/qqq/issues/1059)) designates the app whose dashboard is the instance's home page; it is published only to users who may open that app. `supplementalInstanceMetaData.materialDashboardTheme` supplies the application theme, and `environmentValues` can configure analytics.
## 2. Install and run
The preferred QQQ-hosted distribution is the `com.kingsrook.qqq:qqq-frontend-next` Maven jar. A QQQ 4.1+ application's `QApplicationJavalinServer` serves its static export from `next-dashboard/` at `/` on the same origin as `/qqq/v1`. The [repository README](../../README.md) and [jar README](../maven-artifact/README.md) describe this packaging. The QQQ [sample quickstart](https://github.com/QRun-IO/qqq/blob/main/qqq-sample-project/README.md#quickstart-with-next) exercises the packaged path. Material can be selected with `withServeFrontendMaterialDashboard(true)` or `-Dqqq.javalin.frontend=material`; do not assume Material and Next use the same URL layout.
```xml
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.36</version>
</dependency>
```
For frontend development, use a compatible Node.js version (the README specifies 20.19+ or 22.12+) and pnpm 9.15.9:
```bash
pnpm install
NEXT_PUBLIC_MOCK_API=true pnpm dev
pnpm build:export
pnpm build:jar
```
The mock setting is for isolated development. Set `NEXT_PUBLIC_MOCK_API=false` and connect a real QQQ backend for integration. `build:export` writes `out/`; `build:jar` packages it with Maven. The Maven POM enforces the presence of static placeholder pages. The default `pnpm build` instead creates a standalone Node server, whose `QQQ_BACKEND_URL` build setting adds same-origin rewrites. For the container path see [`docker/quickstart/Dockerfile`](../../docker/quickstart/Dockerfile). This checkout targets `1.0.0-RC.13`, overriding an older BOM-managed frontend; confirm its availability in the [RC13 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.13) before installing. It requires a published QQQ 4.1 candidate (4.1.0-RC.1 or later; 4.1.0-RC.2 or later for standalone branding images, QQQ #1009; 4.1.0-RC.3 or later for FILE_PREVIEW thumbnails and the instance home app, QQQ #1056/#1059); see the release notes for the verified backend. Use a matching local backend/frontend build to test later branch changes.
| Setting | Behavior in this checkout |
|---|---|
| `NEXT_PUBLIC_API_BASE_URL` | Defaults to `/qqq/v1`; baked into browser requests. Keep the UI and API on the same origin where possible. |
| `NEXT_PUBLIC_MOCK_API` | `true` enables MSW development fixtures; `build:export` forces `false`. |
| `QQQ_NEXT_OUTPUT` | `export` selects the Javalin-hosted static export; otherwise standalone build. |
| `QQQ_BACKEND_URL` | Standalone build only: backend origin for Next rewrites; fixed at build time. |
| `NEXT_PUBLIC_APP_VERSION` | Optional version reported to the backend; defaults to `package.json` version. |
| `QQQ_DASHBOARD_CSP_SOURCES` | Standalone server runtime setting: additional CSP directives/sources for external services. See Google Drive hosting below. |
The static export is built for `/`, with `trailingSlash: true`. QQQ serves placeholder HTML for dynamic route shapes; [`useRouteParams`](../../src/lib/hooks/use-route-params.ts) reads real path segments from the browser. Keep that behavior intact when adding routes. The [parity ledger](../acceptance/material-parity.md) currently lists sub-path hosting as unsupported. A plain file server without the QQQ route fallback is not a valid deep-link test.
## 3. Authentication and security
`GET /qqq/v1/metaData/authentication` selects `AUTH_0`, `OAUTH2`, `FULLY_ANONYMOUS`, `MOCK`, or `TABLE_BASED` (see [`QAuthenticationMetaData`](../../src/types/metadata.ts) and [`auth-provider.tsx`](../../src/lib/auth/auth-provider.tsx)). OAuth flows use authorization code and PKCE; table-based sign-in sends credentials to the QQQ session endpoint. The server issues `sessionUUID`/`sessionId` cookies; current Next code resumes an existing session with an empty `manageSession` request, allowing HttpOnly cookies that browser script cannot read. Browser API calls use credentials. A 401 clears session state and returns to sign-in. The pre-login response exposes only limited login branding, not banners or custom CSS. Authentication metadata is cached briefly; `?clearAuthenticationMetaDataLocalStorage` clears that cache.
Existing provider registrations remain usable: OAUTH2 redirects to `{origin}/token`, while AUTH_0 redirects to `{origin}/`. Both use the same state-checked PKCE callback. A state-matched provider denial displays its description once on the login page; a forged callback or direct login link shows only a safe generic error.
Permissions apply to metadata and data: check table capabilities and the corresponding read/insert/edit/delete flags, `process.hasPermission`, `report.hasPermission`, and `widget.hasPermission`. A disabled widget can show a permission message without fetching; omitted metadata cannot be guessed from the UI. Use the backend to prove a forbidden operation is rejected. HTML from metadata or widget payloads is sanitized before display. Treat theme `customCss` and custom component bundles as trusted application configuration with a stricter review boundary. The Javalin dashboard and standalone Next server configure security headers and CSP; embedding an external provider needs an allowed source, not a disabled policy. The [security review](../security/next-ui-review.md) is an earlier audit; compare its follow-ups with current code and the latest acceptance gate before treating them as resolved.
## 4. Navigation and deep links
[`useAppTreeRoutes`](../../src/lib/hooks/use-routes.ts) recursively walks `appTree` to build sidebar links, labels, ancestor breadcrumbs, and the first accessible app. `APP` nodes nest; visible `TABLE`, `PROCESS`, and `REPORT` nodes navigate to `/app/{name}`. Hidden objects are omitted from navigation but can still have direct routes when authorized. An app home uses its `widgets` and `sections`; a table slug opens its query page; a process slug opens its wizard; a report slug opens its runner. The unified route resolves name collisions in that order: app, table, process, report. Use unique names across these maps.
| URL | Screen |
|---|---|
| `/` | Opens `/app`, or handles an Auth0 authorization callback. |
| `/login`, `/callback`, `/token` | Sign-in and supported authorization callback routes. |
| `/app` | Instance home page: the `homeAppName` app's dashboard when the instance designates one the user may open (client-side redirect to `/app/{homeAppName}`; the sidebar logo and breadcrumb Home crumb link there and the sidebar has no separate Dashboard entry), otherwise Next's overview. |
| `/app/{appName}` | App home and its ordered widgets/sections. |
| `/app/{tableName}` | Query or key lookup, according to table capability. |
| `/app/{tableName}/create` | Create record. |
| `/app/{tableOrProcessName}/dev` | Table or process developer view; table takes precedence. |
| `/app/{tableName}/{recordId}` | Record view; `/edit`, `/copy`, and `/dev` are subroutes. |
| `/app/{tableName}/{processName}` | Table-scoped process alias; redirects to the process runner with query parameters and a return path to the table. |
| `/app/{tableName}/{reportName}` | Alias for a report whose `tableName` matches; redirects to its runner. |
| `/app/{tableName}/{recordId}/createChild/{childTable}` | Child create form with parent association values. |
| `/app/{tableName}/{recordId}/{processName}` | Resolves a registered process and redirects to its runner with record and return context; execution still checks permission. |
| `/app/{tableName}/key?...` | Unique-key lookup. |
| `/app/{tableName}/savedView/{viewId}` | Server-backed saved query view. |
| `/app/{processName}` | Process run. |
| `/app/{reportName}` | Report run and download. |
| `/app/search?q=...` | Navigation, recent-record, and available record search results. |
| `/app/developer` | Developer information available to permitted users. |
The header search and `/` dialog locate navigation targets and recent records; record search is offered when the backend supports it. RC8 includes the search-focus correction: Tab/Shift+Tab remain within the search dialog, Clear returns focus to the search input, and Escape or backdrop dismissal restores focus to the opener. A pointer-opened phone search restores focus to its actual button in verified Chromium/mobile cases. RC8 tablet WebKit has two open Clear-search focus failures; see the release notes for remaining browser issues. RC7 does not include this correction. The command palette opens with `.` or Cmd/Ctrl+K and has a header button; `?` opens keyboard help. Keyboard shortcuts on query and record pages are listed in the [feature matrix](../acceptance/feature-matrix.md). The sidebar has a phone drawer, nested app groups, banners, and a user menu. Branding can add a company footer and a themed bar above the shell. `?helpHelp` reveals help-slot keys for content authors.
Material's nested `/{app}/{table}/{id}` URLs are not the native Next route. A not-found page now checks the signed-in instance metadata, applies exact or `/*` redirect rules, and resolves nested Material app paths to `/app/...` while preserving query/hash ([`legacy-paths.ts`](../../src/lib/utils/legacy-paths.ts), [`LegacyPathRedirect.tsx`](../../src/components/layout/LegacyPathRedirect.tsx)). Verify a specific old bookmark rather than assuming arbitrary legacy URLs match. For links generated by widgets use [`material-links.ts`](../../src/lib/utils/material-links.ts), which normalizes supported legacy links and preserves a trailing slash for static-export routes.
### Link parameters and launch examples
The table-scoped process/report aliases are resolved before a record GET, so avoid record IDs that collide with those names. Process resolution checks an exact table process, then an instance process; the record-scoped route also accepts a table process name suffix. A matching process takes precedence over a report. Use full process names for unambiguous new links. See [`material-links.ts`](../../src/lib/utils/material-links.ts).
| Link context | Supported parameters and behavior |
|---|---|
| Record view query | `tab` selects a section; `view=tabs` or `view=list` selects the layout. `from` is a safe local return path and `fromLabel` labels it. The default view comes from Preferences. |
| Record view hash | `#audit` opens audits when available; `#/launchProcess=<name>` launches a process; `#<sectionName>` selects a section. `#/createChild=<table>` starts child creation and accepts the presets below. |
| Create/child-create hash | `#/defaultValues=<encoded JSON object>/disabledFields=<encoded JSON array>` seeds and locks fields. `disabledFields` also accepts a JSON object whose truthy entries name locked fields. These are UI presets; backend validation and authorization still apply. |
| Process query | `recordsParam=recordIds&recordIds=1,2` selects IDs; `recordsParam=filterJSON&filterJSON=<encoded QQueryFilter>` selects by filter. The legacy selector `queryFilter` uses the same `filterJSON` value. |
| Process context | `defaultProcessValues=<encoded JSON object>` presets inputs; `returnTo=<encoded local path>` supplies the return destination. `tableName` supplies launch context only for a process without its own table and only when that table exists in metadata. |
| Dashboard hash | `#<widgetName>` targets a widget anchor. An embedded `process` widget also reads the page's `recordIds` query parameter. |
Build encoded links instead of concatenating raw JSON. For example, where metadata declares the supplied process/table names:
```ts
const params = new URLSearchParams({
recordsParam: 'recordIds',
recordIds: selectedIds.join(','),
defaultProcessValues: JSON.stringify({ sendNotification: true }),
returnTo: `/app/${encodeURIComponent(tableName)}`,
})
const processHref = `/app/${encodeURIComponent(processName)}/?${params}`
const createHref = `/app/${encodeURIComponent(tableName)}/create/#/defaultValues=${encodeURIComponent(JSON.stringify(defaultValues))}/disabledFields=${encodeURIComponent(JSON.stringify(disabledFieldNames))}`
```
Only preset fields actually declared by the application's metadata. Record/form hash decoding is implemented by [`formPresetsFromHash`](../../src/lib/utils/material-links.ts); process query decoding lives in the [slug page](../../src/app/%28dashboard%29/app/%5Bslug%5D/page.tsx). Test a generated link on the static-export host and after refresh.
## 5. Tables, queries, records, and forms
A failed query displays its backend error with Retry and Dismiss controls. Retry repeats the request; Dismiss closes the panel without changing the filter. Correct or reset an invalid filter to load records again. Query and pivot error text use theme-derived shades for readability; the top error notification also uses the configured error palette.
Tables are described by [`QTableMetaData`](../../src/types/metadata.ts): `primaryKeyField`, `fields`, `sections`, `associations`, `exposedJoins`, capabilities, and permission flags. A field supplies `name`, `label`, `type`, `isRequired`, `isEditable`, `isHeavy`, `isHidden`, and `adornments`; possible values, bounds, formats, and help are optional. The supported primitive field types are `STRING`, `INTEGER`, `LONG`, `DECIMAL`, `BOOLEAN`, `DATE`, `TIME`, `DATE_TIME`, `TEXT`, `HTML`, `PASSWORD`, and `BLOB`. [`DynamicFormField`](../../src/components/forms/DynamicFormField.tsx) chooses editors, while [`zod-from-metadata.ts`](../../src/lib/utils/zod-from-metadata.ts) builds validation. Configure fields in QQQ metadata, not in a per-table React component.
Table dashboard settings are read from `supplementalMetaData.materialDashboard` in the v1 response, with `supplementalTableMetaData.materialDashboard` accepted as a legacy fallback. The v1 object takes precedence when both are present. These settings include `defaultQuickFilterFieldNames`, `gotoFieldNames`, `showRecordSidebar`, and `recordViewActionsPlacement`. Quick-filter defaults use the configured nonempty field list, otherwise T1 section fields; unresolved names and duplicates are removed. Query-selectable `virtualFields` also become columns, but only virtual fields with `isQueryCriteria` can be filtered or sorted.
For a fixed choice list, put `inlinePossibleValueSource` on the QQQ field with `type: ENUM`, an `idType` matching the field, and ordered `enumValues` containing `id` and `label`. Do not also set `possibleValueSourceName`: QQQ rejects both sources on one field. Next filters inline labels by prefix without a possible-values request and stores the selected `id`. A `CHIP` field adornment with keys such as `color.HIGH` and `icon.HIGH` styles both record values and form options. The v1 table metadata response must include the inline source; [REC-062](../acceptance/feature-matrix.md) verifies its rendering, persistence, and reload against the sample server.
The query page supports basic quick-filter chips and an advanced nested filter builder, quick search, sort, column visibility/order/width/pinning, density, pagination, CSV export, row selection, bulk process launch, column statistics, and table variants where declared. On desktop, Filter opens a panel containing the Basic/Advanced switch, sort controls, quick-filter chips, and advanced builder. On phones, Filter opens a sheet containing the same mode, sort, and quick-filter controls; Advanced shows the nested builder inside that sheet. Opening Filter preserves the selected mode. Advanced mode uses a single editable condition list with per-row removal and one confirmed Clear action in the footer. Complex criteria remain in Advanced mode; the basic chips expose metadata-selected fields and let the user add more. The Columns popup supports keyboard entry, search, reordering and focus return on close; embedded report column editors keep the surrounding editor focus. Query columns start at 150 px unless a `SIZE` adornment or a saved/resized width supplies another value. `TABLE_QUERY`, `TABLE_GET`, `TABLE_COUNT`, `TABLE_INSERT`, `TABLE_UPDATE`, `TABLE_DELETE`, `TABLE_EXPORT`, and `QUERY_STATS` gate operations alongside permissions. For tables with `usesVariants`, choose a backend-provided variant before querying; it is stored under `qqq.tableVariant.<table>` and sent to query, process and record view/edit/copy GET requests. Backend field-download URLs carry the same variant; inline BLOB data comes from the variant-specific record response. [REC-063](../acceptance/feature-matrix.md) verifies view/edit/copy against duplicate record IDs in two variants. URL parameters `page`, `pageSize`, `filter`, and `q` carry shareable query state ([`use-record-query.ts`](../../src/lib/hooks/use-record-query.ts)). Saved views have backend persistence, quick-view shortcuts and counts, modified-state comparison, and their own routes when the backend capabilities exist ([`use-saved-views.ts`](../../src/lib/hooks/use-saved-views.ts)). Browser local storage holds personal display preferences and last view state, not authoritative records or permissions.
Record pages render sections, field values, associated records, widgets, metadata-defined actions, help, and audit history. `QTableSection.alternatives.RECORD_VIEW` replaces the base section for this screen. Visible sections appear in metadata order; fields, sections and widget cards use `gridColumns` as twelfths on large screens and stack on narrow screens. When widths are not configured, Overview uses half-width cards on large screens; a selected section uses a full-width card with two field columns from the small breakpoint, stacking fields on phones. Explicit field, section, or widget widths take precedence over those presentation defaults. A collapsible section uses `collapsible.isCollapsible` and `initiallyOpen`, then remembers the choice under `qqq.recordView.collapsibleSectionOpenStates.<table>.<section>`; a section's rule overrides its widget's rule. The sticky desktop section sidebar is off by default; only `showRecordSidebar: true` in the table dashboard settings enables it. `recordViewActionsPlacement` can be set on the table or `supplementalInstanceMetaData.materialDashboard`, with the instance winning; `INLINE_WITH_PAGE_TITLE` places desktop actions beside the local record title, while the default `IN_IDENTITY_SECTION` keeps them in the identity controls. The identity card omits the primary-key T1 field and T1 values already contained in the record label; display values take precedence over raw values for that comparison. Desktop Copy and Delete buttons are omitted when the Actions menu already contains the corresponding enabled action, including in a submenu. An absent or disabled menu action preserves its available direct button. The Audit action reads through `GetAuditsForRecord` when permitted, otherwise through the `audit` table; its dialog groups entries by the viewer's local date, supports oldest/newest sort saved under `audit.sortDirection`, and reports when the 1,000-detail cap truncates results. Create/edit/copy forms use a centered container capped at `max-w-4xl` and follow field metadata, section layout, adjusters, possible-value choices, and server validation; `FILE_UPLOAD` width `full` spans the form grid, while `half` uses the default half width. Copy defaults to Base Copy, which duplicates editable fields; choose Full Copy to edit and submit loaded named associations with the new record. A widget section can edit association rows or form values through `WidgetFormContext` when its type and defaults allow it. Process launch actions can receive selected record IDs or a filter. `gotoFieldNames` in Material supplemental table metadata enables a Go To dialog for the primary key or configured unique keys. Developer views are capability and permission dependent. The record developer view (`/app/{table}/{id}/dev`) keeps the record screen's header and actions and adds a sticky section navigation over fields, API access (v1 routes and application APIs from their OpenAPI specs, filtered by the user's table permissions, with curl, fetch and Java examples that only use placeholder credentials), the panels returned by `GET /qqq/v1/table/{table}/{pk}/developer/panels` (rendered generically from their blocks: KEY_VALUES, TEXT, CODE, TABLE, LINKS and ACTIONS; app-relative or http(s) links only), relations, recent audits, associated scripts, raw JSON and a searchable table-metadata explorer. Modules and qbits add panels by registering a `DeveloperPanelProviderInterface` in QQQ; no frontend change is needed. Consult [record and query acceptance rows](../acceptance/feature-matrix.md) for exact supported cases; do not infer coverage from the existence of a component alone.
Query changes replace the current URL immediately, preserving unrelated parameters and the hash. Reload and browser back/forward restore the visible filter, sort, search and pagination state; changing those controls does not add a separate history entry.
Record section tabs support Left/Right, Home and End to move focus. Enter or Space opens the focused section; Tab moves into the named content panel. Moving focus alone does not load section widgets. Phone sections use accordion buttons with Enter/Space activation. The selected tab remains reflected in the URL.
Query header labels can wrap to two lines when their text and sort indicator do not fit, including with wider system or configured fonts. Configured column widths and 44px touch menu targets remain effective. Active sorts display their direction arrow; unsorted headings remain labeled sort buttons. Relative-date preset menus scroll within the space available beside their trigger.
Selection and display-density menus support arrow-key navigation, Enter to choose, and Escape to close and return focus to the trigger. They stay within the viewport on phone and tablet. Density choices expose radio-menu semantics; the selected option uses the theme primary/foreground color pair.
Record action menus come from `QTableMetaData.menus`. The first `VIEW_SCREEN_ACTIONS` menu replaces the default Actions menu; `VIEW_SCREEN_ADDITIONAL` menus add ordered menu buttons. Item types are `BUILT_IN`, `RUN_PROCESS`, `DOWNLOAD_FILE`, `SUB_MENU`, `SUB_LIST`, and `DIVIDER`. Their `values` supply `option`, `processName`, `fieldName`, or nested `items`, respectively. Built-in options are `NEW`, `COPY`, `EDIT`, `DELETE`, `DEVELOPER_MODE`, `AUDIT`, `THIS_TABLE_PROCESS_LIST`, and `ALL_TABLES_PROCESS_LIST`; permissions filter available operations. See [`record-menu-utils.ts`](../../src/lib/utils/record-menu-utils.ts) for resolution and process deduplication.
For a joined field in a record section, list its qualified name (`person.firstName`) in `QTableSection.fieldNames` and expose a readable join through `QTableMetaData.exposedJoins`. Next asks for that join on the record GET and uses the joined table's field metadata to render the qualified response value. Join paths through tables without read permission are omitted. [REC-064](../acceptance/feature-matrix.md) checks the real sample server in five browser profiles.
## 6. Processes and reports
`QProcessMetaData` declares the process name, label, table association, permission, limits on input records, and ordered `frontendSteps`. The wizard initializes a run, submits each step, polls asynchronous jobs, handles validation results, and cancels or retries as appropriate ([`ProcessRun.tsx`](../../src/components/process/ProcessRun.tsx), [`processes.ts`](../../src/lib/api/processes.ts)). It preserves return navigation to its caller. `QFrontendComponent.type` selects a process component. The currently registered types are:
| Type | Purpose |
|---|---|
| `HELP_TEXT`, `HTML` | Guidance and sanitized markup. |
| `EDIT_FORM`, `VIEW_FORM` | Metadata-driven writable and read-only fields. |
| `RECORD_LIST`, `PROCESS_SUMMARY_RESULTS` | Input/output records and completion summary. |
| `BULK_EDIT_FORM` | Changes applied to selected records. |
| `BULK_LOAD_FILE_MAPPING_FORM`, `BULK_LOAD_VALUE_MAPPING_FORM`, `BULK_LOAD_PROFILE_FORM` | File preview; flat/tall/wide layout and header-row choices; field-to-column/default mappings; per-value translations; saved load/edit profiles. |
| `VALIDATION_REVIEW_SCREEN` | Review validation findings before commit. |
| `DOWNLOAD_FORM` | Download parameters and result. |
| `GOOGLE_DRIVE_SELECT_FOLDER` | Selects one folder from a shared drive using Google OAuth. Requires `GOOGLE_APP_CLIENT_ID` and `GOOGLE_APP_API_KEY` in instance environment values. Submits `googleDriveAccessToken`, `googleDriveFolderId`, and `googleDriveFolderName`; cancellation clears them. |
| `WIDGET` | Embedded connected widget. |
Do not invent `values` keys for these components: their shapes vary by backend process implementation. Start with the QQQ producer and a [real fixture](../../tests/acceptance/fixture), then verify the versioned response and end-to-end step submission. A `WIDGET` step uses the named widget's full dashboard controls; an editable `childRecordList` contributes `frontendRecords`, while an editable `rowBuilder` writes its configured output field. Ad hoc composite blocks share the dashboard block renderer, including modal control codes, typed inputs and layouts. A dashboard widget of type `process` embeds the wizard without its stepper or Cancel action; Return starts a fresh run in place. For bulk load, test duplicate headers, required fields, saved profiles, value mappings, validation review, and the committed records. Reports appear in `QInstance.reports`. [`ReportRun.tsx`](../../src/components/reports/ReportRun.tsx) offers CSV, XLSX, and JSON, handles required inputs and asynchronous process-backed reports, and provides a download. A report without a backing process uses the versioned streaming report route. Saved and scheduled report behavior is covered by the report rows of the acceptance matrix.
For bulk load, the mapping screen derives required fields, child associations, layout and suggested mappings from the backend's table structure and process values. Its Add Fields menu searches the main and child-table groups; WIDE layouts may repeat a child field. A mapped field can read a file column or a typed default, including possible values, dates and booleans. Header-row changes remap by name and warn about duplicates. File and value mapping show the current profile in a bordered Saved Bulk Load Profiles section; the final review shows that status in its profile summary. Saved bulk-load profiles are grouped by owner; owners can save, rename and delete, while shared profiles can be selected but not modified. Open the profile menu with Enter or Arrow Down; use arrow keys or Home/End to move, Enter to choose, and Escape to close. Long lists scroll within the viewport. New, Save As and Rename open an inline Profile Name field inside the profile section. Save Profile or Enter saves the name without advancing the process; Cancel or Escape closes it. Duplicate-name errors stay beside the editor. Closing returns focus to the opening control, or the profile-menu button if that control was replaced after saving. Updates and deletion retain their confirmations. New, Empty Mapping, Suggested Mapping and Reset All Changes affect only the draft until the process step is submitted. The validation preview can show the associated child grids declared by `previewRecordAssociated*` values.
Report output formats come from the backend's `reportFormat` possible values unless a process screen declares its own `reportFormat` field. Report input screens use the same dynamic form renderer as other processes, so declared possible values, Booleans, dates and validation rules apply. Intermediate report process screens render before the download; the completed file can be a server path or storage reference.
Standard report processes with an input form followed by a download use the original compact input card and **Generate Report** action. Cancel remains available beside it. Ordinary fields stack at a 384px maximum width; explicitly configured grid widths and rich editors keep their layout. These inputs retain the shared dynamic form, required validation, possible values and typed submission. Longer or custom report processes retain their step headings and navigation.
## 7. Widgets: placement, configuration, and data
Each app dashboard lists widget **names** in order. The matching `QWidgetMetaData` supplies `name`, `label`, optional `type`, and `hasPermission`. The UI posts to `/qqq/v1/widget/{name}` with current dropdown and record parameters ([`ConnectedWidget.tsx`](../../src/components/widgets/ConnectedWidget.tsx), [`widgets.ts`](../../src/lib/api/widgets.ts)). The widget response contains its data and sometimes its type. [`WidgetRenderer.tsx`](../../src/components/widgets/WidgetRenderer.tsx) uses `metadata.type` first, falling back to `payload.type`; it contains errors and unknown types within the widget.
A valid TypeScript metadata example, suitable for an instance metadata fixture (the backend should produce the equivalent JSON), is:
```ts
import type { QWidgetMetaData } from '@/types'
const ordersToday = {
name: 'ordersToday',
label: 'Orders today',
type: 'statistics',
hasPermission: true,
gridColumns: 4,
isCard: true,
showReloadButton: true,
showExportButton: true,
tooltip: 'Orders created since midnight',
icon: 'inventory_2',
collapsible: { isCollapsible: true, initiallyOpen: true },
dropdowns: [{ name: 'region', label: 'Region', possibleValueSourceName: 'regions', type: 'POSSIBLE_VALUE_SOURCE' }],
storeDropdownSelections: true,
} satisfies QWidgetMetaData
```
`gridColumns` spans 1–12 twelfths on large screens (default 12); smaller screens stack widgets unless explicit breakpoint overrides are supplied in `defaultValues` (see the configuration recipes below). `isCard: false` removes card chrome. `icon` supplies the main header glyph using the Next outline presentation. `collapsible.isCollapsible` enables a header toggle, starts according to `initiallyOpen`, and remembers state under `qqq.widget.collapsibleOpenState.<name>`. `minHeight`, `footerHTML`, help content, `icons.topLeftInsideCard`/`topRightInsideCard`, tooltip, reload, and export customize the frame. Payload `label`, `sublabel`, and `footerHTML` can override or add frame text; a parent payload's `isLabelPageTitle` promotes the label to a page title. HTML is sanitized. For export, provide `csvData` as rows of cells; a table payload may also export `columns` and `rows`. The dropdown controls are declared in metadata **and populated by parallel payload lists** (`dropdownNameList`, `dropdownLabelList`, `dropdownDataList`, `dropdownDefaultValueList`). Controls offer searchable choices or a native date input; metadata can set `width`, `startIconName`, `allowBackAndForth`, `backAndForthInverted`, and `disableClearable`. The selected ID becomes a request parameter; a possible-value source uses its source name. Required selection can delay content, and `storeDropdownSelections` persists choices at `qqq.widgets.dropdownData.<widget>.<param>`. See [WID-040–WID-050](../acceptance/feature-matrix.md) for the tested contract.
Choice dropdowns support search and keyboard selection. A `DATE_PICKER` uses the platform calendar and a Today action; set `allowBackAndForth` for previous/next day buttons. If the `timeframe` dropdown offers `custom`, selecting it opens start and end inputs; after both are entered, the UI sends `custom,<UTC start>,<UTC end>`.
For example, a canonical QQQ statistics response uses `count`, not the demo fixture's `value`:
```json
{ "type": "statistics", "count": 42, "countContext": "open orders", "percentageAmount": 12.5, "percentageLabel": "vs previous week", "increaseIsGood": true }
```
The catalog below lists **every explicit dispatch type** in `WidgetRenderer.tsx`. Payload fields are from the corresponding renderer; they are examples of what the backend emits, not a replacement for the QQQ Java renderer API. When `type` is `chart` or a chart variant, canonical `chartData.labels` and `chartData.datasets` select the QQQ chart path. A payload with `chartType`, `seriesData`, or demo `data` instead uses the legacy/demo chart path.
### Canonical QQQ display widgets
| `type` | Use and important response fields |
|---|---|
| `alert` | Status message: `html`, `alertType`, optional `bulletList` and `hideWidget`. A demo `message` payload uses the demo alert renderer. |
| `divider` | Horizontal separator, optionally `label`; rendered without normal card chrome. |
| `fieldValueList` | Labeled record fields: `fields`, `record.values`, optional `record.displayValues`, prefix icon maps, indentation map. Display values win. |
| `generic` | Common widget frame fields, especially `sublabel` and sanitized `footerHTML`; optional `html` payload. |
| `html` | Sanitized `html` string. A demo payload with `blocks` uses the demo block renderer. |
| `location` | `imageUrl`, `title`, `description`, `location`, `footerText`. |
| `quickSightChart` | Provider embed `url` in an iframe; supply a server-authorized URL and appropriate CSP. Real AWS account coverage is an approved external exclusion. |
| `stepper` | `steps` with labels and optional links/icons, plus zero-based `activeStep` and optional `title`. |
| `usaMap` | `mapMarkerList` of names and latitude/longitude; optional CSS `height`. |
### Canonical QQQ charts, statistics, and tables
| `type` | Use and important response fields |
|---|---|
| `barChart` | Vertical bars from `chartData.labels` and `chartData.datasets`; dataset `data`, colors, and URLs. |
| `horizontalBarChart` | Horizontal bars; supports negative values relative to zero. |
| `stackedBarChart` | Multiple datasets stacked for each label. |
| `lineChart` | Line points from labels and datasets. |
| `smallLineChart` | Compact line presentation with `title` and optional sanitized `description`. |
| `pieChart` | Colored slices and legend; optional `chartSubheaderData`. |
| `chart` | Generic canonical chart, displayed as a bar variant when `chartData` is used; can be inferred from payload type. |
| `statistics` | Headline `count`, optional `countContext`, `percentageAmount`, `increaseIsGood`, and count/percentage links. Demo `value` payload uses the demo statistic renderer. |
| `multiStatistics` | `statisticsGroupData`: groups with `header`, `subheader`, icon, and statistic list. |
| `table` | `columns` (`header`, `accessor`, alignment, optional help/width) and `rows`; typed number, HTML, tooltip, image, composite/block cells; hidden helper columns, expandable subrows, optional `noRowsFoundHTML`, entries-per-page, sticky header and last-row behavior. |
| `multiTable` | `tableDataList` of table payloads, each with its own label, rows and export action. |
### Composite and interactive widgets
| `type` | Use and important response fields |
|---|---|
| `composite` | QQQ block tree: `blocks`/`blockTypeName`, layout, values, links, tooltips and styles. Without those fields, demo `childWidgets` uses `CompositeWidget`. |
| `parentWidget` | Server response `childWidgetNameList`; renders permitted registered children in a 12-column grid or `layoutType: "TABS"`. Children fetch their own data. |
| `process` | Inline wizard using `processMetaData`, optional `defaultValues` and selected record IDs. Server permission still applies. |
| `childRecordList` | Joined child records from `queryOutput.records` and `childFrontendTableMetaData`/`childTableMetaData`; `includeExposedJoinTables` adds readable join columns, and `defaultValuesForNewChildRecords` hides parent-key columns on screen. Optional `viewAllLink`, add-child defaults, row limits, `disableRowClick`, and CSV export via `showExportButton`. On a record form, `defaultValues.manageAssociationName` enables association editing. |
| `customComponent` | Loads a trusted bundle from metadata `defaultValues.componentSourceUrl` and `componentName`, then resolves `window[componentName][componentName]`. The component receives widget metadata/data/record, `qContext`, and `qfmdBridge`; `window.React`/`window.ReactDOM` are exposed. Check CSP and test the actual older bundle before claiming compatibility. |
| `cronUI` | Record view displays the expression, time zone and backend `cronDescription`. Put the widget in a table section and set `defaultValues.includeOnRecordEditScreen: true`, `cronExpressionFieldName`, and `timeZoneFieldName` to edit the named record fields on create/edit forms. The expression editor offers Basic days/hours/minutes, Advanced Quartz input, Clear, live description and validation. Give the time-zone field a possible-value source to render a select. |
| `dynamicForm` | Labeled `fieldList` and `recordOfFieldValues`, including merged JSON field values. Editable in a form/process host when `defaultValues.isEditable` allows it; fields join host validation and submission. In a record form, `mergedDynamicFormValuesIntoFieldName` names the declared JSON table field that stores the values. Put that field in a hidden section, so QQQ still returns it to the widget renderer; a field marked `isHidden` is omitted from normal record reads. The renderer should return current values in `recordOfFieldValues` for reloads. |
| `dataBagViewer` | Data bag/version browser with raw/preview tabs and version editor when `storeDataBagVersion` is available; uses `queryParams.id` or record ID and the built-in `dataBag` and `dataBagVersion` tables. |
| `pivotTableSetup` | Record view summarizes saved `pivotTableJson` (`rows`, `columns`, `values`). In a form, a switch and modal edit its definition after table/`columnsJson` fields are available. OK changes the form draft; Save persists it. Turning the switch off clears the stored definition when the host form is saved. |
| `filterAndColumnsSetup` | Record view summarizes saved filter, sort and columns JSON and shows a paged, sortable, resizable preview unless `hidePreview` is set. A host form offers Edit, + Add Filters and + Add Columns controls, then opens an editor for nested filters, multiple sort levels, visible/ordered columns and a draft preview; `OK` writes JSON into the host form, `Cancel` discards the draft. When payload `allowVariables` is true, a criterion value can become a `FilterVariableExpression`; its `${VARIABLE}` chip is saved in `queryFilterJson`, and preview and the opened query page wait for a value. Saving the host record or advancing a process persists the JSON. A `WIDGET` field adornment can host the setup in a process or row-builder field; its `widgetName` names the widget and only that field receives its output. `overrideIsEditable: false` keeps it read-only. |
| `rowBuilder` | Payload `records`, with columns from `defaultValues.frontendFields` or `fields`. Record view/dashboard is read-only; a form/process host can add, edit, delete and reorder rows when the metadata's edit flags allow it. |
| `scriptViewer` | Script revisions, files, logs, test runner, docs and editor; uses `queryParams.id` or record ID and the built-in script tables. |
| `ESB_OVERVIEW` | ESB destination/trigger overview; fetches its own ESB API data and does not use the ordinary widget payload for content. Requires the ESB backend module. |
For `scriptViewer`, pass a script ID through `queryParams.id` or host it on that script's record. The built-in script and script-type tables supply revisions, schema-ordered files, docs and logs. Register `storeScriptRevision` to expose editing and `testScript` to expose the test panel. The editor supports per-file syntax coloring, split panes, API name/version selection when those fields exist, and a guard for unsaved changes. Local suggestions include QQQ API/logger helpers, language keywords and identifiers in the current file. Type a prefix or press Ctrl+Space, use arrows to choose and Enter/Tab to insert, or click/tap a suggestion. Escape dismisses the list; Escape followed by Tab leaves the editor. Completion supports native undo/redo; the browser controls how consecutive edits are grouped. Suggestions do not validate or execute code.
QQQ composite block types rendered by [`QqqBlocks.tsx`](../../src/components/widgets/blocks/QqqBlocks.tsx) include `TEXT`, `BIG_NUMBER`, `UP_OR_DOWN_NUMBER`, `NUMBER_ICON_BADGE`, `ICON`, `TABLE_SUB_ROW_DETAIL_ROW`, `PROGRESS_BAR`, `DIVIDER`, `IMAGE`, `AUDIO`, `INPUT_FIELD`, and `BUTTON`. Supported layouts include `FLEX_COLUMN`, `FLEX_ROW`, `FLEX_ROW_WRAPPED`, `FLEX_ROW_SPACE_BETWEEN`, `FLEX_ROW_CENTER`, `TABLE_SUB_ROW_DETAILS`, and `BADGES_WRAPPER`. Buttons that submit process actions and standalone input submissions need a host callback. Modal control codes also work locally on dashboard composites. Unknown block types show a contained warning. See [WID-057–WID-059](../acceptance/feature-matrix.md).
`INPUT_FIELD` blocks read `values.fieldMetaData`, optional `value`, `placeholder`, `autoFocus`, and `submitOnEnter`. Ordinary scalar inputs retain the compact block controls; the backend's neutral `%s` display format does not change their presentation. Possible-value sources, explicit formatting/behaviors, and `CODE_EDITOR`, `FILE_UPLOAD`, or `WIDGET` adornments use the shared Next field editors. Hidden fields are omitted and read-only fields cannot be changed. Standalone metadata editors retain a 14rem minimum width so short choice labels do not collapse the control. For `STRING`, `TEXT`, and explicitly configured `PASSWORD` blocks, `fieldMetaData.behaviors` accepts `TO_UPPER_CASE` or `TO_LOWER_CASE`: text changes case while typing and the caret remains at the edit position, including conversions that expand characters such as `ß` to `SS`. Selection boundaries and direction are preserved. For single-line inputs, process Enter and Submit both send the transformed value; standalone Enter requires `submitOnEnter` and a host callback. Multiline `TEXT` fields preserve newlines: Enter edits the text even when `submitOnEnter` is true, and the process Submit button sends the transformed value. Password inputs remain masked by default; showing or hiding the value does not change it. Passwords without a case behavior retain their entered case. The behavior names come from backend metadata, so do not apply a second conversion in an action callback.
Numeric block controls in RC4 blur on mouse-wheel scrolling to preserve the entered value, matching shared numeric form fields. Clicking back into the control allows normal typing and arrow-key edits. Text controls keep focus when scrolling. This correction is included in published RC4 (`3c409f9`); RC3 does not contain it.
In a process, block editors use the screen's form, validation and submission. A BLOB or FILE_UPLOAD field sends the selected `File` as multipart content, and required files block submission until chosen. Named choices on block-only fields search their standalone possible-value source; declared process fields use the process source. WIDGET fields receive surrounding process values (for example `tableName`) merged with current form values; editing writes only the adorned field. Scalar Enter submits only when `submitOnEnter` is true; `->code` submits an action code. Multiline code, choice searches and file controls own their keyboard interactions. Standalone blocks keep their draft locally and send scalar Enter values through their action callback. PRC-062 and WID-074 cover the process and standalone fixture paths. Enter trims text before form validation and submission; required whitespace cannot advance. Process initial values come from the process state, while standalone values come from the block. Configured autofocus takes precedence over heading focus on subsequent process screens, and the first blur does not display a premature required error. PRC-063 verifies this configuration across screen transitions and checks the actual stored text.
Ordinary record edits omit unchanged timestamps. Base Copy and both child-row editor hosts resubmit complete values, so they retain each unchanged timestamp's source instant and fractional seconds. REC-065 verifies copy persistence during the repeated daylight-saving hour; component regressions cover association and in-process child rows.
`DATE_TIME` process fields and input blocks display instants in the browser's local timezone, retain seconds, and send edited times as zoned UTC values. Unchanged values retain their original instant and subsecond precision, including a repeated daylight-saving hour. `DATE` and `TIME` remain calendar and wall-clock values. PRC-064 checks seeded and edited values across plain blocks, metadata editors and regular process forms against backend SQL; WID-075 checks standalone display.
The filter/column editor reads the payload's `tableName` or the form's `tableName`, then loads that table's metadata. It writes to `queryFilterJson` and `columnsJson` by default; `filterFieldName` and `columnFieldName`/`columnsFieldName` override those keys. `hidePreview` controls the read-only summary preview. Starting in RC5, the editing dialog retains its query grid even when that summary is hidden, matching Material behavior; RC4 incorrectly hides both. Payload flags `hideColumns`, `hideSortBy`, `allowVariables`, `isApiVersioned`, `apiName`/`apiPath`/`apiVersion`, `filterDefaultFieldNames`, `filterDefaultFieldNameSourceFieldNames`, `omitExposedJoins`, `editButtonLabel`, and `modalHeader` affect the editor. It waits for a selected table and required default-field values. The preview supports server-side paging, sorting, column resizing, Refresh, column configuration, and column-menu actions (filter, hide, pin, copy page/full-query values, and statistics when permitted). The editor also offers density, Basic quick filters, Advanced conditions, and selection of an existing saved view. Selecting a view imports its filter and columns into the draft; it does not modify that saved view. Column widths and pins survive host Save. Unresolved variables suppress the preview; assign variables in Advanced mode. Interactions in a read-only record preview are temporary and never update the saved report. Starting in RC4, `omitExposedJoins` limits new filter/column choices in both the read-only preview and editor. Existing saved joined columns, criteria and sorts remain usable, and reordering available columns preserves omitted columns in place. Opening the editor and pressing OK preserves those saved fields; Basic/Advanced changes and host Save/reopen retain their values and widths. This is a choice restriction, not a backend permission boundary; existing joined data still uses the normal query permissions. WID-073 checks the configured and unconfigured cases against SQL, including local edits and reload. `OK` updates the host form's values, so the record must still be saved to persist. [WID-030 and WID-070](../acceptance/feature-matrix.md) cover the summary, preview, Cancel, OK, and persisted host save across five browser profiles.
In the report setup dialog, Left/Right Arrow switches between Filters and sort and Columns; Home/End selects the first/last available tab. Tab enters the selected panel's controls. Switching tabs preserves the draft, and metadata that hides Columns leaves only the filter tab. The tab controls expose `filter-editor-tab-<widget>-filters` and `filter-editor-tab-<widget>-columns` CSS hooks.
Selecting a saved view imports it into the report draft. **Reset Changes** reapplies that view; **Reset to New View** clears filters and custom quick fields, restores default columns, and sorts by the primary key descending when present. Both discard an open quick-filter edit. These actions leave the saved view unchanged. OK applies the draft to the host form; saving the form persists it. Cancel discards the dialog draft.
Complete conditions, including nested groups, mark the corresponding preview column header. Use that indicator to open the existing filter. It adds no condition; editing a read-only preview stays local, and editor changes remain a draft until OK and host Save. Very narrow columns retain the shared grid’s compact header treatment.
When a preview table declares `usesVariants`, select its backend variant before querying. The choice uses the same per-table browser storage as Record Query (`qqq.tableVariant.<table>`). Switching variants starts a fresh preview page; query/count, full-column copy and statistics use the selection. It does not alter the saved report definition. Application-API query/count require the backend fix in [QQQ PR #913](https://github.com/QRun-IO/qqq/pull/913), merged to QQQ develop as `cba758507dcc54cf282a6638ab50a7d3a1904c7c`; verification used the middleware JAR built from its reviewed source. This does not require or announce a QQQ 4.1 release.
### Demo and compatibility dispatch labels
The remaining dispatch labels mainly support local mock fixtures. They are implemented UI paths, but are not a promise that the production QQQ backend emits those shapes. Check [`src/mocks/fixtures/widgets`](../../src/mocks/fixtures/widgets) for demo payloads and the real widget fixtures and matrix for QQQ-backed examples.
| `type` | Use and important response fields |
|---|---|
| `recordGrid` | Demo record grid with `tableName`, `records`, and `columns` or full `fields`; optionally `totalCount`. |
| `quickLinks` | Demo list of labeled navigation links; inspect the fixture's `links` payload before using it. |
| `processSummary` | Demo process status/summary cards; inspect the fixture's status and counts before using it. |
| `block` | Chooses QQQ composite blocks when the payload has `blockTypeName` or `blocks`; otherwise falls back to `BlockWidget`. A demo `blocks` list therefore enters the QQQ composite branch. |
| `parent` | Demo `CompositeWidget` alias using `childWidgets`; canonical QQQ parent widgets use `parentWidget` and `childWidgetNameList`. |
Canonical chart types can also fall through to the demo chart components when the response is not a QQQ `chartData` payload.
### Widget configuration recipes
These examples are payload fragments emitted by an application's backend renderer, with illustrative names and values. Pair them with matching `QWidgetMetaData`; they are not standalone Java producer definitions. Every dispatch label is listed above; the details below cover the shared and specialized configuration that is easy to miss when copying a basic example.
**Responsive placement.** Metadata `defaultValues` accepts `gridCols:sizeClass:xs`, `sm`, `md`, `lg`, `xl`, and `xxl`. Their minimum viewport widths are 0, 576, 768, 992, 1200, and 1400 px. Values are column counts from 1 to 12; numeric strings are accepted. Once any override is set, `xs` defaults to 12 and `xxl` defaults to `gridColumns` or 12. Other declared spans apply until the next declared breakpoint. This example gives a half-width widget from 768 px upward, including 1400 px:
```json
{"gridColumns":6,"defaultValues":{"gridCols:sizeClass:xs":12,"gridCols:sizeClass:md":6}}
```
**Readable widths (Next UI).** Without per-breakpoint overrides, a widget's span follows the width of the dashboard grid itself (CSS container queries, so an open sidebar counts), not the viewport. Two `defaultValues` hints tune it, and neither needs a QQQ change:
- `gridMinWidth`: the narrowest the widget may be, in rem (a number, or `"28rem"` / `"448px"`). The widget shares a row only when it, and a neighbour of the same minimum, each get at least that much; otherwise it takes the full row. Values snap up to the tiers 16, 20, 24, 28, 32, 40, 48 or 64 rem; anything wider always takes the full row. Two `gridColumns: 6` widgets with `gridMinWidth: 28` pair from a 57.5rem (920 px) grid and go to thirds (`gridColumns: 4`) from 86.5rem.
- `gridProfile`: `widgets` (charts, statistics: half from a 40rem grid), `cards` or `table` (tables: half from 66rem), or `fields` (small tiles: half from 30rem). The default is `cards` for table-type widgets and `widgets` for the rest.
`gridCols:sizeClass:*` wins over `gridMinWidth`, which wins over `gridProfile`.
```json
{"type":"table","gridColumns":6,"defaultValues":{"gridMinWidth":28}}
```
Table widgets (`table`, `multiTable`) stop growing at 96rem (1536 px) so columns stay readable on ultra-wide screens; set `defaultValues.tableMaxWidth` to another width (rem, or a CSS length in `rem`, `px`, `ch`, `em` or `%`) or to `"none"`. Where a table widget is narrower than 30rem, or than its declared column widths need (6rem per `fr` or unsized column plus the fixed widths), each row renders as a stacked card of `label: value` lines instead of a table that scrolls sideways.
To cap a whole dashboard page on ultra-wide screens, give the app a supplemental app metadata of type `dashboardLayout` (a backend `QSupplementalAppMetaData` subclass whose `getType()` returns `dashboardLayout` and that is included in frontend metadata) with `maxContentWidth` (rem, or a CSS length). The page then centres at that width: `{"supplementalAppMetaData":{"dashboardLayout":{"maxContentWidth":160}}}`.
See [`widgetColumnClasses`](../../src/components/widgets/widget-utils.ts). Parent grids use the same sizing. A `parentWidget` payload such as `{"childWidgetNameList":["totals","trend"],"layoutType":"TABS"}` resolves those names in the instance widget registry. Children inherit parent request parameters and dropdown selections and fetch their own data; tab selection is remembered locally. A child omitted from permitted metadata cannot be configured into existence by listing its name.
**Dropdown data.** Metadata and response lists are paired by position; keep their lengths and order aligned. For the `region` dropdown example above, a response fragment is:
```json
{"dropdownNameList":["regions"],"dropdownLabelList":["Region"],"dropdownDataList":[[{"id":"east","label":"East"},{"id":"west","label":"West"}]],"dropdownDefaultValueList":["east"]}
```
The request parameter is the payload's `dropdownNameList` entry (`regions` here). The backend convention uses the possible-value source name for choice controls and the declared name for date controls. A valid stored choice wins over the response default; unavailable stored choices are cleared. `labelForNullValue` adds an explicit empty option only when the payload has no default. `disableClearable` removes the clear button. To gate the body, return `dropdownNeedsSelectedText` until the required choice is supplied. Metadata `isRequired` and `defaultValue` alone are not consumed as client-side gating/default rules by [`ConnectedWidget`](../../src/components/widgets/ConnectedWidget.tsx). A date control sends local `YYYY-MM-DD` text; the special custom timeframe sends the UTC range described above.
**Charts and statistics.** All seven chart dispatch labels use the canonical shape below. `backgroundColors` and `urls` are arrays aligned with data points; `color` supplies the series color and `backgroundColor` is the stacked-series fallback. Top-level `chartData.urls` supplies category links. `height` is numeric; `isCurrency` or `isYAxisCurrency` enables the renderer's currency formatting.
```json
{"type":"barChart","height":280,"isCurrency":true,"chartData":{"labels":["Open","Closed"],"datasets":[{"label":"Value","data":[1200,800],"backgroundColors":["info","success"],"urls":["/app/orders/","/app/archive/"]}]}}
```
`chartSubheaderData` accepts `mainNumber`, `vsPreviousPercent`, `vsPreviousNumber`, `isUpVsPrevious`, `isGoodVsPrevious`, `vsDescription`, `mainNumberUrl`, and `previousNumberUrl`; use it for the optional comparison summary. See [`QqqChartPayload`](../../src/components/widgets/widget-types.ts). Statistics adds `countFontSize`, `countURL`, `percentageURL`, and `isCurrency` to the basic example; numeric currency counts format as USD, while string counts pass through. A zero or absent percentage hides the change row. Multi-statistics uses this nested shape:
```json
{"type":"multiStatistics","statisticsGroupData":[{"header":"Orders","subheader":"Today","icon":"inventory_2","iconColor":"#0062ff","statisticList":[{"label":"Open","value":7,"url":"/app/orders/"}]}]}
```
**Tables.** Each `table` payload (and each member of `multiTable.tableDataList`) uses the same configuration. `rowsPerPage` defaults to 10; `hidePaginationDropdown` hides its size selector, `fixedHeight` constrains the scroll area, and `fixedStickyLastRow` fixes the final row. `linkText`/`linkURL` add the table link. Columns accept `type`, `header`, `accessor`, `width`, `align`, and `verticalAlign`; widths can be fixed CSS lengths or `fr` shares.
```json
{"type":"table","rowsPerPage":5,"columns":[{"header":"Name","accessor":"name","width":"2fr"},{"header":"Count","accessor":"count","width":"1fr"},{"accessor":"tooltip","type":"hidden"}],"rows":[{"name":"Open","count":7,"tooltip":"Current backlog","subRows":[{"name":"Priority","count":2}]}]}
```
Cell `type` is `default`, `html`, `htmlAndTooltip`, `composite`, `block`, `image`, or `hidden`. An `htmlAndTooltip` cell reads the row's `tooltip`; `image` reads `imageUrl`, `imageLabel`, `imageTotal`, and `imageTotalType`. Composite/block cells read a block payload at the column accessor. Hidden helper columns do not display but remain available to cells and export. `subRows` provides expandable rows. See [`QqqTablePayload`](../../src/components/widgets/QqqTableWidget.tsx) and [`TableWidgetColumn`](../../src/components/widgets/table-widget-utils.ts).
**Display and editor details.** These keys supplement each catalog row; they are read by the linked implementations.
| Widget | Additional configuration |
|---|---|
| `stepper` | Each step accepts `label`, `linkText`, `linkURL`, `iconOverride`, and `colorOverride`; only the active step shows its action link. |
| `fieldValueList` | Use `fieldLabelPrefixIconNames`, `fieldLabelPrefixIconColors`, and `fieldIndentLevels`, keyed by field name. Fields use the shared record formatter, including declared formats/adornments and possible-value record links. |
| `alert` | `alertType` supports ERROR, WARNING, SUCCESS and INFO; absent/unknown values use INFO. `bulletList` contains sanitized HTML strings. Empty `html` renders no alert body. |
| `usaMap` | `mapMarkerList` entries use `name`, `latitude`, and `longitude`; the UI includes the US basemap and an accessible location list. |
| `html`, `generic`, `divider`, `location`, `quickSightChart` | Use the fields in the catalog and shared chrome; these types do not expose host-form editing. QuickSight's optional payload `label` names the iframe. |
| `childRecordList` | `omitFieldNames` and `onlyIncludeFieldNames` control columns. `canAddChildRecord` enables Add; `disabledFieldsForNewChildRecords` locks named defaults, and `defaultValuesForNewChildRecordsFromParentFields` maps child field names to parent field names. `totalRows` supplies the total. A process host uses `isInProcess`, `allowRecordEdit`, and `allowRecordDelete` to control in-memory rows. |
| `rowBuilder` | Metadata defaults include `frontendFields` (fallback `fields`), `associationName`/`outputFieldName`, `orderByFieldName`, `mayReorderRows`, `isForRecordViewAndEditScreen`, `isEditable`, `useModalEditor`, `inlineHeading`, `modalTitle`, and `defaultValuesForNewRowsFromParentRecord`; see the [agent editor guide](./next-ui-agent-guide.md#widget-editors-and-host-forms). |
| `process` | `processMetaData.name` selects the process; payload `defaultValues` presets its inputs. Reloading the widget reinitializes the embedded run. |
| `cronUI`, `dynamicForm`, `pivotTableSetup`, `filterAndColumnsSetup` | These edit host fields through their documented bindings above; changing the widget draft alone does not save a record. Use the shared form context and inspect each editor's validation before introducing a new field binding. |
| `dataBagViewer`, `scriptViewer`, `ESB_OVERVIEW`, `customComponent` | These require their respective backend module/process or trusted bundle described above; ordinary display payloads cannot replace those dependencies. |
| `recordGrid`, `quickLinks`, `processSummary`, `parent`, demo `block` | Use the [demo fixtures](../../src/mocks/fixtures/widgets) and matching renderer for the demo contract. These labels do not imply an equivalent canonical Java renderer. |
**Composite blocks.** `link` and `tooltip` apply to the block; `linkMap` and `tooltipMap` apply to named slots, such as `NUMBER`. Links carry `href` and optional `target`; tooltips carry `title`, `placement`, or nested `blockData`. Help slots can provide fallback tooltip text. `styles` configures the leaf block, while composites also accept `styleOverrides`, sanitized `overlayHtml`, and `overlayStyleOverrides`.
| `blockTypeName` | Values and styles to supply |
|---|---|
| `TEXT` | `values.text`, optional `startIcon`/`endIcon`; `styles.color`, `format`, `size`, `weight`. |
| `NUMBER_ICON_BADGE` | `values.number`, `iconName`. |
| `UP_OR_DOWN_NUMBER` | `values.number`, `context`, `isUp`, `isGood`; `styles.colorOverride`, `isStacked`. |
| `TABLE_SUB_ROW_DETAIL_ROW` | `values.label`, `value`; `styles.labelColor`, `valueColor`. |
| `PROGRESS_BAR` | `values.percent`, `heading`, `value`; `styles.barColor`. |
| `BIG_NUMBER` | `values.heading`, `number`, `context`; `styles.width`, `numberColor`. |
| `DIVIDER` | No values required. |
| `IMAGE` | `values.path`, `alt`; `styles.width`, `height`, `bordered`. |
| `AUDIO` | `values.path`, `showControls`, `autoPlay`; playback still follows browser policy. |
| `ICON` | `values.name`; `styles.color`, `fontSize`. |
| `BUTTON` | `values.label`, `actionCode` or `controlCode`, optional `startIcon`/`endIcon`; `styles.format` is filled, outlined or text. |
| `INPUT_FIELD` | `values.fieldMetaData`, `value`, `placeholder`, `autoFocus`, `submitOnEnter`; host behavior is detailed above. |
| `COMPOSITE` | Nested `blocks`, one of the documented `layout` values, and optional `blockId`/`modalMode`. |
A host evaluates `conditional` as the truthiness of the named host value and interpolates `${name}` in text; it is not a JavaScript expression. Dashboard composites without a process host do not evaluate those conditions. Modal buttons use `showModal:<blockId>`, `hideModal:<blockId>`, or `toggleModal:<blockId>` control codes. A nonempty `modalMode` marks the target composite; a host value with that block ID set to literal `true` opens it initially. This local dashboard example needs no process submission callback:
```json
{"type":"composite","blocks":[{"blockTypeName":"BUTTON","values":{"label":"Details","controlCode":"showModal:details"}},{"blockTypeName":"COMPOSITE","blockId":"details","modalMode":"dialog","blocks":[{"blockTypeName":"TEXT","values":{"text":"Details appear here."}}]}]}
```
See [`QqqComposite`](../../src/components/widgets/blocks/QqqComposite.tsx), [`QqqBlocks`](../../src/components/widgets/blocks/QqqBlocks.tsx), and [`BlockSlot`](../../src/components/widgets/blocks/BlockSlot.tsx). RC6 icon compatibility preserves existing Lucide mappings and adds a locally served glyph for valid unmapped names from the pinned 2,234-name legacy Material Icons inventory. Examples include `3d_rotation`, `account_balance_wallet`, `battery_6_bar`, and `60fps`. Unknown names retain their fallback; this does not include every Material Symbols name or distinct outlined/rounded font variants. Metadata image paths still take precedence. The font and Apache license ship with the static export/JAR; no Google Fonts request is needed at runtime. See the [font provenance](../../public/fonts/material-icons/README.md). This behavior first ships in RC6. The parity ledger also retains open INPUT_FIELD configuration and visual review work.
## 8. Theme, branding, and customization
`QInstance.branding` supplies app/company name, logo/icon, accent colors, banners and optional `gravatarDefault`. A company footer appears when both `companyName` and a safe HTTP(S) `companyUrl` are present. The active application theme is `QInstance.supplementalInstanceMetaData.materialDashboardTheme`, read by [`material-theme.ts`](../../src/lib/theme/material-theme.ts); do not configure `QInstance.theme` and expect these styles to apply. Its optional fields cover palette, fonts, typography, radii, density, logo/icon/favicon, icon style, branded header, app bar, sidebar, table colors, and `customCss`. Invalid theme values are ignored. For example:
```json
{"supplementalInstanceMetaData":{"materialDashboardTheme":{"primaryColor":"#245ca6","fontFamily":"Inter, sans-serif","borderRadiusCard":"12px","brandedHeaderEnabled":true,"brandedHeaderTagline":"Operations","customCss":"[data-qqq-id='sidenav-root'] { border-right: 1px solid #ccc; }"}}}
```
[`apply-branding.ts`](../../src/lib/theme/apply-branding.ts), [`material-theme.ts`](../../src/lib/theme/material-theme.ts), [`tokens.ts`](../../src/lib/theme/tokens.ts), and [`qqq-theme.css`](../../src/styles/qqq-theme.css) define the CSS mapping, including supported Material `--qqq-*` variables. Scope custom CSS to observed `data-qqq-id` attributes, and inspect actual rendering and contrast before migrating a Material stylesheet. A declared application theme sets the app look and keeps light mode active. Without an application theme, the local display-mode preference applies; a fresh profile starts in light mode regardless of the operating system preference. Density is available through theme metadata and local preferences. Open the sidebar user menu → Preferences → Appearance to choose Light or Dark. Changes apply immediately and persist in `localStorage` as `qqq-dark-mode` for this browser profile and origin (scheme, host and port), not the signed-in account; there is no cross-device sync or operating-system mode. Clearing site data removes the preference. When an application theme is active, the choices are disabled with an explanation and the saved preference is retained. Reset to Defaults resets appearance to light along with table and record defaults.
See the [Material CSS hook guide](../CSS-HOOKS.md) for the ID sanitizer, selector patterns, named hooks, and phone/tablet visibility rules.
Analytics is configured by the backend's `QInstance.environmentValues` ([`src/lib/analytics`](../../src/lib/analytics)). It stays off when no analytics values are published. `ANALYTICS_PROVIDERS` can name Google, PostHog, or registered plugins; provider-specific IDs and enable flags are also required. The default privacy behavior omits record IDs, labels and query strings and does not identify users. Only publish `ANALYTICS_INCLUDE_RECORD_DATA=true` or `ANALYTICS_IDENTIFY_USERS=true` after deciding those disclosures are appropriate for the application.
### Theme property reference
All keys below belong to `supplementalInstanceMetaData.materialDashboardTheme`. Unset properties retain the Next defaults. The executable allow-list and value validation are [`THEME_PROPERTY_KINDS`](../../src/lib/theme/material-theme.ts); [`QThemeMetaData`](../../src/types/metadata.ts) defines their types.
| Area | Keys or key pattern |
|---|---|
| Palette | `primaryColor`, `secondaryColor`, `backgroundColor`, `surfaceColor`, `textPrimary`, `textSecondary`, `errorColor`, `warningColor`, `successColor`, `infoColor`. |
| Action color | `preferInfoColorToPrimaryColor` chooses the info color for the controls that honor that option. |
| Fonts | `fontFamily`, `headerFontFamily`, `monoFontFamily`, `fontSizeBase`, `fontWeightLight`, `fontWeightRegular`, `fontWeightMedium`, `fontWeightBold`. |
| Typography | `typography{variant}{property}`, where variant is `H1`–`H6`, `Body1`, `Body2`, `Button`, or `Caption`, and property is `FontSize`, `FontWeight`, `LineHeight`, `LetterSpacing`, or `TextTransform`. Example: `typographyH2FontSize: "1.5rem"`. |
| Radii | `borderRadiusGlobal`, `borderRadiusScale`, and `borderRadius{component}` for `Button`, `Card`, `Chip`, `Dialog`, `OutlinedInput`, `LinearProgress`, `MenuPaper`, `PaperRounded`, `PopoverPaper`, and `Tooltip`. A component radius overrides the global radius; the scale applies when no global radius is set. |
| Density and icons | `density`: `compact`, `normal`, `comfortable`; `iconStyle`: `filled`, `outlined`, `rounded`, `sharp`, `two-tone`. Unsupported icon names can still fall back to the default glyph. |
| Images and CSS | `logoPath`, `iconPath`, `faviconPath`, `customCss`. |
| Branded header | `brandedHeaderEnabled`, `brandedHeaderBackgroundColor`, `brandedHeaderTextColor`, `brandedHeaderLogoPath`, `brandedHeaderLogoAltText`, `brandedHeaderHeight`, `brandedHeaderTagline`. |
| App bar | `appBarBackgroundColor`, `appBarTextColor`. |
| Sidebar | `sidebarBackgroundColor`, `sidebarTextColor`, `sidebarIconColor`, `sidebarSelectedBackgroundColor`, `sidebarSelectedTextColor`, `sidebarHoverBackgroundColor`, `sidebarDividerColor`. |
| Tables | `tableHeaderBackgroundColor`, `tableHeaderTextColor`, `tableRowHoverColor`, `tableRowSelectedColor`, `tableBorderColor`. |
| Borders | `dividerColor`, `borderColor`, `cardBorderColor`. |
Lengths use CSS length strings; colors use supported named, hex, RGB or HSL values. Font weights must be integers from 1 through 1000; radius scale is 0 through 10. Text transforms are `none`, `uppercase`, `lowercase`, or `capitalize`. Invalid values are ignored rather than inserted into CSS. Application CSS and external assets remain trusted configuration, and their actual appearance/CSP must be checked on the host.
### Help content configuration
Fields and sections expose `helpContents`; widgets expose a slot map in `helpContent`. Entries supply `content`, `format` (`TEXT`, `HTML`, or `MARKDOWN`), and optional `roles`. The shared selector prefers the first entry matching the most specific screen role, then an entry without roles. View roles are `VIEW_SCREEN`, `READ_SCREENS`, `ALL_SCREENS`; query substitutes `QUERY_SCREEN`; edit uses `EDIT_SCREEN`, `WRITE_SCREENS`, `ALL_SCREENS`; create/copy substitutes `INSERT_SCREEN`; processes use `PROCESS_SCREEN`, `ALL_SCREENS`.
For example, widget help can use `{"helpContent":{"label":[{"content":"Current totals","format":"TEXT"}],"columnHeader=count":[{"content":"Matching records","format":"TEXT"}]}}`. Table widget headers use `columnHeader=<accessor>` slots; composite slots use `<blockId>,<slot>` keys. Turn on `?helpHelp` to inspect the exact key at the target control before defining content. See [`help-utils.ts`](../../src/lib/utils/help-utils.ts) and [`widgetSlotHelp`](../../src/components/widgets/widget-utils.ts).
### Analytics configuration example
Publish these values through `QInstance.environmentValues`, not frontend build variables. Google uses `GOOGLE_ANALYTICS_ENABLED="true"` and `GOOGLE_ANALYTICS_TRACKING_ID` (a valid GA4 tracking ID). PostHog uses `POSTHOG_ENABLED="true"`, `POSTHOG_API_KEY` (alias `POSTHOG_PROJECT_API_KEY`), and optional `POSTHOG_HOST`, which defaults to `https://us.i.posthog.com`.
```json
{"environmentValues":{"ANALYTICS_PROVIDERS":"google,posthog","GOOGLE_ANALYTICS_ENABLED":"true","GOOGLE_ANALYTICS_TRACKING_ID":"G-EXAMPLE123","POSTHOG_ENABLED":"true","POSTHOG_PROJECT_API_KEY":"<public-project-key>","ANALYTICS_INCLUDE_RECORD_DATA":"false","ANALYTICS_IDENTIFY_USERS":"false"}}
```
Replace the illustrative IDs with the application's public provider identifiers. `ANALYTICS_PROVIDERS` accepts comma-, semicolon-, or newline-separated names; names are lowercased. If omitted, Google and PostHog are considered, but each still requires its own enable/configuration values. `ANALYTICS_PLUGIN_SCRIPTS` (alias `ANALYTICS_PLUGIN_SCRIPT_URLS`) loads configured provider scripts; those register through `window.QQQAnalytics`. Use the [provider registry](../../src/lib/analytics/registry.ts) and [provider interface](../../src/lib/analytics/types.ts), and configure host CSP for each script/endpoint. Privacy defaults and opt-ins are described above; never place server-side provider secrets in environment values exposed to browsers.
## 9. Accessibility, testing, and known boundaries
Components use labeled fields, keyboard navigation, focus-managed dialogs and drawers, inline errors, contained widget failures, and touch layouts. Below 768 px, queries become record cards and filters/actions use sheets; tablet testing uses a touch WebKit project. Test with keyboard and assistive technology in addition to axe. See [browser matrix](../acceptance/browser-matrix.md) and [accessibility rows](../acceptance/feature-matrix.md).
Run `pnpm typecheck`, `pnpm lint`, `pnpm test`, `pnpm test:e2e`, `pnpm build:export`, and `pnpm perf:budget` for the relevant change. For RC1, prioritize complete Chromium workflows and the full Chromium gate before expanding browser/viewport coverage; verify defaults, validation, save/reopen and persisted results. The final release acceptance gate is `QQQ_ACCEPTANCE_BROWSERS=chromium,firefox,webkit,mobile,tablet pnpm test:acceptance` with `QQQ_SAMPLE_JAR` pointing at a compatible real sample build. It verifies SQL/backend behavior as well as browser behavior; mocked tests are not a substitute. See [test setup](../../tests/acceptance/README.md), [performance budget](../acceptance/performance.md), and [API error handling](../API-ERROR-HANDLING.md).
Current limits to account for: the Material parity ledger has open Partial/Missing rows beyond the narrower feature matrix; the report filter/column editor supports variables and API-versioned metadata but still needs complete embedded-query equivalence review; a built-in QFMD bridge does not prove every third-party Material bundle works; root-path static hosting is the documented deployment; and Google Picker requires a backend with the #704 metadata/CSP changes (backend PR #908 merged to develop as `bac663044`). Real Auth0 tenant, Google account, and AWS QuickSight service tests have approved external-service exclusions in the feature matrix. Validate any additional feature against the current branch, backend contract, and matrix before describing it as production-compatible.
### Google Drive process configuration
This implementation is part of work after the immutable RC1 release. RC1 itself retains the disabled picker placeholder; the final 1.0 implementation requires the matching backend changes below.
Set the public browser OAuth client ID and browser API key as `GOOGLE_APP_CLIENT_ID`
and `GOOGLE_APP_API_KEY` in the QInstance environment (normally `QQQ_ENV_` environment
variables). Enable the Google Picker and Drive APIs and configure the application's
JavaScript origin in Google Cloud. Restrict the API key to the required APIs and website
origins, including `https://docs.google.com/*` as required by the
[Google Picker setup guide](https://developers.google.com/workspace/drive/picker/guides/web-picker).
Client secrets are not browser configuration and are not published in v1 metadata.
The Next UI middleware must include the #704 changes (backend PR #908, merged to develop as `bac663044`) that publish these two settings
and permit Google's SDK and picker origins in CSP only when both are configured.
Those conditional headers apply to the Javalin-hosted static export. The standalone
Node/container host also derives them when the backend publishes `dashboardCspSources`
in authentication metadata. With an older backend that omits that optional field, supply
`QQQ_DASHBOARD_CSP_SOURCES` at runtime. Merge these sources with any other deployment
additions and start through [`standalone/qqq-server.mjs`](../../standalone/qqq-server.mjs):
```bash
export QQQ_DASHBOARD_CSP_SOURCES='script-src https://accounts.google.com/gsi/client https://apis.google.com; connect-src https://accounts.google.com/gsi/; frame-src https://accounts.google.com/gsi/ https://docs.google.com; style-src https://accounts.google.com/gsi/style'
```
This matches the #704 backend directive additions. The standalone parser adds these to
its base policy; custom reverse proxies/hosts must supply equivalent allowances. See
[`security-headers.mjs`](../../standalone/security-headers.mjs). Keep the metadata client
ID/API key configured too: CSP permissions alone do not enable the picker.
The component preserves Material's
shared-drive folder view and `drive` OAuth scope. It loads the official SDKs before the
button becomes ready, supports retry after loading or authorization errors, rejects files,
and requires selection again if authorization expires before submission. Tokens remain
in screen memory and are submitted to the backend process; they are not persisted in
localStorage or sessionStorage. Without configuration the picker stays disabled while
other inputs remain usable. PRC-065 verifies the browser/backend contract with controlled
Google SDK responses; PRC-039 retains the approved real-account testing exclusion.
### Post-RC4 table appearance correction
RC5 restores the original Next uniform row background; RC4 has alternating row shading. Selection highlighting and opaque pinned cells are retained. Applications can target `data-row-parity="odd"` or `"even"` through their configured CSS if they explicitly want alternating colors.
### API playground theme integration (RC7)
The embedded RapiDoc viewer uses its own shadow DOM. Next applies existing dashboard color tokens to its dark accent/inverse pair and light contact/status colors, preserving the surrounding layout. Preferences theme changes apply without reloading the page. Focused Chromium desktop/mobile checks cover the fixture's visible text, hovered controls and live theme changes; expanded endpoint states, arbitrary custom palettes and narrow-screen inner layout still require review. This correction is not included in RC6.
### TIME block correction (RC7)
Plain process and standalone TIME blocks now use a one-second native input step, matching regular/shared form fields. This permits second-level clock values without a minute-step validation error and makes native stepping advance one second. TIME values remain local clock strings, with no timezone conversion. This correction is published in RC7 at `f65cf6b`; earlier RCs remain unchanged.
### Narrow API controls and LONG validation (RC8)
The API specification Download and View actions fit narrow panels, including 320px phones, and wrap their labels when necessary. Their download/new-tab behavior is preserved. On wide panels, buttons grow to fit the rendered font instead of clipping labels at a fixed width. This correction is not included in RC7.
When the frontend receives LONG range bounds, invalid integer text produces a field error before range comparisons run. Integer-string bounds retain exact precision beyond JavaScript's safe-number range; optional blanks and required-field messages retain their existing behavior. This describes the frontend schema's supported inputs, not a new backend metadata contract. RC8 includes this correction; RC7 does not.
@@ -0,0 +1,26 @@
# API playground controls in narrow panels — GH-724
## Requirement
Keep the original Next developer view and make its API specification actions fully visible and usable in narrow panels, including 320px phones. Preserve the wide desktop layout, real spec download/new-tab actions, light/dark themes and keyboard access.
## Evidence and implementation
- The packaged RC7 JAR fails the new real-backend visibility test: the Download button is partially clipped before any focus/click can horizontally scroll hidden ancestors. The View button extends outside the panel too.
- RapiDoc 9.3.8 has a 360px host minimum, a negative overview info margin and two fixed-width, non-wrapping buttons.
- Allow the host to fit its panel. Use the public overview/button CSS parts under a panel-width container query to provide safe inset and share available button width with wrapped labels. A named overview container limits button adjustments to specification actions, excluding authentication and endpoint controls. No dependency changes or shadow DOM mutation.
## Verification plan
1. Retain the failing Chromium phone trace and screenshot.
2. Run light/dark, normal-width and 320px workflows with desktop and touch input. Check complete visibility before interaction, download the real OpenAPI JSON, and open the same spec using keyboard navigation.
3. Inspect screenshots and compare wide desktop before/after layout.
4. Run existing API contrast/theme workflows, unit suite, types, lint, export and bundle budgets. Obtain independent review before integration.
Evidence: original checkout `test-results/visual-review/api-playground-width/`. This follow-up is separate from frozen RC7 candidate `f65cf6b`.
## Results
The final production export passed 12 focused desktop/mobile workflows, with zero failures, skips, flaky outcomes or report errors. All 1,904 unit tests, types, lint/license checks and the default Turbopack export passed. The unchanged bundle budget passed at 963.3KB/1050KB. The 1440px overview/button geometry and entire RapiDoc screenshot are identical to RC7; final phone screenshots were inspected.
Earlier evidence is retained: the first green attempt exposed a relative-versus-absolute URL assertion error; a later run used a running Java server with a replaced export, invalidating its startup asset/CSP registry. The server was restarted against the finished export before the passing run. A Webpack build exceeded the size budget; the release/CI Turbopack pipeline passed without changing budgets. The incompatible old font-response mock was removed from the build command; the final build uses the normal font pipeline.
@@ -0,0 +1,17 @@
# Bounded LONG validation — GH-728
## Defect and correction
Configured LONG ranges exposed two defects in the shared metadata schema: chained Zod refinements called `BigInt` after format validation had already failed, and exact integer-string bounds were rounded through `Number`. Seven new schema cases reproduced exceptions or wrongly accepted values before the fix.
The schema now pipes successfully validated integer text into its range checks. Integer-string bounds remain BigInt values; numeric fractional bounds use native mixed numeric comparisons. Optional blanks, required-field errors, signs, and existing field messages are preserved. No rendering changes or dependencies are introduced.
## Verification
- Seven schema regressions passed after failing on the previous source.
- Block and shared process-form component tests verify invalid fractional text, a below-minimum error, blocked submission, and correction to exact digits beyond the safe-number range.
- An unrelated intermittent unit failure expected a globally unique “Green” label although both view and edit fields display it. Its separate correction targets the read-only value being tested.
- All 1,913 units across 180 files passed; the 95 affected tests, types, lint/license and diff checks passed. Independent review approved both corrections.
- Evidence: original checkout `test-results/visual-review/long-range-validation/`. The initial full-run failure remains retained in `units.log`; the final full run is `units-final.log`.
This follow-up is not included in immutable RC7 `f65cf6b`. Production export and real-backend verification of the next integrated candidate remain required; these results establish schema/component behavior, not a new backend range-metadata contract.
+25
View File
@@ -0,0 +1,25 @@
# Next UI 1.0 testing period
## Final release is on hold
On September 29, 2026, the project owner requested a couple of weeks to test RC1 in real applications and report issues. **Do not tag, publish, or deploy final 1.0 without renewed, explicit owner approval.** Elapsed time and passing CI are not release approval.
RC1 remains immutable: [v1.0.0-RC.1](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.1), source `e901df91017204add7ebffa81765a02094e581b0`. See [RC1 release notes](1.0.0-RC.1.md) for installation details.
## Release candidate cadence
The owner authorizes publishing new RCs at verified stability or daily milestones. Each RC gets a new immutable version and notes describing its fixes, backend prerequisites, verification, and known gaps. RC publication does not authorize final 1.0.
Previously verified public candidate at the RC36 preparation checkpoint: **[1.0.0-RC.35](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.35)**. Its public release entry records exact artifact verification, backend requirements and known limits. RC1 through RC20 and RC22 through RC35 remain available and unchanged (RC21 was never published).
## Fixes during testing
This checkout targets [RC36](1.0.0-RC.36.md); confirm availability in the [RC36 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.36) before installing. RC36 adds accessibility fixes: readable link text, 24px clear targets, h2 widget titles and skip-link focus ([PR175](https://github.com/QRun-IO/qqq-frontend-next/pull/175) (a11y: readable link text, 24px clear target, h2 widget titles, skip link focus)). RC35 added a profile photo uploader and avatars everywhere (UI API 1.8.0) ([PR164](https://github.com/QRun-IO/qqq-frontend-next/pull/164) (profile photo uploader and avatars everywhere, UI API 1.8.0)). RC34 added fail-soft error handling: layered error boundaries, validated assistant payloads and streams that can no longer lock the panel ([PR172](https://github.com/QRun-IO/qqq-frontend-next/pull/172) (layered error boundaries, zod payload repair, streaming can no longer lock the assistant panel)). RC33 added the new navigation (sidebar sections and favorites, one search, a new header, single-key shortcuts) and an assistant crash fix ([PR168](https://github.com/QRun-IO/qqq-frontend-next/pull/168) (sidebar sections and favorites, one search, new header, single-key shortcut switch), [PR170](https://github.com/QRun-IO/qqq-frontend-next/pull/170) (empty assistant query results no longer crash the page)). RC32 added a What's new page and user-menu entry for release notes ([PR165](https://github.com/QRun-IO/qqq-frontend-next/pull/165) (What's new page and user-menu entry, release-notes UI API 1.0.0)). RC31 added UI API 1.6.0 adoption: client capabilities, field validation, one error schema and v1 branding ([PR162](https://github.com/QRun-IO/qqq-frontend-next/pull/162) (adopt QQQ UI API 1.6.0: clientCapabilities, field validation, error schema, v1 branding)). RC30 added optional Jam QA capture on the qbit-qa-capture UI API extension ([PR160](https://github.com/QRun-IO/qqq-frontend-next/pull/160) (optional Jam QA capture on the qbit-qa-capture qa-capture extension)). RC29 added the EDI mapper page on the qbit-edi UI API extension ([PR136](https://github.com/QRun-IO/qqq-frontend-next/pull/136) (EDI mapper page on the qbit-edi edi extension, all calls on spec)). RC28 added zero off-spec UI API calls, so every backend call is a /qqq/v1 operation in the spec (minimum UI API 1.4.0) ([PR141](https://github.com/QRun-IO/qqq-frontend-next/pull/141) (zero off-spec UI API calls; OFF_SPEC_CEILING 0; minimum UI API 1.4.0)). RC27 added the secured-data UI (masked values, reveal, restricted filters, break-glass banner) and the live job view with a header Jobs tray and Jobs page ([PR124](https://github.com/QRun-IO/qqq-frontend-next/pull/124) (secured-data UI: masked values, reveal, restricted filters, break-glass banner), [PR155](https://github.com/QRun-IO/qqq-frontend-next/pull/155) (live job view, header Jobs tray and Jobs page), [PR156](https://github.com/QRun-IO/qqq-frontend-next/pull/156) (large-table acceptance fixture declares its default columns)). RC26 added tables that open with a short default column set instead of every field ([PR142](https://github.com/QRun-IO/qqq-frontend-next/pull/142) (tables open with a short default column set)). RC25 added an assistant panel that never blocks asking, re-attaches after refresh, shows live progress and can always Stop ([PR151](https://github.com/QRun-IO/qqq-frontend-next/pull/151) (assistant: never block asking, re-attach after refresh, live progress and Stop)). RC24 added the tablet record-tab fix, empty tables that explain themselves, a Full access checkbox in the policy editor and homes without duplicate card grids ([PR149](https://github.com/QRun-IO/qqq-frontend-next/pull/149) (record section tabs wrap instead of folding into More; tablet regression from PR144), [PR150](https://github.com/QRun-IO/qqq-frontend-next/pull/150) (empty tables explain what they are for and how rows get created), [PR148](https://github.com/QRun-IO/qqq-frontend-next/pull/148) (Full access checkbox in the authentik policy editor), [PR145](https://github.com/QRun-IO/qqq-frontend-next/pull/145) (no automatic card grids on homes that declare widgets)). RC23 added help everywhere, the Preferences page, contingent possible values in forms, search typeahead and System/Light/Dark theming ([PR127](https://github.com/QRun-IO/qqq-frontend-next/pull/127) (help everywhere: help icons, long-help panel, help search, author links), [PR121](https://github.com/QRun-IO/qqq-frontend-next/pull/121) (Preferences page, notification settings drawer and section actions), [PR120](https://github.com/QRun-IO/qqq-frontend-next/pull/120) (contingent possible values across forms, bulk edit, processes and filters), [PR143](https://github.com/QRun-IO/qqq-frontend-next/pull/143) (typeahead suggestions from qbit-quick-search in the global search box), [PR140](https://github.com/QRun-IO/qqq-frontend-next/pull/140) (authentik policy editor feature permissions and member preview), [PR138](https://github.cLine truncated
RC9's immutable-tag acceptance run completed with known Firefox and tablet failures; its release notes preserve the exact results. Follow [qqq#985](https://github.com/QRun-IO/qqq/issues/985) for browser investigations and [qqq#713](https://github.com/QRun-IO/qqq/issues/713) for the complete backlog. The [original Next visual review](../acceptance/next-ui-visual-review.md) and owner approval remain separate requirements.
## Reporting application issues
Create or update the central QQQ ticket and Project 12 item using the [tracking policy](../ISSUE-TRACKING.md). Include the exact UI artifact/version or branch commit, QQQ backend version, page/workflow, expected and actual results, reproduction steps, and relevant screenshots or sanitized errors. This distinguishes an issue in a published RC from a later branch regression. Never include credentials or sensitive application records.
The compatibility ledger and remaining acceptance work still govern final readiness. The testing hold does not waive those requirements or authorize a QQQ 4.1 release.
+57
View File
@@ -0,0 +1,57 @@
# Next UI 1.0.0-RC.1
Release candidate for testing in real QQQ applications. Published from commit `e901df91017204add7ebffa81765a02094e581b0`. Final 1.0 visual review and compatibility work remain open.
## Install
```xml
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.1</version>
</dependency>
```
Override any older BOM-managed frontend version. Select Next with `-Dqqq.javalin.frontend=next`. The JAR serves the dashboard at `/` alongside `/qqq/v1`; no Node server is required. Keep Material available during evaluation if you need to switch back with `-Dqqq.javalin.frontend=material`.
[Maven Central JAR](https://repo.maven.apache.org/maven2/com/kingsrook/qqq/qqq-frontend-next/1.0.0-RC.1/qqq-frontend-next-1.0.0-RC.1.jar) — verified by download, checksum, a consuming Maven project, and real-backend runtime checks.
Container: `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.1` (linux/amd64 and linux/arm64).
```text
ghcr.io/qrun-io/qqq-frontend-next@sha256:6edd16f20557c189bc618079851cd2d567c04001c6e34a5d3b7dde30895f9958
```
The quickstart image expects its backend at `http://host.docker.internal:8000`; that origin is fixed at build time. For other deployments, use the Maven artifact or build the image with the appropriate backend configuration.
This candidate is tested with the QQQ **4.1.0-SNAPSHOT development backend**, including the Sleep Interactive fix merged in [QQQ PR #896](https://github.com/QRun-IO/qqq/pull/896) (`e33e1fc52c55c203e3ee3014837970ef2614b809`). It does not publish QQQ 4.1 or establish compatibility with every older backend release. Pin and report your backend version/commit.
## Verification
- **547 real-backend Chromium scenarios passed**, with zero failures, skips or flaky retries; all 389 required acceptance rows passed, with three previously approved external-service exclusions. [Exact-candidate CI](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36601699322/job/109521050432).
- 1,806 unit tests, type checking, lint, mocked E2E, Storybook, license and bundle-budget checks passed. Candidate dependency audit reported zero known vulnerabilities on 2026-09-29.
- The actual Central JAR resolved in a consuming Maven project. It contains 186 dashboard export files, expected routes and license, and no development mock worker. Its checksum verified; a signature is published. The CI production export uses a different bundler from the local 204-file verification build.
- The downloaded JAR ran against the real sample backend: Sleep Interactive defaults, required validation and completion passed; report-filtered statistics and exported CSV matched independent SQL.
- The published container manifest includes both architectures; the downloaded arm64 image started and served the dashboard successfully. This container check is a runtime/HTML smoke test, separate from the full backend workflow suite.
Maven JAR SHA-256: `5a5f56e892164c2b3c220d929372cf57c019dabaa35340839be3b9a6907076eb`.
Testing prioritizes complete Chromium workflows: normal entry/defaults, validation, editing/cancellation, save/reopen, dependent selections, permissions, recovery and persisted/downloaded results. Test counts do not prove every application configuration works.
## Known limitations
- Secondary-browser verification is complete and has unresolved failures. In the [candidate run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36601699322), WebKit passed 546 and failed NAV-034 (legacy process redirect, internal navigation error); Firefox passed 546 and failed WID-021 (process widget visibility timeout); phone/tablet passed 1,043 and failed tablet SEC-002 (internal WebKit navigation error). The [tag run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36605084236) passed Chromium again but failed Firefox RPT-016 (report sharing), WebKit REL-007 (association navigation), and tablet NAV-015 (document-title navigation), with the same respective passing counts. Logs/traces are retained; these failures have not been waived or established as infrastructure-only. No complete cross-browser pass is claimed.
- Hosting beneath a URL base path is not implemented ([QQQ #732](https://github.com/QRun-IO/qqq/issues/732)); evaluate at the origin root.
- Google Drive folder selection remains a stub ([QQQ #704](https://github.com/QRun-IO/qqq/issues/704)).
- Full embedded report/query equivalence remains under review ([QQQ #722](https://github.com/QRun-IO/qqq/issues/722)).
- Remaining input-block configurations and unsupported Material icon names need completion ([QQQ #728](https://github.com/QRun-IO/qqq/issues/728)).
- Script editor autocomplete is absent ([QQQ #724](https://github.com/QRun-IO/qqq/issues/724)).
- Full visual comparison against the original Next UI design remains open. Material is the behavior reference; the original Next UI is the visual reference.
These are recorded RC1 gaps, not approved exclusions from final 1.0. The [behavior parity ledger](https://github.com/QRun-IO/qqq-frontend-next/blob/e901df91017204add7ebffa81765a02094e581b0/docs/acceptance/material-parity.md) remains authoritative.
## Guides and bug reports
[Developer guide](https://github.com/QRun-IO/qqq-frontend-next/blob/e901df91017204add7ebffa81765a02094e581b0/docs/guides/next-ui-developer-guide.md) · [Agent guide](https://github.com/QRun-IO/qqq-frontend-next/blob/e901df91017204add7ebffa81765a02094e581b0/docs/guides/next-ui-agent-guide.md) · [Acceptance instructions](https://github.com/QRun-IO/qqq-frontend-next/blob/e901df91017204add7ebffa81765a02094e581b0/tests/acceptance/README.md).
[Report an issue](https://github.com/QRun-IO/qqq-frontend-next/issues/new) with frontend/backend versions, browser, affected widget or metadata, steps from a fresh page, expected/actual behavior and whether it survives reload. Include screenshots or sanitized errors where useful. For reports, include table, filters, sorts and columns; for processes, include step/input configuration. Remove credentials, session cookies and private application data.
+30
View File
@@ -0,0 +1,30 @@
# Next UI 1.0.0-RC.10 — preparation
RC10 is being prepared for application testing and **is not yet published**. [RC9](1.0.0-RC.9.md) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.10` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.10`. Public checksums, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Changes from RC9
- Fix section-hash loss and stale immediate URLs when changing record tabs or views (#989), preserving URL context, return links, focus and history. The baseline eventually restored the tab; this is not a claim of proven tab loss.
- Preserve search-dialog keyboard focus (#950), WebKit possible-value selections (#978), and authentication loading through logout/provider departure (#984).
- Add ESB developer views and overview/management controls, with real-backend acceptance. See the [ESB contract](../acceptance/esb.md).
- Improve browser readiness and diagnostic checks while preserving assertions. The pinned Playwright runner contains the corrected Linux WebKit library; stable-runner migration remains #973. See [change details](unreleased.md).
## Verification
Verification uses source `5e3738b73f14d7ed25580560f3c97b91aed58fce` ([PR21](https://github.com/QRun-IO/qqq-frontend-next/pull/21)) with RC10 metadata and QQQ backend `55b33e6bd7f8cc2aef3b41546ef8409ad307ff05`.
- **1,979 unit tests / 184 files**, typecheck, lint/license (zero errors, five existing warnings), static export, Maven packaging and bundle budgets pass.
- The actual RC10 JAR passes **85/85** auth/focus/selection/ESB real-backend cases: Chromium, Firefox and WebKit 21 each; mobile and tablet 11 each.
- Four navigation regressions pass **8/8 in Firefox/WebKit**, using the same JAR's assets with mocked APIs. These are separate from real-backend API coverage.
Both browser reports have zero failures, skips, flaky outcomes or report errors, with retries disabled. All 192 dashboard files are packaged without a mock service worker; Maven and compiled frontend versions are RC10. Served JavaScript and font bytes match the JAR. The primary fixture serves the JAR directly; authentication variants use verified assets extracted from it. These are local candidate checks, not public delivery receipts. PR23 changes test selection and documentation only; production code and test assertions are unchanged from this verified candidate.
## Browser coverage and remaining RC limits
The historical [PR18 run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36942612338) failed its touch-coverage gate despite all 2,972 executed cases passing: ESB-001–010 were required but not selected on phone and tablet. The original failed result is retained under [#990](https://github.com/QRun-IO/qqq/issues/990).
[PR23](https://github.com/QRun-IO/qqq-frontend-next/pull/23) corrects selection by adding touch markers to the ten existing ESB tests, preserving production code and every assertion. Its separate focused real-broker run on the PR18 export and QQQ `55b33e6` passed all 20 phone/tablet cases with zero retries, failures, skips, flaky outcomes or report errors. This focused result is not a fresh full hosted matrix pass; full hosted verification remains pending at this preparation checkpoint.
The secondary-browser/tablet RC exception remains documented in [#985](https://github.com/QRun-IO/qqq/issues/985#issuecomment-5943724086) and [#798](https://github.com/QRun-IO/qqq/issues/798#issuecomment-5943457468). No gate, test requirement or exclusion was weakened. This exception does not waive product defects or authorize GA.
Remaining parity, real Auth0/Drive/QuickSight checks, browser investigations, visual review and application soak stay tracked in [#713](https://github.com/QRun-IO/qqq/issues/713), [#985](https://github.com/QRun-IO/qqq/issues/985), [#711](https://github.com/QRun-IO/qqq/issues/711) and [#712](https://github.com/QRun-IO/qqq/issues/712). The container backend origin is fixed at build time to `host.docker.internal:8000`; use the JAR or rebuild for another origin. This candidate does not itself publish QQQ 4.1.
+19
View File
@@ -0,0 +1,19 @@
# Next UI 1.0.0-RC.11 — preparation
At this preparation checkpoint, RC11 is being prepared for application testing and **is not yet published**. [RC10](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.10) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.11` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.11`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Changes from RC10
[PR24](https://github.com/QRun-IO/qqq-frontend-next/pull/24), merged source `a1d3e48419812fcb8d47091cb4af315cad4e76e9` (tree-identical to reviewed head `ea73118fcabdf3c08ea8614c8bd0ad63749fb598`), invalidates stale searchable-selector results immediately when input changes (#970), and keeps script-completion suggestions open when a delayed scroll event reports an unchanged position (#954). Actual position changes still dismiss suggestions. No acceptance assertion or gate is weakened.
## Verification checkpoint
PR24's [hosted primary acceptance](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36954433088) passes on its exact source: 614 cases and 401 required rows. The actual merged source above has the same tree. With RC11 metadata, fresh verification passes **1,984 unit tests / 184 files**, typecheck, lint/license (zero errors, five existing warnings), static export, Maven packaging and bundle budgets. All **192 dashboard files** match the export byte-for-byte, with no mock service worker and matching RC11 frontend/Maven versions.
The actual RC11 JAR passes **125/125 real-backend cases** against QQQ `55b33e6bd7f8cc2aef3b41546ef8409ad307ff05`: 25 each in Chromium, Firefox, WebKit, phone and tablet. These include the 105 auth/focus/selection/ESB cases and 20 selector/completion regression cases. A separate **8/8 record-navigation regressions in Firefox/WebKit** use the same JAR assets with mocked APIs. Both reports have zero failures, skips, flaky outcomes or report errors, with retries disabled. Served versioned JavaScript and font bytes match the JAR. The scoped matrix check passes only the exercised rows; it is not a full hosted matrix result. These local candidate checks are separate from eventual public-artifact verification; no RC10 test count or artifact digest is attributed to RC11.
## Remaining RC limits
Historical observations remain preserved: REC-054 on mobile light expected three completion options but found none; PRC-061 in WebKit left the Casey selection at its placeholder. See [#954](https://github.com/QRun-IO/qqq/issues/954), [#970](https://github.com/QRun-IO/qqq/issues/970) and [RC10 delivery notes](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.10). The original [PR18 touch-coverage failure](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36942612338) remains historical evidence. [#990 is complete](https://github.com/QRun-IO/qqq/issues/990#issuecomment-5944836159): PR23’s complete touch report covers all 401 required IDs on each profile, with zero missing, and all ten ESB scenarios pass first attempt on both phone and tablet. That report records **1,149 passes and one unrelated tablet NAV-056 failure**; its full touch gate and whole workflow remain **failed**. [NAV-056 diagnostics remain under investigation](https://github.com/QRun-IO/qqq/issues/985#issuecomment-5944830383), provisionally tracked in [#904](https://github.com/QRun-IO/qqq/issues/904#issuecomment-5944558037), without claiming a shared historical cause. Scoped #990 completion does not imply an all-browser pass or waive that failure.
The documented secondary-browser/tablet RC exception remains under [#985](https://github.com/QRun-IO/qqq/issues/985#issuecomment-5943724086) and [#798](https://github.com/QRun-IO/qqq/issues/798#issuecomment-5943457468); it does not waive new product defects or authorize GA. Material parity, sub-path hosting, real Auth0/Drive/QuickSight verification, visual review and application soak remain tracked in [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711) and [#712](https://github.com/QRun-IO/qqq/issues/712). The image backend origin remains fixed at build time to `host.docker.internal:8000`; use the JAR or rebuild for another origin. This candidate does not publish QQQ 4.1.
+60
View File
@@ -0,0 +1,60 @@
# Next UI 1.0.0-RC.12 — preparation
At this preparation checkpoint, RC12 is being prepared for application testing and **is not yet published**. [RC11](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.12` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.12`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `b0b0d469feb0b1bd473fbf189540e712ffc09807` (merge of [PR46](https://github.com/QRun-IO/qqq-frontend-next/pull/46)). Merged since RC11: PR25, PR27, PR28, PR29, PR30, PR32, PR35, PR36, PR38, PR40, PR42, PR43, PR44 and PR46. The SQL Workbench work in draft PR47 is not included.
## Backend requirements
RC12 runs on a published QQQ 4.1 candidate: **4.1.0-RC.1** or later. Standalone branding images (below) need **4.1.0-RC.2** or later, which contains QQQ #1009's `/qqq/branding/logo` and `/qqq/branding/icon` endpoints. RC12 does not publish QQQ 4.1.
## Product fixes
- Always show the record view's Actions menu when it has entries. Tables without processes or a custom `VIEW_SCREEN_ACTIONS` menu, and read-only tables, again reach Developer Mode and Audit (Material parity); Copy and Delete move into that menu instead of also appearing as separate buttons. [#45](https://github.com/QRun-IO/qqq-frontend-next/issues/45)
- Clear remembered query filters and saved-view selections on logout or a detected user identity change. Same-user resumes keep their query state; global appearance, density and legacy column preferences remain intact. This correction awaits its own branch acceptance. [#1006](https://github.com/QRun-IO/qqq/issues/1006)
- In searchable possible-value fields, invalidate old results immediately when the search term changes, so a debounced request cannot remove an option during its selection. [#970](https://github.com/QRun-IO/qqq/issues/970)
- Keep code suggestions open when a delayed textarea scroll event reports the position already used for the popup; dismiss them when the scroll position actually changes. [#954](https://github.com/QRun-IO/qqq/issues/954)
- Preserve focus on the search dialog's Clear control when keyboard interaction occurs before its scheduled initial focus. [#950](https://github.com/QRun-IO/qqq/issues/950)
- Preserve a clicked possible-value option in WebKit when blurring its search input would scroll the form between mousedown and mouseup. The selected value now reaches process submission and generated output. [#978](https://github.com/QRun-IO/qqq/issues/978)
- Keep authentication loading until explicit logout finishes, so a late logout response cannot erase cookies from a subsequent successful sign-in. [#984](https://github.com/QRun-IO/qqq/issues/984)
## Standalone branding and CSP (QQQ #1009, #1017)
The standalone Node server can route the exact root-relative `branding.logo` and
`branding.icon` paths published by authentication metadata through fixed backend
image roles. Local Node `public` files keep precedence; external image URLs and
Java dashboard serving are unchanged. This does not expose the whole application
overlay or add a general proxy.
This requires **a QQQ middleware-javalin build containing QQQ #1009's
`/qqq/branding/logo` and `/qqq/branding/icon` endpoints**. They are absent from
published **4.1.0-RC.1**. The minimum numbered release is not yet assigned; a
source build must record the exact backend commit/JAR hashes. Older deployments
must supply branding through their Node `public` files or an existing external
image URL. No automatic compatibility with old relative backend assets is claimed.
The preserved standalone qualification against public RC1 remains **5 passed,
38 failed** out of 43 cases because of missing branding assets. The original
staging failure is also retained. The corrected cross-repository pairing has
not yet received its browser qualification; neither #1009 nor #734 is closed by
focused unit/HTTP results.
## Browser testing and infrastructure
- Pin the official Playwright runner containing the fixed Linux WebKit network library, and retain native browser diagnostics for navigation failures. The previous bundle's heap corruption was reproduced; migration to a stable runner remains tracked separately. [#904](https://github.com/QRun-IO/qqq/issues/904), [#973](https://github.com/QRun-IO/qqq/issues/973)
- Correlate interrupted Firefox font diagnostics with real request and decode evidence, wait for initial fonts before the deliberate redirect, and observe transient feedback during the action that produces it. [#951](https://github.com/QRun-IO/qqq/issues/951), [#953](https://github.com/QRun-IO/qqq/issues/953), [#967](https://github.com/QRun-IO/qqq/issues/967)
- Wait for nested report metadata and the destination's owned iframe before advancing acceptance navigation. [#961](https://github.com/QRun-IO/qqq/issues/961), [#972](https://github.com/QRun-IO/qqq/issues/972)
- Verify Linux WebKit's downloaded filename normalization while retaining file-content and cleanup assertions. [#979](https://github.com/QRun-IO/qqq/issues/979)
- Verify actual browser refusal of disallowed framing with a control that renders when the protective headers are removed. [#981](https://github.com/QRun-IO/qqq/issues/981)
- Recognize Firefox's network-error diagnostic for the gallery's intentionally missing extension only after that same script request returns HTTP 404; preserve the raw evidence and reject unrelated failures. [#983](https://github.com/QRun-IO/qqq/issues/983)
- Select all ten ESB scenarios for both phone and tablet acceptance, retaining real broker delivery, lifecycle, replay, SQL and permission checks. Matrix references now use the central QQQ tickets. [#986](https://github.com/QRun-IO/qqq/issues/986), [#990](https://github.com/QRun-IO/qqq/issues/990)
## Verification checkpoint
PR checks on the RC12 preparation source (typecheck, lint, unit tests and coverage, mocked e2e, bundle budgets, Storybook, image builds and the full real-backend acceptance workflow) are recorded on the preparation PR. Tag acceptance and public-artifact verification are recorded in the [RC12 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.12) after delivery. No RC11 test count or artifact digest is attributed to RC12.
## Remaining RC limits
The RC11 limits carry forward unchanged. The RC11 tag acceptance recorded 2,990 passes and 2 owner-accepted failures (Firefox NAV-014 font diagnostic, tablet NAV-056 access-control diagnostics) under the documented secondary-browser/tablet exception ([#985](https://github.com/QRun-IO/qqq/issues/985), [#798](https://github.com/QRun-IO/qqq/issues/798)); it does not waive new product defects or authorize GA. Historical REC-054/PRC-061 observations remain under [#954](https://github.com/QRun-IO/qqq/issues/954) and [#970](https://github.com/QRun-IO/qqq/issues/970). Material parity, sub-path hosting, real Auth0/Drive/QuickSight verification, visual review and application soak remain tracked in [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904). The image backend origin remains fixed at build time to `host.docker.internal:8000`; use the JAR or rebuild for another origin.
+43
View File
@@ -0,0 +1,43 @@
# Next UI 1.0.0-RC.13 — preparation
At this preparation checkpoint, RC13 is being prepared for application testing and **is not yet published**. [RC12](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.12) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.13` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.13`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `84c95e524a93056864e60863f5ab45357969e786` (merge of [PR53](https://github.com/QRun-IO/qqq-frontend-next/pull/53)). Merged since RC12: [PR47](https://github.com/QRun-IO/qqq-frontend-next/pull/47) (SQL Workbench), [PR49](https://github.com/QRun-IO/qqq-frontend-next/pull/49) (FILE_PREVIEW thumbnails and lightbox), [PR50](https://github.com/QRun-IO/qqq-frontend-next/pull/50) (responsive layouts, readable child lists, compact dates) and [PR53](https://github.com/QRun-IO/qqq-frontend-next/pull/53) (instance home app). The single page scroll container ([PR54](https://github.com/QRun-IO/qqq-frontend-next/pull/54), its WebKit acceptance not yet green) and widget width readability ([PR55](https://github.com/QRun-IO/qqq-frontend-next/pull/55)) are not included; they follow in RC14.
## Backend requirements
RC13 runs on a published QQQ 4.1 candidate: **4.1.0-RC.1** or later. Standalone branding images need **4.1.0-RC.2** or later (QQQ #1009). **FILE_PREVIEW thumbnails and the instance home app need QQQ 4.1.0-RC.3 or later**, which carries the `FILE_PREVIEW` adornment ([qqq#1056](https://github.com/QRun-IO/qqq/issues/1056)) and `QInstance.homeAppName` ([qqq#1059](https://github.com/QRun-IO/qqq/issues/1059)); on older backends neither appears and the rest of RC13 works unchanged. The SQL Workbench page needs the [qbit-sql-workbench](https://github.com/QRun-IO/qbit-sql-workbench) QBit, **1.0.0-RC.1** or later, on the backend. RC13 does not publish QQQ 4.1.
## New features
- SQL Workbench: render the `SQL_WORKBENCH` widget of the [qbit-sql-workbench](https://github.com/QRun-IO/qbit-sql-workbench) QBit as a super-user SQL notebook.
- Personal and shared notebooks with SQL and markdown cells, `:parameters`, versions and pinned results.
- A CodeMirror 6 editor with dialect highlighting, schema and QQQ-name autocomplete, and formatting.
- A typed, sortable, paged result grid with CSV/XLSX/JSON export, quick charts, and EXPLAIN plan trees.
- Read-only runs by default, and a two-step write confirmation for users with the write permission.
- CodeMirror and the formatter load only on that page (new `lazyChunks` budgets).
## Features
- Support an instance home app. When the backend designates one with `QInstance.homeAppName` ([qqq#1059](https://github.com/QRun-IO/qqq/issues/1059)), and the user may open it, `/app` shows that app's dashboard (a client-side redirect, matching backends that redirect full page loads of `/app/`), the sidebar logo and the breadcrumb Home crumb link there, and the sidebar drops its separate Dashboard entry. Without a home app, or with one the user may not open, `/app` remains Next's overview.
- Show `FILE_PREVIEW`-adorned fields as thumbnails, and open a preview on click or Enter. Thumbnails appear in the query grid and card view, the record view (a larger thumbnail with the file name), associated-record tables, child-record list widgets and record-grid widgets. They load lazily at a fixed size, so loading does not shift the layout, with a loading pulse and a file-type icon fallback. The preview is an accessible dialog: focus is trapped and restored, Escape closes it, and Left/Right/Home/End step through the other previews on the same screen. It offers zoom and fit, open-original and download links, and shows type, size, dimensions and caption. PDFs served by the backend (same-origin `/qqq/` paths) and video/audio play inline; other types, and other or cross-origin PDFs (frames are limited to `'self'`), get a file card or open in a new tab. Thumbnails are shown only for explicitly adorned fields; plain URL values are never auto-detected. URLs are limited to http(s), same-origin paths and inline images. The lightbox loads on demand (about 3.5 KB gzip). Requires the QQQ backend adornment from [qqq#1056](https://github.com/QRun-IO/qqq/issues/1056).
## Product fixes
- Size record-view cards, dashboard widgets and form fields by the width of their own grid (container queries) rather than the viewport: table panels sit side by side only when each is readable and otherwise stack, cards keep their own height, fields share a row only when they fit, and very wide screens use the room. No horizontal page scroll from 360px up.
- Make child-record lists in record-view panels readable: they show the columns that matter most and fit (the record name, status and boolean columns, the most recently changed date; the key and long text last), with `+N more columns` and View All for the rest. Cells align to the top and long text is clamped with the full text on hover. A widget's `onlyIncludeFieldNames` order is respected.
- Show dates compactly everywhere: `6:33 PM` today, `Jul 5, 6:33 PM` this year, `Jul 5, 2025` before, on one line with the full zoned timestamp on hover, in the viewer's zone and locale. DATE values are no longer shifted a day west of UTC. Exports are unchanged.
## Bundle budget
The record developer route `/app/_/_/dev` and the table developer route `/app/_/dev` each have a 360 KB first-load cap (measured 354.9 KB and 350.8 KB on the combined tree); every other route keeps the 350 KB default. The SQL Workbench editor (CodeMirror 6) and formatter load on demand under separate `lazyChunks` budgets; the FILE_PREVIEW lightbox is a lazy chunk.
## Verification checkpoint
PR checks on the RC13 preparation source (typecheck, lint, unit tests and coverage, mocked e2e, bundle budgets, Storybook, image builds and the full real-backend acceptance workflow) are recorded on the preparation PR. During integration the WebKit/tablet NAV-016, NAV-023 and NAV-056 access-control diagnostics and one Firefox image-decode diagnostic (SEC-026) recurred intermittently and passed on rerun; they belong to the tracked [#985](https://github.com/QRun-IO/qqq/issues/985) / [#951](https://github.com/QRun-IO/qqq/issues/951) investigations and no assertion was relaxed. Tag acceptance and public-artifact verification are recorded in the [RC13 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.13) after delivery. No RC12 test count or artifact digest is attributed to RC13.
## Remaining RC limits
The RC12 limits carry forward unchanged: Material parity, sub-path hosting, real Auth0/Drive/QuickSight verification, visual review and application soak remain tracked in [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904). The image backend origin remains fixed at build time to `host.docker.internal:8000`; use the JAR or rebuild for another origin.
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.14 — preparation
At this preparation checkpoint, RC14 is being prepared as an urgent fix for application testing and **is not yet published**. [RC13](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.13) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.14` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.14`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `b699204d8736f5cfcc61358c38e86d89ab3d5541` (merge of [PR57](https://github.com/QRun-IO/qqq-frontend-next/pull/57)). Merged since RC13: [PR52](https://github.com/QRun-IO/qqq-frontend-next/pull/52) (Next.js 16.3.8 security update and sharp) and [PR57](https://github.com/QRun-IO/qqq-frontend-next/pull/57) (record search tolerates results without a label). RC14 is cut early because global record search is broken in production on RC13. The README adoption guide (PR58), the record developer view (PR61), the single page scroll container (PR54), widget width readability (PR55) and the NAV-056 diagnostic fix are not included; they follow in RC15.
## Backend requirements
Unchanged from RC13: a published QQQ 4.1 candidate, **4.1.0-RC.1** or later; **4.1.0-RC.2** or later for standalone branding images (QQQ #1009); **4.1.0-RC.3** or later for FILE_PREVIEW thumbnails ([qqq#1056](https://github.com/QRun-IO/qqq/issues/1056)) and the instance home app ([qqq#1059](https://github.com/QRun-IO/qqq/issues/1059)); [qbit-sql-workbench](https://github.com/QRun-IO/qbit-sql-workbench) **1.0.0-RC.1** or later for the SQL Workbench page. The search fix needs no backend change; the server-side companion [qqq#1068](https://github.com/QRun-IO/qqq/pull/1068) stops emitting blank labels. RC14 does not publish QQQ 4.1.
## Product fixes
- Record search no longer fails when one result has no label. QQQ serializes JSON without empty values, so a record whose label is empty arrived without `recordLabel`, and the strict response schema rejected the whole response ("Record search failed. Try again."). The response envelope is still checked strictly, but each result is now validated on its own: `tableName` and `recordId` are required (a numeric `recordId` is accepted), a result without a label is shown as "{table label} {record id}", and a result that cannot link to a record is skipped with a console warning while the others render.
## Dependencies
- Next.js 16.3.6 to 16.3.8 (security fixes, including the image-optimization SSRF advisory GHSA-cjq9-62q9-8jv4) and the matching `sharp` update ([PR52](https://github.com/QRun-IO/qqq-frontend-next/pull/52)).
## Verification checkpoint
PR checks on the RC14 preparation source (typecheck, lint, unit tests and coverage, mocked e2e, bundle budgets, Storybook, image builds and the full real-backend acceptance workflow) are recorded on the preparation PR. During integration the WebKit/tablet NAV-023 diagnostic recurred intermittently and passed on rerun. **Known failure accepted for this release:** the WebKit NAV-056 diagnostic (appearance preferences explain an application theme override) failed on [PR57](https://github.com/QRun-IO/qqq-frontend-next/pull/57) and also fails on main without RC14's changes; the owner explicitly approved merging and releasing RC14 with it because production record search was broken. It is tracked under [#985](https://github.com/QRun-IO/qqq/issues/985), a fix is in progress for RC15, and no assertion was relaxed. Tag acceptance and public-artifact verification are recorded in the [RC14 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.14) after delivery. No RC13 test count or artifact digest is attributed to RC14.
## Remaining RC limits
The RC13 limits carry forward unchanged: Material parity, sub-path hosting, real Auth0/Drive/QuickSight verification, visual review and application soak remain tracked in [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904). The image backend origin remains fixed at build time to `host.docker.internal:8000`; use the JAR or rebuild for another origin.
+41
View File
@@ -0,0 +1,41 @@
# Next UI 1.0.0-RC.15 — preparation
At this preparation checkpoint RC15 **is not yet published**. [RC14](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.14) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.15` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.15`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `5de69be`. Merged since RC14: [PR58](https://github.com/QRun-IO/qqq-frontend-next/pull/58) (README adoption guide), [PR55](https://github.com/QRun-IO/qqq-frontend-next/pull/55) (readable dashboard widget widths), [PR54](https://github.com/QRun-IO/qqq-frontend-next/pull/54) (one page scroll container), [PR77](https://github.com/QRun-IO/qqq-frontend-next/pull/77) (Dependabot limited to three open PRs per ecosystem), [PR78](https://github.com/QRun-IO/qqq-frontend-next/pull/78) (SQL Workbench dark mode and standard controls), [PR61](https://github.com/QRun-IO/qqq-frontend-next/pull/61) (record developer view), [PR60](https://github.com/QRun-IO/qqq-frontend-next/pull/60) (QQQ Assistant panel, phase 1), [PR59](https://github.com/QRun-IO/qqq-frontend-next/pull/59) (search-as-you-type quick search, richer results and a search page).
## Backend requirements
A published QQQ 4.1 candidate, **4.1.0-RC.1** or later; **4.1.0-RC.3** or later for FILE_PREVIEW and the instance home app; **4.1.0-RC.4** or later for record developer panels ([qqq#1076](https://github.com/QRun-IO/qqq/pull/1076)); [qbit-sql-workbench](https://github.com/QRun-IO/qbit-sql-workbench) 1.0.0-RC.1 or later for the SQL Workbench page (1.0.0-RC.4 or later opens developer-panel SQL in the Runner); [qbit-assistant](https://github.com/QRun-IO/qbit-assistant) 0.1.0-RC.3 or later for the assistant panel (without it nothing renders). RC15 does not publish QQQ 4.1.
## Features
- **QQQ Assistant panel (Phase 1: read-only questions and navigation).** When the `qbit-assistant` backend is installed and `GET /qqq/v1/assistant/config` enables it for the user, a header button (with unread badge), `Ctrl/⌘+J` and a command palette "Ask Assistant" item open a chat panel. It docks beside the page from 1024 px (resizable 360-640 px, its own scrolling message list) and is a full-height sheet below that. Replies stream over SSE with resume after a dropped connection and a Stop button; tool steps, record chips, records/record/count/history renders and chart widgets come from structured payloads, and markdown renders without raw HTML or images, with links only to same-origin `/app` paths. `?assistant=open&assistantPrompt=...` opens a new conversation with the prompt pre-filled. Without the backend nothing renders and the shortcut is inert. The panel code loads on first open (about 1.7 KB added to first load).
- **Search** ([PR59](https://github.com/QRun-IO/qqq-frontend-next/pull/59)): search-as-you-type quick search with richer results (highlights, details, per-table status from QQQ 4.1.0-RC.4 record search) and a URL-addressable search page; the search page and previews load on demand.
- **SQL Workbench dark mode and standard controls** ([PR78](https://github.com/QRun-IO/qqq-frontend-next/pull/78)).
## Product fixes
- **Readable dashboard widget widths** ([PR55](https://github.com/QRun-IO/qqq-frontend-next/pull/55)): per-widget minimum widths, ultra-wide caps and a table card layout.
- **Record developer view** (`/app/{table}/{id}/dev`): the record screen's navigation (back to the record, record title, Edit, Actions, Audit, breadcrumbs) and a sticky section bar (a section picker on phones, wrapping links elsewhere) over Fields (display and raw values, types, possible-value sources and adornments, filterable), **API Access** (the v1 routes this screen uses and every application API that exposes the table, read from its OpenAPI spec and limited to what the user may do, with copyable curl, fetch and Java examples built from the record's values, placeholder credentials only, and links to the API docs and specs), **developer panels** from QQQ's provider contract ([qqq#1076](https://github.com/QRun-IO/qqq/pull/1076): record security, ESB events, and any qbit's panel such as quick search, SQL workbench or the assistant, shown only when installed and active for the table; QQQ before 4.1.0-RC.4 has no panels endpoint, so the view shows none and asks for it only once per session), Relations, recent Audit, associated scripts, raw JSON and a searchable Table Metadata explorer. Readable from 360 to 3200px with no sideways scrolling. The view loads on demand, keeping the route's first-load JavaScript at 333.5 KB (budget 360 KB).
- One scroll container per page: on long pages the document no longer grows a second scrollbar beside the content area's (and no longer slides the header off-screen). The app shell and `<main>` are now positioned, so absolutely positioned descendants such as `sr-only` text (the company footer's "opens in a new tab") stay inside the scrolling area instead of stretching the document. Covered by `tests/e2e/single-scroll-container.spec.ts` at 390-3200px.
## Browser testing and infrastructure
- Dependabot keeps at most three open PRs per ecosystem ([PR77](https://github.com/QRun-IO/qqq-frontend-next/pull/77)).
- The WebKit NAV-056 diagnostic race is fixed in the acceptance tests ([PR63](https://github.com/QRun-IO/qqq-frontend-next/pull/63)).
- README: "Adopting in another app" ([PR58](https://github.com/QRun-IO/qqq-frontend-next/pull/58)).
## Bundle budget
Owner decision 2026-10-12: the total JavaScript cap (every on-demand feature) is 1100 KB gzip and grows with features; first-load route caps, the 23 KB shared CSS cap and the lazy-chunk caps stay strict. See `perf-budget.json`.
## Verification checkpoint
PR checks on the RC15 preparation source (typecheck, lint, unit tests and coverage, mocked e2e, bundle budgets, Storybook, image builds and the real-backend acceptance workflow) are recorded on the preparation PR. Tag acceptance and public-artifact verification are recorded in the RC15 GitHub release after delivery. No RC14 test count or artifact digest is attributed to RC15.
## Remaining RC limits
The RC14 limits carry forward unchanged: Material parity, sub-path hosting, real Auth0/Drive/QuickSight verification, visual review and application soak remain tracked in [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+35
View File
@@ -0,0 +1,35 @@
# Next UI 1.0.0-RC.16 — preparation
At this preparation checkpoint RC16 **is not yet published**. [RC15](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.15) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.16` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.16`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `6dea1c3` (merge of [PR80](https://github.com/QRun-IO/qqq-frontend-next/pull/80)). Merged since RC15: [PR79](https://github.com/QRun-IO/qqq-frontend-next/pull/79) (resizable desktop navigation), [PR84](https://github.com/QRun-IO/qqq-frontend-next/pull/84) (search preview thumbnails), [PR86](https://github.com/QRun-IO/qqq-frontend-next/pull/86) (CSS budget 24 KB), [PR87](https://github.com/QRun-IO/qqq-frontend-next/pull/87) (range-error wording follows the backend), [PR85](https://github.com/QRun-IO/qqq-frontend-next/pull/85) (Alerts bell and record panel), [PR82](https://github.com/QRun-IO/qqq-frontend-next/pull/82) (date-first filters), [PR81](https://github.com/QRun-IO/qqq-frontend-next/pull/81) (quiet, metadata-driven form validation), [PR80](https://github.com/QRun-IO/qqq-frontend-next/pull/80) (record picker for large possible-value fields).
## Backend requirements
A published QQQ 4.1 candidate, **4.1.0-RC.1** or later; **4.1.0-RC.4** or later for record developer panels; **4.1.0-RC.5** or later for the record picker ([qqq#1099](https://github.com/QRun-IO/qqq/pull/1099)), the form validation limits it reads from metadata ([qqq#1100](https://github.com/QRun-IO/qqq/pull/1100)) and the date filter `StartOfPeriod` presets ([qqq#1115](https://github.com/QRun-IO/qqq/pull/1115)); on earlier backends these features fall back to the RC15 behavior. The Alerts bell and record panel render only when the instance metadata includes the `alerts` app ([qbit-alerts](https://github.com/QRun-IO/qbit-alerts) 0.1.0-RC.1 or later); otherwise they send no requests. RC16 does not publish QQQ 4.1.
## Features
- **Record picker** ([PR80](https://github.com/QRun-IO/qqq-frontend-next/pull/80)): possible-value fields over large tables open a searchable, filterable, paged picker dialog (lazy-loaded, with thumbnails where the table has images).
- **Form validation** ([PR81](https://github.com/QRun-IO/qqq-frontend-next/pull/81)): quiet, metadata-driven validation (required fields, too-long behavior, rejecting value ranges) before any request; server messages are still shown once.
- **Date-first filters** ([PR82](https://github.com/QRun-IO/qqq-frontend-next/pull/82)): date and date-time filters with presets, an include-time toggle, and the `X-QQQ-UserTimezone` header.
- **Alerts bell and record panel** ([PR85](https://github.com/QRun-IO/qqq-frontend-next/pull/85)): a header bell and a record panel for qbit-alerts, gated on the `alerts` app in metadata.
- **Resizable navigation** ([PR79](https://github.com/QRun-IO/qqq-frontend-next/pull/79)) and **search preview thumbnails** ([PR84](https://github.com/QRun-IO/qqq-frontend-next/pull/84)).
## Product fixes
- Range errors use the backend wording ([PR87](https://github.com/QRun-IO/qqq-frontend-next/pull/87)).
## Bundle budget
Shared CSS cap 24 KB ([PR86](https://github.com/QRun-IO/qqq-frontend-next/pull/86), measured 23.07 KB after PR79); total JavaScript cap 1100 KB (owner decision 2026-10-12). Route and lazy-chunk caps are unchanged.
## Verification checkpoint
Each merged PR passed real-backend acceptance (Chromium, Firefox, WebKit, mobile/tablet) on its own head against QQQ develop with the 4.1.0-RC.5 batch. PR checks on the RC16 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC16 GitHub release after delivery.
## Remaining RC limits
The RC15 limits carry forward unchanged: Material parity, sub-path hosting, real Auth0/Drive/QuickSight verification, visual review and application soak remain tracked in [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.17 — preparation
At this preparation checkpoint RC17 **is not yet published**. [RC16](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.16) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.17` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.17`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `6ed2575` (merge of [PR89](https://github.com/QRun-IO/qqq-frontend-next/pull/89)). Merged since RC16: [PR89](https://github.com/QRun-IO/qqq-frontend-next/pull/89) (notification center).
## Backend requirements
Unchanged from RC16 (QQQ **4.1.0-RC.5** or later for the record picker, validation limits and date presets). The notification center renders only when the instance metadata includes the `alerts` app, and needs [qbit-alerts](https://github.com/QRun-IO/qbit-alerts) **0.1.0-RC.2** or later (user notifications); without it nothing renders and no requests are sent. RC17 does not publish QQQ 4.1.
## Features
- **Notification center** ([PR89](https://github.com/QRun-IO/qqq-frontend-next/pull/89)): a bell inbox and notification list with read state and preferences for qbit-alerts user notifications. The center loads on demand (lazy chunk `notificationCenter`, 15.2 KB gzip).
## Bundle budget
**Both the total JavaScript and the first-load budgets are within 2 KB of their caps:** total JS 1098.0 / 1100 KB and `/app` first load 349.2 / 350 KB. The next Next feature needs a measured cap raise (under the owner decision of 2026-10-12) or a code-splitting pass first.
## Verification checkpoint
PR89 passed real-backend acceptance on Chromium, Firefox, WebKit and mobile/tablet. PR checks on the RC17 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC17 GitHub release after delivery.
## Remaining RC limits
The RC16 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+32
View File
@@ -0,0 +1,32 @@
# Next UI 1.0.0-RC.18 — preparation
At this preparation checkpoint RC18 **is not yet published**. [RC17](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.17) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.18` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.18`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `0282328` (merge of [PR94](https://github.com/QRun-IO/qqq-frontend-next/pull/94)). Merged since RC17: [PR91](https://github.com/QRun-IO/qqq-frontend-next/pull/91) (bundle budget report-only (owner decision)), [PR97](https://github.com/QRun-IO/qqq-frontend-next/pull/97) (faster sharded acceptance CI), [PR98](https://github.com/QRun-IO/qqq-frontend-next/pull/98) (budget script honors "enforce": false), [PR95](https://github.com/QRun-IO/qqq-frontend-next/pull/95) (Authentik policy editor widget), [PR94](https://github.com/QRun-IO/qqq-frontend-next/pull/94) (assistant pending-action card, persona and refresh after a write).
## Backend requirements
Unchanged from RC17 (QQQ **4.1.0-RC.5** or later). The Authentik policy editor needs [qbit-authentik](https://github.com/QRun-IO/qbit-authentik) **0.1.0-RC.2** or later (cut-over tooling in RC.3). The assistant pending-action card and persona need [qbit-assistant](https://github.com/QRun-IO/qbit-assistant) **0.1.0-RC.6** or later. Each renders only when its backend is installed. RC18 does not publish QQQ 4.1.
## Features
- **Authentik policy editor** ([PR95](https://github.com/QRun-IO/qqq-frontend-next/pull/95)): the `AUTHENTIK_POLICY_EDITOR` widget for qbit-authentik policies.
- **Assistant pending-action card and persona** ([PR94](https://github.com/QRun-IO/qqq-frontend-next/pull/94)): the assistant panel shows proposed actions as a pending-action card, adds a persona, and refreshes the page after a write.
## Browser testing and infrastructure
- Faster sharded real-backend acceptance with a quarantine lane and an acceptance gate ([PR97](https://github.com/QRun-IO/qqq-frontend-next/pull/97)).
## Bundle budget
The bundle budget is **report-only** (owner decision 2026-10-13, [PR91](https://github.com/QRun-IO/qqq-frontend-next/pull/91)): `perf-budget.json` has `"enforce": false`, and [PR98](https://github.com/QRun-IO/qqq-frontend-next/pull/98) makes the script honor it. Size overruns print as warnings with every number still reported, while structural problems (a lazy chunk on a first load, a missing chunk marker) still fail. Total JavaScript is now above the 1100 KB reference value.
## Verification checkpoint
PR94 and PR95 passed real-backend acceptance. PR checks on the RC18 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC18 GitHub release after delivery.
## Remaining RC limits
The RC17 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+28
View File
@@ -0,0 +1,28 @@
# Next UI 1.0.0-RC.19 — preparation
At this preparation checkpoint RC19 **is not yet published**. [RC18](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.18) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.19` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.19`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `a2f2318` (merge of [PR101](https://github.com/QRun-IO/qqq-frontend-next/pull/101)). Merged since RC18: [PR96](https://github.com/QRun-IO/qqq-frontend-next/pull/96) (related-record picker: single, multi-child and link modes), [PR101](https://github.com/QRun-IO/qqq-frontend-next/pull/101) (assistant Read-only / Write switch per conversation).
## Backend requirements
Unchanged from RC18 (QQQ **4.1.0-RC.5** or later; the related-record picker builds on the RC.5 picker contract, qqq#1099). The Read-only / Write switch needs qbit-assistant **0.1.0-RC.6** or later. RC19 does not publish QQQ 4.1.
## Features
- **Related-record picker** ([PR96](https://github.com/QRun-IO/qqq-frontend-next/pull/96)): pick related records in single, multi-child and link modes, built on the media picker dialog.
- **Assistant Read-only / Write switch** ([PR101](https://github.com/QRun-IO/qqq-frontend-next/pull/101)): each conversation can be switched between read-only and write.
## Bundle budget
Report-only (owner decision 2026-10-13): sizes are measured and reported; structural problems still fail.
## Verification checkpoint
PR96 and PR101 passed real-backend acceptance. PR checks on the RC19 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC19 GitHub release after delivery.
## Remaining RC limits
The RC18 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+64
View File
@@ -0,0 +1,64 @@
# Next UI 1.0.0-RC.2
Release candidate for testing in real applications. Final 1.0 remains on hold for owner testing and explicit approval. RC1 remains unchanged.
## Install
```xml
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.2</version>
</dependency>
```
Override an older BOM-managed frontend version. Select Next with `-Dqqq.javalin.frontend=next`. The JAR serves the UI at `/` alongside `/qqq/v1`; no Node server is required.
Container: `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.2` (linux/amd64 and linux/arm64). This quickstart image uses the build-time backend origin `http://host.docker.internal:8000`; use the Maven artifact or rebuild the image for a different backend origin.
## Changes since RC1
- Report previews support backend table variants consistently across query, count, statistics and full-result copy. Variant switches clear stale preview state and cannot let an old copy request overwrite the clipboard.
- Report previews honor query and count capabilities independently, including Refresh, and stop showing cached data/errors when a capability is removed.
- Report filter editors restore saved-view reset behavior and show active filters in column headers. Statistics and CSV checks compare results with independent SQL.
- Configured Google Drive folder selection replaces the placeholder, with backend metadata/CSP prerequisites and controlled-provider tests. Real-provider verification retains its documented external-service limitation.
- Script editing provides local QQQ helper, keyword and file-identifier suggestions, with keyboard/touch selection and native undo/redo behavior.
- Copy controls align with the form. Selected record sections retain the original Next full-width card and responsive field columns when metadata omits widths; explicit widths still take precedence.
- Pinned table selection cells expose the full checkbox touch target at every density.
- Child-record CSV export returns to the original Next header position with one working control. Export respects content visibility/permissions, and widget render failures can retry with fresh backend data.
## Backend requirements
Use the QQQ 4.1.0-SNAPSHOT development backend with PR #913 (`cba758507dcc54cf282a6638ab50a7d3a1904c7c`) for report variants and concurrent metadata handling, and PR #908 (`bac663044`) for configured Google Drive selection. The primary CI run used backend `cba758507dcc54cf282a6638ab50a7d3a1904c7c`. This release does not include a QQQ 4.1 release; record the backend commit used in your application.
## Verification
- Source: `0905cfb0cd046fcc98b058cd0b489e7bb00d5fbd`; signed tag `v1.0.0-RC.2`.
- **563 real-backend Chromium tests passed**, covering all 390 required rows, with zero failures, skips or flaky results. The CI merge checkout and tagged source have identical file trees. [Primary CI evidence](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36640179043).
- **1,857 unit tests passed**. Types, lint, coverage, Storybook, mocked Chromium tests, bundle budgets and CircleCI passed. [General CI](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36640179012).
- Public Maven Central JAR, POM, checksum and signature verified. A fresh, independent Maven consumer resolved the identical JAR. License, route assets, RC2 marker and absence of the mock service worker verified. All 77 application/runtime JavaScript chunks match primary CI byte-for-byte; generated build IDs and unused CSS utilities differ between build environments.
- The actual downloaded JAR passed five Chromium workflow checks: Sleep Interactive, parent CSV export, child CSV export, count-only report refresh, and filtered report statistics/CSV. These used an isolated local fixture backend; they supplement the full primary CI run.
- Both container architectures published; anonymous manifest retrieval verified. The actual downloaded arm64 image passed HTML and referenced-JavaScript runtime checks. [Image publication](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36642612472).
Maven JAR SHA-256: `872d7175feb98dab7d10fd50c659066449ce6801dc511011ead89438b6ef226a`.
Container digest: `sha256:dfd1b793c947a17e3c9f43473e4b8dfc12c5e45c8c1a9b57800bcbd6c3912b64`.
## Known limitations
- Hosting beneath a URL base path remains unresolved; evaluate at the origin root.
- Full embedded report/query equivalence, remaining input-block configurations, and unsupported Material icon names remain open.
- The existing 1,621-image review is complete, but missing states, scroll positions, themes and historical provenance still limit whole-product visual approval. The reference is the original Next UI design.
- Real Google Drive provider verification retains its external-service limitation.
- On this source's PR CI run, WebKit passed 560 tests and failed three: QRY-030 density-menu interaction, NAV-026 navigation widget-request access-control errors, and NAV-056 appearance-preference query-request access-control errors. Traces are retained and causes are not declared resolved. Firefox passed 562 tests and failed REC-048: the expected validation toast was visible, but the subsequent one-toast count observed zero. Mobile passed all 538 tests. Tablet passed 536 and failed two: QRY-030 density-menu interaction and REL-005, where the child list did not show the renamed record within the assertion timeout. These failure causes remain under investigation. No blanket cross-browser pass is claimed.
- The [completed tag acceptance run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36642612560) independently passed **563 Chromium tests / 390 required rows** and **538 mobile tests**, without skips or flaky results. WebKit passed 561 and failed QRY-030 (density menu) and RPT-012 (internal navigation error opening a saved report). Firefox passed 561 and failed PRC-061 (bulk-load default choice did not remain selected) and INT-011 (expected failed-save feedback was absent). Tablet passed 537 and failed QRY-030 (density menu). All traces are retained; these causes remain under investigation. The overall tag run failed because of these secondary-browser results.
- Confirmed after publication: multiline `TEXT` fields do not apply configured uppercase/lowercase behavior while editing. A fix and regression tests are in progress for the next candidate; RC2 does not contain that fix.
These are candidate limitations, not waived final-release requirements. See the [compatibility ledger](https://github.com/QRun-IO/qqq-frontend-next/blob/0905cfb0cd046fcc98b058cd0b489e7bb00d5fbd/docs/acceptance/material-parity.md) and [browser matrix](https://github.com/QRun-IO/qqq-frontend-next/blob/0905cfb0cd046fcc98b058cd0b489e7bb00d5fbd/docs/acceptance/browser-matrix.md) for prior checkpoints.
## Guides and issue reports
[Developer guide](https://github.com/QRun-IO/qqq-frontend-next/blob/0905cfb0cd046fcc98b058cd0b489e7bb00d5fbd/docs/guides/next-ui-developer-guide.md) · [Agent guide](https://github.com/QRun-IO/qqq-frontend-next/blob/0905cfb0cd046fcc98b058cd0b489e7bb00d5fbd/docs/guides/next-ui-agent-guide.md) · [Report an issue](https://github.com/QRun-IO/qqq-frontend-next/issues/new).
Include frontend RC/commit, backend version/commit, browser, page/workflow, relevant metadata, reproduction steps, expected/actual results, and sanitized screenshots or errors. State whether the issue persists after reload.
+34
View File
@@ -0,0 +1,34 @@
# Next UI 1.0.0-RC.20 — preparation
At this preparation checkpoint RC20 **is not yet published**. [RC19](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.19) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.20` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.20`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `438eda3` (merge of [PR111](https://github.com/QRun-IO/qqq-frontend-next/pull/111)). Merged since RC19: [PR92](https://github.com/QRun-IO/qqq-frontend-next/pull/92) (UI API version contract), [PR104](https://github.com/QRun-IO/qqq-frontend-next/pull/104) (UI API contract picker paths), [PR109](https://github.com/QRun-IO/qqq-frontend-next/pull/109) (acceptance ESB extension fix), [PR103](https://github.com/QRun-IO/qqq-frontend-next/pull/103) (data-analysis column and table profile panels), [PR105](https://github.com/QRun-IO/qqq-frontend-next/pull/105) (favorites, server recents and command-palette favorites), [PR111](https://github.com/QRun-IO/qqq-frontend-next/pull/111) (readable assistant answers: GFM markdown and presentation blocks).
## Backend requirements
QQQ **4.1.0-RC.5** or later for everything in RC19. The new screens use the UI API 1.2 contract and the collaboration and data-analysis cores from QQQ **4.1.0-RC.6**: the profile panels need the data-analysis aggregates (qqq#1139, qqq#1145), and favorites and server recents need the collaboration core (qqq#1138 and follow-ups). Assistant presentation blocks need qbit-assistant **0.1.0-RC.9** or later. RC20 does not publish QQQ 4.1.
## Features
- **Readable assistant answers** ([PR111](https://github.com/QRun-IO/qqq-frontend-next/pull/111)): GFM markdown (tables, task lists) and presentation blocks from qbit-assistant 0.1.0-RC.9.
- **Data-analysis profile panels** ([PR103](https://github.com/QRun-IO/qqq-frontend-next/pull/103)): column and table profiles with drill-down.
- **Favorites and recents** ([PR105](https://github.com/QRun-IO/qqq-frontend-next/pull/105)): favorites, server-side recents and palette favorites.
- **UI API version contract** ([PR92](https://github.com/QRun-IO/qqq-frontend-next/pull/92), [PR104](https://github.com/QRun-IO/qqq-frontend-next/pull/104)).
## Browser testing and infrastructure
- Acceptance ESB extension fix ([PR109](https://github.com/QRun-IO/qqq-frontend-next/pull/109)).
## Bundle budget
Report-only (owner decision 2026-10-13): sizes are measured and reported; structural problems still fail.
## Verification checkpoint
The merged PRs passed real-backend acceptance. PR checks on the RC20 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC20 GitHub release after delivery.
## Remaining RC limits
The RC19 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+29
View File
@@ -0,0 +1,29 @@
# Next UI 1.0.0-RC.21 — preparation
> **RC21 was never published.** Its tag build failed the UI API off-spec ceiling test, so the Maven Central and GHCR publishes were cancelled. Its changes ship in [RC22](1.0.0-RC.22.md).
At this preparation checkpoint RC21 **is not yet published**. [RC20](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.20) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.21` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.21`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `a6c95b1` (merge of [PR125](https://github.com/QRun-IO/qqq-frontend-next/pull/125)). Merged since RC20: [PR125](https://github.com/QRun-IO/qqq-frontend-next/pull/125) (attach a file in the assistant panel for bulk load).
## Backend requirements
QQQ **4.1.0-RC.6** or later for everything in RC20. The assistant file attach feeds assisted bulk load: it uses the bulk-load UI API operations from QQQ **4.1.0-RC.7** (qqq#1179) and the bulk-load tools in qbit-assistant **0.1.0-RC.10** (qbit-assistant#38). The upload itself uses `POST /qqq/v1/uploads` from [qqq#1181](https://github.com/QRun-IO/qqq/pull/1181), which is not yet in a QQQ release; until it ships, an attach attempt fails with a toast saying the file could not be attached, and nothing else changes. RC21 does not publish QQQ 4.1.
## Features
- **Assistant file attach** ([PR125](https://github.com/QRun-IO/qqq-frontend-next/pull/125)): attach a file in the assistant panel to start an assisted bulk load.
## Bundle budget
Report-only (owner decision 2026-10-13): sizes are measured and reported; structural problems still fail.
## Verification checkpoint
PR checks on the RC21 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC21 GitHub release after delivery.
## Remaining RC limits
The RC20 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+38
View File
@@ -0,0 +1,38 @@
# Next UI 1.0.0-RC.22 — preparation
At this preparation checkpoint RC22 **is not yet published**. [RC20](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.20) remains the latest verified public candidate and is immutable. RC21 was tagged but never published (its tag build failed the off-spec ceiling test and publishing was cancelled); its content is included here. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.22` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.22`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `48e81b4` (merge of [PR128](https://github.com/QRun-IO/qqq-frontend-next/pull/128)). Merged since RC20: [PR113](https://github.com/QRun-IO/qqq-frontend-next/pull/113) (collaboration record panel: comments, files, activity, tags and follow), [PR128](https://github.com/QRun-IO/qqq-frontend-next/pull/128) (collaboration: realtime presence, conflict review, sharing, tasks and My work), [PR106](https://github.com/QRun-IO/qqq-frontend-next/pull/106) (data-analysis pivot / group-by explorer), [PR118](https://github.com/QRun-IO/qqq-frontend-next/pull/118) (data-analysis compare, correlation, crosstab and shift explorers), [PR123](https://github.com/QRun-IO/qqq-frontend-next/pull/123) (data-analysis save, share, star, export and watch), [PR126](https://github.com/QRun-IO/qqq-frontend-next/pull/126) (run processes in background, background-job banner, widget refresh), [PR125](https://github.com/QRun-IO/qqq-frontend-next/pull/125) (attach a file in the assistant panel for bulk load), [PR132](https://github.com/QRun-IO/qqq-frontend-next/pull/132) (vendored alerts and sql-workbench extension documents, off-spec 46 to 21), [PR131](https://github.com/QRun-IO/qqq-frontend-next/pull/131) (off-spec ceiling for POST /uploads), [PR129](https://github.com/QRun-IO/qqq-frontend-next/pull/129) (chart data table overflow and doubled widget section headings).
## Backend requirements
QQQ **4.1.0-RC.6** or later. The collaboration screens need qbit-collaboration **0.1.0-RC.1** or later. The data-analysis explorers need qbit-data-analysis **0.1.0-RC.1** or later (extension 1.4.0). Assistant file attach uses the bulk-load UI API operations in QQQ **4.1.0-RC.7** and the tools in qbit-assistant **0.1.0-RC.10**; the upload endpoint (qqq#1181) is not yet released, so an attach attempt fails with a toast until it ships. Background process runs appear only when the server advertises `durableJobEligible` (qqq#1178 and qqq#1181); against older servers nothing new shows. RC22 does not publish QQQ 4.1.
## Features
- **Collaboration** ([PR113](https://github.com/QRun-IO/qqq-frontend-next/pull/113), [PR128](https://github.com/QRun-IO/qqq-frontend-next/pull/128)): record panel (comments, files, activity), tags and follow; realtime presence, conflict review, sharing, tasks and My work.
- **Data analysis** ([PR106](https://github.com/QRun-IO/qqq-frontend-next/pull/106), [PR118](https://github.com/QRun-IO/qqq-frontend-next/pull/118), [PR123](https://github.com/QRun-IO/qqq-frontend-next/pull/123)): pivot / group-by, compare, correlation, crosstab and shift explorers; save, share, star, export and watch.
- **Background process runs** ([PR126](https://github.com/QRun-IO/qqq-frontend-next/pull/126)): run in background, background-job banner, widget refresh.
- **Assistant file attach** ([PR125](https://github.com/QRun-IO/qqq-frontend-next/pull/125)) for assisted bulk load.
## Product fixes
- Chart data table overflow and doubled widget section headings ([PR129](https://github.com/QRun-IO/qqq-frontend-next/pull/129)).
## UI API contract
- Vendored alerts and sql-workbench extension documents; the off-spec list drops from 46 to 21 ([PR132](https://github.com/QRun-IO/qqq-frontend-next/pull/132), [PR131](https://github.com/QRun-IO/qqq-frontend-next/pull/131)).
## Bundle budget
Report-only (owner decision 2026-10-13): sizes are measured and reported; structural problems still fail.
## Verification checkpoint
PR checks on the RC22 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC22 GitHub release after delivery.
## Remaining RC limits
The RC20 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904). Collaboration task assignees show the user id until qbit-collaboration adds a display name.
+43
View File
@@ -0,0 +1,43 @@
# Next UI 1.0.0-RC.23 — preparation
At this preparation checkpoint RC23 **is not yet published**. [RC22](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.22) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.23` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.23`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `39b8f8b` (merge of [PR127](https://github.com/QRun-IO/qqq-frontend-next/pull/127)). Merged since RC22: [PR127](https://github.com/QRun-IO/qqq-frontend-next/pull/127) (help everywhere: help icons, long-help panel, help search, author links), [PR121](https://github.com/QRun-IO/qqq-frontend-next/pull/121) (Preferences page, notification settings drawer and section actions), [PR120](https://github.com/QRun-IO/qqq-frontend-next/pull/120) (contingent possible values across forms, bulk edit, processes and filters), [PR143](https://github.com/QRun-IO/qqq-frontend-next/pull/143) (typeahead suggestions from qbit-quick-search in the global search box), [PR140](https://github.com/QRun-IO/qqq-frontend-next/pull/140) (authentik policy editor feature permissions and member preview), [PR138](https://github.com/QRun-IO/qqq-frontend-next/pull/138) (theme defaults to the OS color scheme), [PR144](https://github.com/QRun-IO/qqq-frontend-next/pull/144) (record section tabs fold into a More menu), [PR139](https://github.com/QRun-IO/qqq-frontend-next/pull/139) (breadcrumb without inner horizontal scroll), [PR146](https://github.com/QRun-IO/qqq-frontend-next/pull/146) (app-nested URLs redirect to their canonical path), [PR137](https://github.com/QRun-IO/qqq-frontend-next/pull/137) (realtime client isolated between tests), [PR68](https://github.com/QRun-IO/qqq-frontend-next/pull/68) (rapidoc 10.1.0), [PR99](https://github.com/QRun-IO/qqq-frontend-next/pull/99) (GitHub Actions updates).
## Backend requirements
Everything in RC22 needs QQQ **4.1.0-RC.6** or later. The new screens use QQQ **4.1.0-RC.7** features: help everywhere needs core typed help and help bundles (qqq#1177, qqq#1198); the Preferences page needs `/qqq/v1/preferences` (qqq#1162); contingent possible values need qqq#1175. Against RC.6 these screens stay hidden or fall back to the previous behaviour. Search typeahead needs qbit-quick-search **1.1.0-RC.8** or later (the `suggest` endpoint); without it the global search box behaves as before. The authentik policy editor additions need qbit-authentik 0.1.0-RC.4 or later. RC23 does not publish QQQ 4.1.
## Features
- **Help everywhere** ([PR127](https://github.com/QRun-IO/qqq-frontend-next/pull/127)): help icons, a long-help panel, help search and author links, driven by core typed help.
- **Preferences** ([PR121](https://github.com/QRun-IO/qqq-frontend-next/pull/121)): Preferences page, notification settings drawer and section actions.
- **Contingent possible values** ([PR120](https://github.com/QRun-IO/qqq-frontend-next/pull/120)) across forms, bulk edit, processes and filters.
- **Search typeahead** ([PR143](https://github.com/QRun-IO/qqq-frontend-next/pull/143)) from the qbit-quick-search `suggest` endpoint.
- **Theme** ([PR138](https://github.com/QRun-IO/qqq-frontend-next/pull/138)): defaults to the OS color scheme (System / Light / Dark).
- **Authentik** ([PR140](https://github.com/QRun-IO/qqq-frontend-next/pull/140)): policy editor feature permissions and member preview.
## Product fixes
- Record section tabs fold into a More menu instead of scrolling ([PR144](https://github.com/QRun-IO/qqq-frontend-next/pull/144)).
- Breadcrumb without inner horizontal scroll ([PR139](https://github.com/QRun-IO/qqq-frontend-next/pull/139)).
- App-nested URLs redirect to their canonical path ([PR146](https://github.com/QRun-IO/qqq-frontend-next/pull/146)).
## Browser testing and infrastructure
- The realtime client is isolated between tests, which fixes the metadata dedupe flake ([PR137](https://github.com/QRun-IO/qqq-frontend-next/pull/137)).
- Dependency updates: rapidoc 10.1.0 ([PR68](https://github.com/QRun-IO/qqq-frontend-next/pull/68)), GitHub Actions ([PR99](https://github.com/QRun-IO/qqq-frontend-next/pull/99)).
## Bundle budget
Report-only (owner decision 2026-10-13): sizes are measured and reported; structural problems still fail.
## Verification checkpoint
PR checks on the RC23 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC23 GitHub release after delivery.
## Remaining RC limits
The RC22 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+37
View File
@@ -0,0 +1,37 @@
# Next UI 1.0.0-RC.24 — preparation
At this preparation checkpoint RC24 **is not yet published**. [RC23](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.23) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.24` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.24`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `3ab4815` (merge of [PR150](https://github.com/QRun-IO/qqq-frontend-next/pull/150)). Merged since RC23: [PR149](https://github.com/QRun-IO/qqq-frontend-next/pull/149) (record section tabs wrap instead of folding into More; tablet regression from PR144), [PR150](https://github.com/QRun-IO/qqq-frontend-next/pull/150) (empty tables explain what they are for and how rows get created), [PR148](https://github.com/QRun-IO/qqq-frontend-next/pull/148) (Full access checkbox in the authentik policy editor), [PR145](https://github.com/QRun-IO/qqq-frontend-next/pull/145) (no automatic card grids on homes that declare widgets).
## Why RC24
RC23's tag real-backend acceptance failed on two tablet shards in both attempts (REC-004 record sections by touch and keyboard, REC-019 zoned date-times, NAV-015 document title). [PR149](https://github.com/QRun-IO/qqq-frontend-next/pull/149) restores wrapping section tabs (the More menu from PR144 is removed), deflakes NAV-015 and adds tablet to the full-acceptance label.
## Backend requirements
Unchanged from RC23: QQQ **4.1.0-RC.6** or later for everything in RC22, and **4.1.0-RC.7** for help everywhere, Preferences and contingent possible values. The Full access checkbox needs qbit-authentik **0.1.0-RC.5** or later (full-access `*` statement). Empty-table help uses core typed help (QQQ 4.1.0-RC.7); against older cores the table shows the previous empty state. RC24 does not publish QQQ 4.1.
## Features
- **Empty tables explain themselves** ([PR150](https://github.com/QRun-IO/qqq-frontend-next/pull/150)): what the table is for and how rows get created, from core help.
- **Full access** ([PR148](https://github.com/QRun-IO/qqq-frontend-next/pull/148)): a Full access checkbox in the authentik policy editor.
## Product fixes
- Record section tabs wrap instead of folding into a More menu; fixes the RC23 tablet regression ([PR149](https://github.com/QRun-IO/qqq-frontend-next/pull/149)).
- Homes that declare widgets no longer get automatic card grids ([PR145](https://github.com/QRun-IO/qqq-frontend-next/pull/145)).
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC24 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC24 GitHub release after delivery.
## Remaining RC limits
The RC23 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.25 — preparation
At this preparation checkpoint RC25 **is not yet published**. [RC24](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.24) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.25` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.25`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `70594c4` (merge of [PR151](https://github.com/QRun-IO/qqq-frontend-next/pull/151)). Merged since RC24: [PR151](https://github.com/QRun-IO/qqq-frontend-next/pull/151) (assistant: never block asking, re-attach after refresh, live progress and Stop).
## Backend requirements
Unchanged from RC24. The assistant changes pair with qbit-assistant **0.1.0-RC.13** (durable turns: lease, heartbeat, cross-pod Stop); with older assistants the panel behaves as before. RC25 does not publish QQQ 4.1.
## Features
- **Assistant panel** ([PR151](https://github.com/QRun-IO/qqq-frontend-next/pull/151)): asking is never blocked while a turn runs, the panel re-attaches to a running turn after refresh, shows live progress, and Stop is always available.
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC25 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC25 GitHub release after delivery.
## Remaining RC limits
The RC24 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.26 — preparation
At this preparation checkpoint RC26 **is not yet published**. [RC25](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.25) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.26` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.26`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `caa89dc` (merge of [PR142](https://github.com/QRun-IO/qqq-frontend-next/pull/142)). Merged since RC25: [PR142](https://github.com/QRun-IO/qqq-frontend-next/pull/142) (tables open with a short default column set).
## Backend requirements
Unchanged from RC25. RC26 does not publish QQQ 4.1.
## Features
- **Default list columns** ([PR142](https://github.com/QRun-IO/qqq-frontend-next/pull/142)): with no saved view, a table opens with a short default column set (primary key, then the first-tier section fields, capped at 6, skipping heavy, text, HTML, BLOB and JSON fields) instead of every field, so the grid no longer scrolls sideways. Fixes [#114](https://github.com/QRun-IO/qqq-frontend-next/issues/114) (kof22 admin UX audit, High).
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC26 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC26 GitHub release after delivery.
## Remaining RC limits
The RC25 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+29
View File
@@ -0,0 +1,29 @@
# Next UI 1.0.0-RC.27 — preparation
At this preparation checkpoint RC27 **is not yet published**. [RC26](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.26) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.27` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.27`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `427a037` (merge of [PR155](https://github.com/QRun-IO/qqq-frontend-next/pull/155)). Merged since RC26: [PR124](https://github.com/QRun-IO/qqq-frontend-next/pull/124) (secured-data UI: masked values, reveal, restricted filters, break-glass banner), [PR155](https://github.com/QRun-IO/qqq-frontend-next/pull/155) (live job view, header Jobs tray and Jobs page), [PR156](https://github.com/QRun-IO/qqq-frontend-next/pull/156) (large-table acceptance fixture declares its default columns).
## Backend requirements
Unchanged from RC26 for existing screens. The Jobs tray and live job view need QQQ **4.1.0-RC.8** (jobs UI API, UI API 1.4.0); against older cores the Jobs button stays hidden. The secured-data UI renders what QQQ **4.1.0-RC.7** field access tiers enforce and needs qbit-secured-data **0.1.0-RC.1** for reveal and break-glass. RC27 does not publish QQQ 4.1.
## Features
- **Secured-data UI** ([PR124](https://github.com/QRun-IO/qqq-frontend-next/pull/124)): masked values shown with a lock chip, reveal with reason, restricted filters, and a break-glass banner. The server masks values; the client never receives the real value.
- **Jobs** ([PR155](https://github.com/QRun-IO/qqq-frontend-next/pull/155)): a header Jobs button next to alerts, a live job view with progress, phases and log, the ability to leave a job running, and a Jobs page.
- Acceptance: the large-table fixture declares its 40 default columns after PR142 ([PR156](https://github.com/QRun-IO/qqq-frontend-next/pull/156)).
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC27 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC27 GitHub release after delivery.
## Remaining RC limits
The RC26 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.28 — preparation
At this preparation checkpoint RC28 **is not yet published**. [RC27](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.27) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.28` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.28`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `16699ae` (merge of [PR141](https://github.com/QRun-IO/qqq-frontend-next/pull/141)). Merged since RC27: [PR141](https://github.com/QRun-IO/qqq-frontend-next/pull/141) (zero off-spec UI API calls; OFF_SPEC_CEILING 0; minimum UI API 1.4.0).
## Backend requirements
**Requires QQQ 4.1.0-RC.9 or later** (UI API 1.4.0 operations; RC.8's `GET /table/{t}/apis` was shadowed by the record route, fixed in qqq#1216). RC28 does not publish QQQ 4.1.
## Features
- **Zero off-spec calls** ([PR141](https://github.com/QRun-IO/qqq-frontend-next/pull/141)): the form adjuster, table API discovery, table API documents and the remaining legacy routes now use spec'd `/qqq/v1` operations. `OFF_SPEC_CEILING` is 0.
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC28 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC28 GitHub release after delivery.
## Remaining RC limits
The RC27 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.29 — preparation
At this preparation checkpoint RC29 **is not yet published**. [RC28](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.28) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.29` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.29`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `abfbbb2` (merge of [PR136](https://github.com/QRun-IO/qqq-frontend-next/pull/136)). Merged since RC28: [PR136](https://github.com/QRun-IO/qqq-frontend-next/pull/136) (EDI mapper page on the qbit-edi edi extension, all calls on spec).
## Backend requirements
Requires QQQ **4.1.0-RC.9** or later (as RC28). The EDI mapper needs qbit-edi with the `edi` UI API extension (qbit-edi#15); without it the Open Mapper action stays hidden. RC29 does not publish QQQ 4.1.
## Features
- **EDI mapper** ([PR136](https://github.com/QRun-IO/qqq-frontend-next/pull/136)): an Open Mapper action on `edi_map` records opens a mapper page built on the vendored qbit-edi `edi` UI API extension, with generated types and an API client. Every call is on spec.
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC29 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC29 GitHub release after delivery.
## Remaining RC limits
The RC28 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+64
View File
@@ -0,0 +1,64 @@
# Next UI 1.0.0-RC.3
Release candidate for application testing. **Final 1.0 remains on hold for owner testing and explicit approval.** RC1 and RC2 remain immutable.
## Install
```xml
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.3</version>
</dependency>
```
Override an older BOM-managed frontend version. Select Next with `-Dqqq.javalin.frontend=next`. The JAR serves the UI at `/` alongside `/qqq/v1`; no Node server is required. If an earlier attempt cached a missing RC3 artifact, retry Maven with `-U`.
Container: `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.3` (linux/amd64 and linux/arm64). This quickstart image uses the build-time backend origin `http://host.docker.internal:8000`; use the Maven artifact or rebuild the image for a different backend origin.
## Changes since RC2
- Multiline TEXT fields rendered by the shared editor honor configured uppercase/lowercase behavior while typing and submit the displayed value. Enter remains a newline.
- Case conversion preserves cursor position when Unicode characters expand, including ß to SS and İ to i plus combining dot. Continued typing and backward selections retain the intended boundaries.
- Added standalone and process regression coverage with exact database readback. Developer and agent guides document these behaviors. Existing layout and styling are preserved.
## Verified artifacts and workflows
Source: `f1a6a3a16868c04d30a0a807f85ea34c7170c508`; signed tag `v1.0.0-RC.3`.
- Hosted real-backend Chromium: **570 tests and all 391 required feature rows passed**, with zero failures, skips or flaky results. Backend commit: `cba758507dcc54cf282a6638ab50a7d3a1904c7c`. The tested PR merge has an identical Git tree to the tagged source. [Candidate acceptance](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36652168977).
- **1,873 unit tests passed**, plus types, lint, unit coverage, mocked Chromium, bundle budgets and Storybook. [General CI](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36652168856).
- Public Maven JAR checksum and PGP signature verified; a separate consumer resolved the same JAR through normal Maven dependency resolution. The actual downloaded JAR passed seven Chromium case-conversion workflows with independent database readback. [Maven deployment](https://circleci.com/gh/QRun-IO/qqq-frontend-next/122).
- Public container manifest verified anonymously for both architectures. The actual arm64 image served the app route and all 21 referenced JavaScript assets. [Container publication](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36654410994).
Public JAR SHA-256:
`596d270893ab733376c7b2c39dfdf4d7bf5027cbb984e67ea95d9090df3e7109`
Container manifest digest:
`sha256:fe7141ded4a625bbfa67675945eff4fa6ba0e1562bf4563fe8fab8933ffee69a`
## Backend and known limitations
Use the QQQ 4.1.0-SNAPSHOT development backend including PR #913 (report variants/concurrent metadata handling) and PR #908 (configured Google Drive selection). Record your application's backend commit. This release does not release QQQ 4.1.
The exact candidate acceptance run is complete and failed overall. Results (zero skips or flaky results):
| Browser | Passed | Failed | Failure evidence |
| --- | ---: | ---: | --- |
| Chromium | 570 | 0 | All 391 required feature rows passed |
| Mobile | 545 | 0 | All tagged phone scenarios passed |
| Firefox | 569 | 1 | WID-005: Widget Gallery heading was not found within 15 seconds |
| WebKit | 568 | 2 | NAV-056: application-theme override reported access-control errors for application requests; QRY-030: density-menu coverage timed out |
| Tablet | 543 | 2 | QRY-030: density-menu option did not appear; REC-046: WebKit internal error navigating to the create route |
Reports and traces are retained; causes remain under investigation. The [tag-triggered acceptance run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36654410984) passed **570 Chromium tests and all 391 required rows**. Its WebKit job passed **568 tests and failed two**: NAV-016 site-root navigation reported application-request access-control errors; QRY-030 density-menu coverage timed out. Its Firefox job passed **568 tests and failed two**: QRY-007 did not observe the light-theme error toast within 15 seconds; WID-073 timed out waiting for the Render Report action. Its mobile job passed **545 tests**; tablet passed **543 and failed two**: NAV-026 navigation-drawer coverage reported widget-request access-control errors; QRY-030 density-menu coverage timed out. The tag run is complete and failed overall, with zero skips or flaky results. All reports and traces are retained; these failures remain open.
Full embedded report/query equivalence, remaining input configurations, unsupported Material icon names, origin sub-path hosting, real Google Drive provider verification and whole-product visual approval remain open. A separately verified follow-up fixes explicitly configured PASSWORD case behavior; it is not included in RC3. Ordinary password case is unchanged. Original Next UI remains the visual reference.
These limitations remain final-release requirements. Final 1.0 needs renewed owner approval after application testing.
## Guides and issue reports
[Developer guide](https://github.com/QRun-IO/qqq-frontend-next/blob/f1a6a3a16868c04d30a0a807f85ea34c7170c508/docs/guides/next-ui-developer-guide.md) · [Agent guide](https://github.com/QRun-IO/qqq-frontend-next/blob/f1a6a3a16868c04d30a0a807f85ea34c7170c508/docs/guides/next-ui-agent-guide.md) · [Report an issue](https://github.com/QRun-IO/qqq-frontend-next/issues/new).
Include frontend RC/commit, backend version/commit, browser, page/workflow, relevant metadata, reproduction steps, expected/actual results, and sanitized screenshots or errors. Never include credentials or sensitive records.
+34
View File
@@ -0,0 +1,34 @@
# Next UI 1.0.0-RC.30 — preparation
At this preparation checkpoint RC30 **is not yet published**. [RC29](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.29) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.30` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.30`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main after [PR160](https://github.com/QRun-IO/qqq-frontend-next/pull/160), which this preparation depends on. Merged since RC29: [PR160](https://github.com/QRun-IO/qqq-frontend-next/pull/160) (optional Jam QA capture on the qbit-qa-capture `qa-capture` extension, all calls on spec).
## Backend requirements
RC30 needs QQQ **4.1.0-RC.9** or later, the same as RC29. QA capture also needs qbit-qa-capture with the `qa-capture` 1.0.0 UI API extension. Without it the UI makes no QA capture call and loads no Jam code. The vendored `qa-capture.json` must match the published QBit fragment before tagging. RC30 does not publish QQQ 4.1.
## Features
- **QA capture (Jam)** ([PR160](https://github.com/QRun-IO/qqq-frontend-next/pull/160)), shown only to users the QBit enables:
- Jam's Recording Links SDK is loaded on demand and initialized with sanitizers built from the server's rules (headers, JSON and form fields, query parameters, value patterns, dropped paths, fail closed).
- Jam receives metadata with the page context and the last request ids.
- "Report a bug" and "QA reports" are in the user menu, and a "QA mode on" badge appears in the header.
- Password, reveal and secured-data values are blurred in recordings.
- Recording Link `jam-*` parameters are kept through sign-in.
- The standalone image adds the Jam CSP origins when `QQQ_QA_CAPTURE_CSP=true`.
- Dependencies: `@jam.dev/recording-links` 0.4.0 and `@jam.dev/sdk` 1.0.1, pinned. See [QA capture](../QA-CAPTURE.md).
## Bundle budget
Report-only (owner decision 2026-10-13). PR160 measured +3.4 to +4.2 KB gzip first-load JS per route and +9.2 KB gzip total JS. The Jam chunks load only for enabled users.
## Verification checkpoint
PR checks on the RC30 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC30 GitHub release after delivery. Acceptance row SEC-050 (QA capture) is excluded pending owner approval until qbit-qa-capture has a real-backend fixture; Vitest covers it meanwhile.
## Remaining RC limits
The RC29 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.31 — preparation
At this preparation checkpoint RC31 **is not yet published**. [RC30](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.30) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.31` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.31`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `9aff198` (merge of [PR162](https://github.com/QRun-IO/qqq-frontend-next/pull/162)). Merged since RC30: [PR162](https://github.com/QRun-IO/qqq-frontend-next/pull/162) (adopt QQQ UI API 1.6.0: clientCapabilities, field validation, error schema, v1 branding).
## Backend requirements
**Requires QQQ 4.1.0-RC.15 or later** (UI API 1.6.0). RC31 does not publish QQQ 4.1.
## Features
- **UI API 1.6.0** ([PR162](https://github.com/QRun-IO/qqq-frontend-next/pull/162)): vendors the core 1.6.0 and API-aware documents and the updated qbit extensions (alerts 1.2.0, assistant 1.3.0, collaboration 1.1.0, data-analysis 1.5.0, edi 1.1.0, esb 1.1.0, quick-search 1.1.0, secured-data 1.1.0, sql-workbench 1.1.0); uses table client capabilities, server field validation, the single error schema and v1 branding. The off-spec ceiling stays 0.
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC31 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC31 GitHub release after delivery.
## Remaining RC limits
The RC30 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.32 — preparation
At this preparation checkpoint RC32 **is not yet published**. [RC31](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.31) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.32` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.32`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `b24b74e` (merge of [PR165](https://github.com/QRun-IO/qqq-frontend-next/pull/165)). Merged since RC31: [PR165](https://github.com/QRun-IO/qqq-frontend-next/pull/165) (What's new page and user-menu entry, release-notes UI API 1.0.0).
## Backend requirements
Requires QQQ **4.1.0-RC.15** or later for the UI API 1.6.0 features (as RC31). What's new needs qbit-release-notes **0.1.0-RC.1** (release-notes UI API extension 1.0.0); without it the feature hides itself. RC32 does not publish QQQ 4.1.
## Features
- **What's new** ([PR165](https://github.com/QRun-IO/qqq-frontend-next/pull/165)): an `/app/whats-new` page and a user-menu entry with an unread count, on the release-notes UI API extension 1.0.0 (vendored). The page is a separate lazy route; off-spec calls stay at 0.
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC32 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC32 GitHub release after delivery.
## Remaining RC limits
The RC31 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+31
View File
@@ -0,0 +1,31 @@
# Next UI 1.0.0-RC.33 — preparation
At this preparation checkpoint RC33 **is not yet published**. [RC32](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.32) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.33` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.33`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `b4b59ce` (merge of [PR170](https://github.com/QRun-IO/qqq-frontend-next/pull/170)). Merged since RC32: [PR168](https://github.com/QRun-IO/qqq-frontend-next/pull/168) (sidebar sections and favorites, one search, new header, single-key shortcut switch), [PR170](https://github.com/QRun-IO/qqq-frontend-next/pull/170) (empty assistant query results no longer crash the page).
## Backend requirements
Requires QQQ **4.1.0-RC.15** or later for the UI API 1.6.0 features (as RC32). The navigation works against any 1.x server (it infers section roles when absent); QQQ **4.1.0-RC.16** (UI API 1.7.0) adds the server-declared section roles, section apps and search keywords. RC33 does not publish QQQ 4.1.
## Features
- **Navigation** ([PR168](https://github.com/QRun-IO/qqq-frontend-next/pull/168)): sidebar sections and favorites, one search, a header with Search, Ask, Jobs, Notifications, Help and Account, a single-key shortcut switch and Shift+D.
## Product fixes
- Empty assistant query results no longer crash the page ([PR170](https://github.com/QRun-IO/qqq-frontend-next/pull/170)).
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC33 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC33 GitHub release after delivery.
## Remaining RC limits
The RC32 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.34 — preparation
At this preparation checkpoint RC34 **is not yet published**. [RC33](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.33) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.34` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.34`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `68a2b9d` (merge of [PR172](https://github.com/QRun-IO/qqq-frontend-next/pull/172)). Merged since RC33: [PR172](https://github.com/QRun-IO/qqq-frontend-next/pull/172) (layered error boundaries, zod payload repair, streaming can no longer lock the assistant panel).
## Backend requirements
Requires QQQ **4.1.0-RC.15** or later for the UI API 1.6.0 features (as RC33). The navigation works against any 1.x server (it infers section roles when absent); QQQ **4.1.0-RC.16** (UI API 1.7.0) adds the server-declared section roles, section apps and search keywords. RC34 does not publish QQQ 4.1.
## Product fixes
- **Fail soft** ([PR172](https://github.com/QRun-IO/qqq-frontend-next/pull/172)): layered error boundaries keep a failing widget, section or panel from taking down the page; assistant payloads are validated and repaired with zod; a stream can no longer lock the assistant panel. Pairs with qbit-assistant 0.1.0-RC.22 (fail-soft server, extension 1.5.0).
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC34 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC34 GitHub release after delivery.
## Remaining RC limits
The RC33 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.35 — preparation
At this preparation checkpoint RC35 **is not yet published**. [RC34](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.34) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.35` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.35`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `ba38367` (merge of [PR164](https://github.com/QRun-IO/qqq-frontend-next/pull/164)). Merged since RC34: [PR164](https://github.com/QRun-IO/qqq-frontend-next/pull/164) (profile photo uploader and avatars everywhere, UI API 1.8.0).
## Backend requirements
Requires QQQ **4.1.0-RC.15** or later for the UI API 1.6.0 features (as RC34). Profiles and avatars need QQQ **4.1.0-RC.17** (UI API 1.8.0). RC35 does not publish QQQ 4.1.
## Features
- **Profiles and avatars** ([PR164](https://github.com/QRun-IO/qqq-frontend-next/pull/164)): a profile photo uploader on the Profile section and user avatars shown throughout the UI.
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC35 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC35 GitHub release after delivery.
## Remaining RC limits
The RC34 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+27
View File
@@ -0,0 +1,27 @@
# Next UI 1.0.0-RC.36 — preparation
At this preparation checkpoint RC36 **is not yet published**. [RC35](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.35) remains the latest public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.36` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.36`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `81a03ea` (merge of [PR175](https://github.com/QRun-IO/qqq-frontend-next/pull/175)). Merged since RC35: [PR175](https://github.com/QRun-IO/qqq-frontend-next/pull/175) (a11y: readable link text, 24px clear target, h2 widget titles, skip link focus).
## Backend requirements
Requires QQQ **4.1.0-RC.15** or later for the UI API 1.6.0 features (as RC35). Profiles and avatars need QQQ **4.1.0-RC.17** (UI API 1.8.0); against older servers avatars fall back to initials and the uploader stays hidden. RC36 does not publish QQQ 4.1.
## Product fixes
- **Accessibility** ([PR175](https://github.com/QRun-IO/qqq-frontend-next/pull/175)): link text meets 4.5:1 contrast through a new derived link color, clear buttons have 24px targets, widget titles are h2 headings, and the skip link takes focus correctly.
## Bundle budget
Report-only (owner decision 2026-10-13).
## Verification checkpoint
PR checks on the RC36 preparation source are recorded on the preparation PR; tag acceptance and public-artifact verification are recorded in the RC36 GitHub release after delivery.
## Remaining RC limits
The RC35 limits carry forward unchanged; see [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904).
+11
View File
@@ -0,0 +1,11 @@
# 1.0.0-RC.38 — prepared candidate
This candidate is not yet released. Merge and publication follow the compatible Core and Collaboration releases.
- Record file controls support previews, opening and downloading through the documented delivery APIs, including PDF previews.
- Script authoring includes the isolated TypeScript editor, server lifecycle actions and record hook controls.
- Process results support file delivery, PDF preview and ordered record lists through standard metadata.
- Record queries respect authorization-filtered pagination; record layouts preserve section metadata.
- The current permission editor and companion Tools workspace remain integrated.
The release uses the standard UI API and extension documents. Worker execution, storage configuration and printer/scanner qualification remain responsibilities of the respective QBits and host application. See [verification and release status](../QBIT-RELEASE-STATUS.md) and [known requirements](../QBIT-KNOWN-ISSUES.md).
+65
View File
@@ -0,0 +1,65 @@
# Next UI 1.0.0-RC.4
Release candidate for application testing. **Final 1.0 remains on hold for owner testing and explicit approval.** RC1, RC2 and RC3 remain immutable.
## Install
```xml
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.4</version>
</dependency>
```
Override an older BOM-managed frontend version. Select Next with `-Dqqq.javalin.frontend=next`. The JAR serves the UI at `/` alongside `/qqq/v1`; no Node server is required. If a previous attempt cached a missing RC4 artifact, retry Maven with `-U`.
Container: `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.4` (linux/amd64 and linux/arm64). This quickstart image uses build-time backend origin `http://host.docker.internal:8000`; use the Maven artifact or rebuild for a different origin.
## Changes since RC3
- Explicitly configured PASSWORD uppercase/lowercase behavior preserves Unicode-aware cursor position, masking and show/hide values. Ordinary passwords retain their entered case.
- Report previews and editors honor `omitExposedJoins` for new choices while retaining saved joined filters, sorts, columns and widths. Reordering available columns preserves omitted columns in place. Cancel, Basic/Advanced switching, OK, host Save and reopening preserve the intended data. Backend permissions remain authoritative.
- Numeric process and widget inputs preserve their values while scrolling; deliberate typing and arrow-key edits still work.
- Density menus stay open when touch-generated focus returns to their own trigger. Trigger toggling, Escape, focus return and outside dismissal remain functional. This fixes the reproduced Linux WebKit density failure.
- Developer documentation and regression coverage updated. The original Next UI remains the visual reference; these fixes preserve its layout.
## Verified artifacts and primary workflows
Source: `3c409f918699bc1fb9981f5b8829737bc608b55a`; signed tag `v1.0.0-RC.4` (tag object `bbdfea79fb4c5e044f717a735baa2073c32a8c39`).
- Both [candidate acceptance](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36664932061) and [tag acceptance](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36714593674) passed **577 Chromium tests and all 391 required feature rows**, with zero failures/skips/flaky outcomes and three approved external-service exclusions. Both used backend `e0d57dd31b979b17215370546ef4e26cd8a38d21`. The tested PR merge tree equals the tagged source tree.
- **1,884 unit tests** and **104 mocked Chromium tests** passed, plus types, lint, coverage, Storybook and bundle budgets. [General CI](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36664931987).
- Public Maven checksum and PGP signature verified. A clean consumer resolved the identical JAR through normal Maven dependency resolution, without manually installing it. [Maven publication](https://app.circleci.com/workflow/fbb640fc-b3a1-4594-abb8-3d95f341fccb).
- The actual downloaded public JAR passed **17 affected real-backend Chromium workflows**, with zero failures, skips, flaky outcomes or report-level errors. Coverage includes configured case conversion/passwords, report preview/editor preservation, numeric inputs and density-menu interactions.
- Public container manifest verified anonymously for AMD64 and ARM64. The downloaded ARM64 image served the app route and all 21 referenced JavaScript assets. This verifies image delivery; backend workflows are verified separately through the Maven JAR. [Container publication](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36714593869).
Public JAR SHA-256:
`ee521bc61f21b1deb24094578e17fbb4f636a33f34ce7e0e646cab3e6cc43bf6`
Container manifest digest:
`sha256:e01502691008c5f2ce444e3a212875819cd0bbd8500cee5827d7a20fa5e67f20`
## Browser results and remaining limitations
Both acceptance runs completed and failed overall because of secondary-browser failures. All profiles had zero skips or flaky outcomes; failures were retained, not waived or silently retried.
| Browser | Candidate result | Tag result |
| --- | --- | --- |
| Chromium | 577 passed | 577 passed |
| Mobile | 547 passed, 1 failed: REC-054 script-suggestion listbox bounding-box timeout in dark mode | 548 passed |
| Firefox | 576 passed, 1 failed: WID-026 record-heading timeout | 576 passed, 1 failed: WID-019 dashboard-heading timeout |
| WebKit | 576 passed, 1 failed: REC-046 internal navigation error on a read-only user's copy route | 576 passed, 1 failed: REL-005 internal navigation error returning to the pet list |
| Tablet | 547 passed, 1 failed: NAV-026 widget-request access-control errors during drawer navigation | 547 passed, 1 failed: SEC-002 internal navigation error opening a denied ledger create route |
Density scenarios passed in every profile of both runs. The other failures remain open; a later pass does not erase the earlier failure or prove full browser compatibility.
Use the QQQ 4.1.0-SNAPSHOT development backend including report-variant and configured Google Drive support. Record your application's exact backend commit. This RC does not release QQQ 4.1.
Full embedded report/query equivalence, remaining input configurations, unsupported Material icon names, origin sub-path hosting, real Google Drive provider verification and whole-product visual approval remain open. Final 1.0 requires renewed owner approval after application testing.
## Guides and issue reports
[Developer guide](https://github.com/QRun-IO/qqq-frontend-next/blob/3c409f918699bc1fb9981f5b8829737bc608b55a/docs/guides/next-ui-developer-guide.md) · [Agent guide](https://github.com/QRun-IO/qqq-frontend-next/blob/3c409f918699bc1fb9981f5b8829737bc608b55a/docs/guides/next-ui-agent-guide.md) · [Report an issue](https://github.com/QRun-IO/qqq-frontend-next/issues/new).
Include frontend RC/commit, backend version/commit, browser, page/workflow, relevant metadata, reproduction steps, expected/actual results, and sanitized screenshots or errors. Never include credentials or sensitive records.
+56
View File
@@ -0,0 +1,56 @@
# Next UI 1.0.0-RC.5
Release candidate for application testing. **Final 1.0 remains on hold for owner testing and explicit approval.** Earlier RCs remain immutable.
## Install
```xml
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.5</version>
</dependency>
```
Override an older BOM-managed frontend version and select Next with `-Dqqq.javalin.frontend=next`. The JAR serves the UI at `/` alongside `/qqq/v1`; no Node server is required. If Maven cached an earlier missing-artifact result, retry with `-U`.
Container: `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.5` (linux/amd64 and linux/arm64). Its build-time backend origin is `http://host.docker.internal:8000`; use the Maven JAR or rebuild the image for a different origin.
## Changes since RC4
- Restored original Next uniform table backgrounds, retaining selection highlighting, pinned cells and application CSS hooks.
- Restored original record overview typography and spacing, retaining metadata widths and selected-section layout.
- Report `hidePreview` hides the read-only summary preview while keeping the editing query available.
- Disabling a saved report's pivot table now clears its stored definition when Save is pressed, correcting silent validation failure.
- Column menus fit short windows and scroll when needed, keeping keyboard and pointer actions reachable.
## Verification
Source: `ac2294697519b5ee108d0ba776400afe30d28927`; signed tag `v1.0.0-RC.5`.
- [Candidate acceptance](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36751436362): **587 real-backend Chromium tests and all 391 required feature rows passed**, with zero failures, skips, flaky results or report errors. Three approved external-service exclusions remain. Backend: `e0d57dd31b979b17215370546ef4e26cd8a38d21`. The actual tested PR merge has the same Git tree as the tagged source.
- **1,884 unit tests** and **104 mocked Chromium tests** passed, plus types, lint, coverage, Storybook and bundle budgets. [General CI](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36751436048).
- Public Maven checksum and PGP signature verified. A clean consumer resolved the identical JAR through ordinary Maven dependency resolution. The actual downloaded JAR passed **12 affected real-backend Chromium workflows**, with zero failures, skips, flaky results or report errors. [Maven publication](https://circleci.com/gh/QRun-IO/qqq-frontend-next/131).
- Public container manifest verified anonymously for both architectures. The actual ARM64 image served the app and all 21 referenced JavaScript assets. This checks image delivery; backend workflows were verified through the public JAR. [Container publication](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36754867839).
Public JAR SHA-256:
`3d4955a763b73cb2e3a3a385164977772fe5e8ec6000699603ab649589070d41`
Container manifest digest:
`sha256:5eba52c5e0a67529048ddb992c609aa622dda5ecd6805ab6f0d6ea1e7ed0b264`
## Known limitations and continuing checks
Candidate WebKit finished with **586 passed and one failed**: REC-046 encountered an internal WebKit navigation error opening a read-only user's copy route. Mobile passed **548 tests**. Tablet passed **547 tests and failed one**: NAV-014 encountered an internal WebKit navigation error opening the Person create route during breadcrumb coverage. These completed profiles had zero skips, flaky outcomes or report errors. Both failures remain open. Candidate Firefox completed with **586 passed and one failed**: WID-073 did not observe the initial report-variant preview row within 15 seconds. It had zero skips, flaky outcomes or report errors. The candidate run is complete and failed overall; all failures remain open. The independent [tag acceptance run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36754867847) also passed **587 Chromium tests and all 391 required feature rows**, with zero failures, skips, flaky results or report errors; its WebKit profile completed with **585 passed and two failed**: NAV-026 reported widget-request access-control errors during navigation-drawer coverage, and NAV-015 timed out after an internal WebKit navigation error opening the unknown-route page. WebKit had zero skips, flaky outcomes or report errors. Tag Firefox completed with **586 passed and one failed**: INT-010 did not observe the failed-query error panel within 30 seconds. It had zero skips, flaky outcomes or report errors. Tag mobile passed **548 tests**; tablet passed **546 tests and failed two**: NAV-014 hit an internal WebKit navigation error opening the interactive greeting process, and SEC-043 hit an internal WebKit error reloading during different-user recent-record isolation coverage. Both profiles had zero skips, flaky outcomes or report errors. The tag run is complete and failed overall. Reports and traces for every failed scenario are retained; causes remain under investigation. Prior secondary-browser failures remain under investigation.
Use the QQQ **4.1.0-SNAPSHOT development backend** including report-variant and configured Google Drive support, and record your application's exact backend commit. This frontend RC does not release QQQ 4.1.
Complete embedded report/query equivalence, remaining input configurations, unsupported Material icon names, hosting under an origin sub-path, real Google Drive provider verification and whole-product visual approval remain open. The original Next UI is the visual reference. A separate icon follow-up is not part of this immutable RC5 release. A subsequent regression check also found that plain TIME blocks use minute increments: process blocks can mark second-level values invalid. A separate correction is being verified; it is not included in RC5.
Final 1.0 requires renewed owner approval after application testing.
## Guides and issue reports
[Developer guide](https://github.com/QRun-IO/qqq-frontend-next/blob/ac2294697519b5ee108d0ba776400afe30d28927/docs/guides/next-ui-developer-guide.md) · [Agent guide](https://github.com/QRun-IO/qqq-frontend-next/blob/ac2294697519b5ee108d0ba776400afe30d28927/docs/guides/next-ui-agent-guide.md) · [Report an issue](https://github.com/QRun-IO/qqq-frontend-next/issues/new).
Include frontend RC/commit, backend version/commit, browser, page/workflow, relevant metadata, reproduction steps, expected/actual results, and sanitized screenshots or errors. Never include credentials or sensitive records.
+52
View File
@@ -0,0 +1,52 @@
# Next UI 1.0.0-RC.6
Release candidate for application testing. **Final 1.0 remains on hold for owner testing and renewed explicit approval.** Earlier RCs remain immutable.
## Changes since RC5
- Legacy metadata icon names missing from the existing Next maps now render from a pinned, locally bundled Material Icons inventory of 2,234 unique names.
- Existing Next Lucide mappings, widget overrides, custom image priority, colors, sizing and layout are preserved. Unknown names keep generic fallback icons.
- The extra inventory loads only when needed, without an external font service or new package dependency.
- Developer and agent guides document icon resolution and its limits.
## Install
Maven coordinate: `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.6`. Select Next with `-Dqqq.javalin.frontend=next`. The JAR serves the UI at `/` alongside `/qqq/v1`; no Node server is required.
Container: `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.6`, AMD64 and ARM64. Its build-time backend origin is `http://host.docker.internal:8000`; use the Maven JAR or rebuild for a different origin.
Both public artifacts are available and verified. If Maven cached a missing-artifact result from an earlier attempt, retry with `-U`.
## Verified candidate
Source: `7ddf45fa4d7c94627bafb1ee40916c3f6c47536f`; signed tag `v1.0.0-RC.6`. Tested PR merge `f52387f10885da1d8ad22961915b76fa9e08ab72` has the same source tree.
- [Hosted candidate acceptance](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36764199293): **589 real-backend Chromium tests and all 391 required feature rows passed**, with zero failures, skips, flaky results or report errors. Three approved external-service exclusions remain. Backend checkout: `e0d57dd31b979b17215370546ef4e26cd8a38d21`.
- [General CI](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36764199430): **1,901 unit tests**, **104 mocked Chromium tests**, types, lint, coverage, Storybook and bundle budgets passed.
- Both candidate container architecture builds passed. The local RC6 JAR passed four light/dark desktop/mobile icon workflows; its 193 static export files and bundled font were verified against the build output.
Candidate WebKit completed with **586 passed and three failed**, with zero skips, flaky outcomes or report errors: NAV-034 encountered an internal WebKit navigation error opening a record process; NAV-002 expected raw sidebar text without decorative legacy icon glyphs; WID-073 reported an application-metadata request access-control error. Reports and traces are retained; these failures remain recorded. A subsequent check reproduced NAV-002 against the public JAR and confirmed its raw-text assertion counted decorative SVG characters. A separately reviewed test correction verifies accessible names, order and hidden glyphs; it passed focused Chromium/WebKit/mobile checks and is not part of the original run. The other failures remain under investigation. The candidate run is now complete and failed overall. Firefox finished **586 passed / 3 failed**: NAV-014 and NAV-015 reported legacy icon font download errors; INT-010 timed out waiting for the failed-query error panel. Mobile finished **549 passed / 1 failed** (NAV-002 decorative-glyph raw-text assertion). Tablet finished **548 passed / 2 failed** (the same NAV-002 assertion and NAV-034 internal WebKit session/navigation failure). These profiles had zero skips, flaky outcomes or report errors. All reports and traces are retained. The independent [tag-triggered acceptance run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36767986263) passed **589 Chromium tests** with zero failures, skips or flaky results. Its WebKit job finished **585 passed / 4 failed**: NAV-026 application-request access-control errors, NAV-016 missing Person grid after site-root navigation, NAV-002 the decorative-glyph raw-text assertion, and SEC-043 an internal WebKit reload error. Firefox finished **586 passed / 3 failed**: NAV-002 the decorative-glyph raw-text assertion, and REC-056/SEC-043 legacy icon font download errors. Mobile finished **548 passed / 2 failed**: NAV-002 and REC-054 a missing script-suggestion popup bounding box in light mode. Tablet finished **549 passed / 1 failed**: NAV-002. The tag run is complete and failed overall; all profiles had zero skips, flaky outcomes or report-level errors. Reports and traces are retained. Earlier secondary-browser failures remain open; Chromium success does not establish full browser compatibility.
## Public artifact verification
- [Maven publication](https://circleci.com/gh/QRun-IO/qqq-frontend-next/135) built the exact tagged source. The downloaded JAR checksum and PGP signature were verified; a clean consumer resolved identical bytes through ordinary Maven dependency resolution.
- The actual public JAR passed **four affected light/dark desktop/mobile icon workflows**, with zero failures, skips, flaky outcomes or report errors. Its 193 dashboard files, three version markers, pinned font and license were checked; no mock worker is packaged.
- [Container publication](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36767986139) succeeded. Anonymous inspection verified both architectures; the downloaded ARM64 image served the app and all 21 JavaScript assets. Backend behavior was verified separately through the JAR.
Public JAR SHA-256: `81ef0a869afb3caeb356704bd8b0345a7601b7cf908d042f66d5c3c9835b2697`.
Container manifest digest: `sha256:8591aaf10946eb1b44da5a4a5f45a9782dac9afc734bdd5187630c5b1351a5d3`.
## Backend and known limitations
Use the QQQ **4.1.0-SNAPSHOT development backend** with report-variant and configured Google Drive support. Record your application's exact backend commit. This frontend RC does not release QQQ 4.1.
Complete embedded report/query equivalence, remaining input and association-editing configurations, developer-tool gaps, origin sub-path hosting, real Google Drive provider verification, secondary-browser failures and whole-product visual approval remain open. Original Next is the visual reference. Arbitrary Material Symbols names and distinct icon font-style variants are not claimed.
Plain TIME blocks retain a known seconds-handling defect in this RC. A separate correction is verified and committed, but not included. API playground contrast corrections are also being verified separately and are not included.
## Guides and feedback
[Developer guide](https://github.com/QRun-IO/qqq-frontend-next/blob/7ddf45fa4d7c94627bafb1ee40916c3f6c47536f/docs/guides/next-ui-developer-guide.md) · [Agent guide](https://github.com/QRun-IO/qqq-frontend-next/blob/7ddf45fa4d7c94627bafb1ee40916c3f6c47536f/docs/guides/next-ui-agent-guide.md) · [Report an issue](https://github.com/QRun-IO/qqq-frontend-next/issues/new).
Include the exact frontend and backend versions/commits, browser, workflow, relevant metadata, reproduction steps, expected/actual results and sanitized screenshots or errors.
+69
View File
@@ -0,0 +1,69 @@
# Next UI 1.0.0-RC.7
Release candidate for application testing. **Final 1.0 remains on hold for owner testing and renewed explicit approval.** Earlier RCs remain immutable.
## Changes since RC6
- Plain process and standalone TIME inputs support seconds, matching shared forms. Seeded values remain valid, native stepping advances one second, and saved values retain seconds. Existing timestamp timezone and daylight-saving behavior is preserved.
- API playground text and button contrast use the dashboard theme in light/dark mode. Preferences changes apply immediately; layout is preserved.
- Sidebar acceptance checks wait for legacy icons and verify accessible labels, order and decorative glyph hiding. This corrects a test assertion; no sidebar product behavior changes.
## Install
```xml
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.7</version>
</dependency>
```
Override an older BOM-managed frontend version. Select Next with `-Dqqq.javalin.frontend=next`. The JAR serves the UI at `/` alongside `/qqq/v1`; no Node server is required. Use Maven `-U` if an earlier lookup cached a missing artifact.
Container: `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.7` (AMD64/ARM64). This quickstart image uses build-time backend origin `http://host.docker.internal:8000`; use the Maven artifact or rebuild for a different origin.
## Verified candidate
Source: `f65cf6bfbd1583538afe1b66f8764812980880c3`. Tested PR merge `f11bb1a6656de71e5a60d5781ac244e5117daa81` has the identical source tree `623efded5c6de13cbe389dc27c9227468a7419c9`.
- [Hosted candidate acceptance](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36771453216): **592 real-backend Chromium tests and all 391 required feature rows passed**, with zero failures, skips, flaky outcomes or report errors. Three approved external-service exclusions remain. Backend checkout: `e0d57dd31b979b17215370546ef4e26cd8a38d21`. WebKit completed with **591 passed / 1 failed**: NAV-026 navigation-drawer coverage reported widget-request access-control errors. There were zero skips, flaky outcomes or report-level errors. The cause remains under investigation; original reports and traces are retained. Mobile completed with **553 passed / 0 failed**. Tablet completed with **552 passed / 1 failed**: NAV-014 breadcrumb navigation reported API request access-control errors. Both had zero skips, flaky outcomes or report-level errors. Firefox completed with **590 passed / 2 failed**: NAV-016 legacy icon font download failure and WID-041 missing plain widget. The candidate run is complete and failed overall; all profiles had zero skips, flaky outcomes or report-level errors.
- [General CI](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36771452885) passed, including unit/coverage, types, lint, mocked Chromium, Storybook and bundle budgets. Fresh local checks passed **1,904 unit tests**, types, lint, export and Maven packaging.
- The actual local candidate JAR passed **14 affected desktop/mobile workflows**, with zero failures, skips, flaky outcomes or report errors. Its 193 export files match the build output, version markers and pinned icon font are correct, and no mock service worker is packaged.
## Publication verification
- [Maven publication](https://circleci.com/gh/QRun-IO/qqq-frontend-next/138) built the exact tagged source. The public JAR checksum and QQQ publishing signature were verified. A clean consumer resolved identical bytes through ordinary Maven dependency resolution.
- The actual downloaded public JAR passed **14 affected desktop/mobile workflows**, with zero failures, skips, flaky outcomes or report errors. Its 193 dashboard files, three version markers, pinned icon font and license were verified; no mock worker is packaged. An initial run used stale compiled test fixtures and failed four missing-clock assertions; that evidence was retained, the fixtures were compiled from tagged source, and the complete affected suite passed.
- [Container publication](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36775272526) succeeded. Anonymous manifest verification confirmed AMD64 and ARM64. The downloaded ARM64 image served the application and all 21 referenced JavaScript assets. This verifies image delivery; backend workflows were verified through the public JAR.
Public JAR SHA-256: `f23f8ede0d82d30f47621efacd14119afb7c00a6961a1936a17ffa2203bebdf0`.
Container manifest digest: `sha256:6d6fc39afd3f6e00aebe619b49a0350a64802f4e6f7cebdacb8aac20284fdba3`.
The independent [tag-triggered acceptance run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36775272542) is complete and failed overall. Original reports and traces are retained; there were zero skips, flaky outcomes or report-level errors.
| Profile | Passed | Failed | Failure evidence |
| --- | ---: | ---: | --- |
| Chromium | 592 | 0 | All 391 required feature rows passed |
| Mobile | 553 | 0 | All tagged phone scenarios passed |
| WebKit | 591 | 1 | NAV-015 incorrect not-found title with a session-request internal error |
| Firefox | 590 | 2 | NAV-016 legacy icon font download failure; WID-024 missing record heading during child export |
| Tablet | 551 | 2 | NAV-014 and NAV-015 internal navigation errors |
These failures remain open. Passing primary workflows does not establish complete browser compatibility.
## Backend and known gaps
Use QQQ 4.1.0-SNAPSHOT development backend including report-variant and configured Google Drive support. Record your application's exact backend commit. This RC does not release QQQ 4.1.
Complete embedded report/query equivalence, remaining input and association-editing configurations, developer-tool gaps, origin sub-path hosting, real Google Drive provider verification, secondary-browser failures and whole-product visual approval remain open. Original Next remains the visual reference.
The API playground's inherited narrow-screen specification-button clipping is not fixed in RC7; a separately reviewed follow-up addresses it. A newly reproduced bounded LONG validation issue can throw for non-integer text or round large string bounds; its separate follow-up passed schema/component tests and independent review, with combined candidate verification underway. Neither follow-up is included here. A separate unit test also has an ambiguous duplicate-label assertion; its correction is outside this immutable source.
Final 1.0 requires renewed owner approval after application testing; passing the current matrix does not establish complete compatibility.
## Guides and issue reports
[Developer guide](https://github.com/QRun-IO/qqq-frontend-next/blob/b9b1f3da84f49eec16ead0da3fda3af3e5f16b98/docs/guides/next-ui-developer-guide.md) · [Agent guide](https://github.com/QRun-IO/qqq-frontend-next/blob/b9b1f3da84f49eec16ead0da3fda3af3e5f16b98/docs/guides/next-ui-agent-guide.md) · [Report an issue](https://github.com/QRun-IO/qqq-frontend-next/issues/new).
Include frontend RC/commit, backend version/commit, browser, page/workflow, relevant metadata, reproduction steps, expected/actual results, and sanitized screenshots or errors. Never include credentials or sensitive records.
+46
View File
@@ -0,0 +1,46 @@
# Next UI 1.0.0-RC.8
**Public Maven and container delivery verified; RC8 prerelease published for application testing.** Tag `v1.0.0-RC.8` points to `cd3d4731d1910e68e994da9c74a8c28a2f1b7c1c`. RC8 supersedes RC7 as the current application-testing prerelease. Final 1.0 remains on hold for owner application testing and renewed explicit approval.
## Changes since RC7
- API specification Download and View controls remain fully visible and usable in narrow panels, including 320px phones. Labels wrap when needed; wide-panel buttons grow to fit the application font. Real specification download and keyboard new-tab behavior are covered in light/dark and touch workflows.
- Search keeps keyboard focus inside the dialog, returns focus to its opener on close, handles Escape from all controls, and returns focus to the input after clearing.
- Bounded LONG fields reject invalid integer text with a field error instead of throwing during range validation. Exact integer-string bounds retain precision beyond JavaScript's safe-number range. Optional blanks and existing required-field messages are preserved.
- A process unit test scopes its choice-label lookup to the read-only field under test, avoiding ambiguity when an editable field displays the same label. This changes no product behavior.
## Historical verification before version change
Both product fixes passed independent review. API layout verification passed 12 affected desktop/mobile workflows; its 1440px RapiDoc screenshot was identical to RC7. The API layout source passed 596 real-backend Chromium tests and all 391 required feature rows, with zero failures/skips/flaky outcomes/report errors and three approved external-service exclusions.
The combined fixes passed all 1,913 unit tests across 180 files, types, lint/license, a clean production export and existing bundle budgets. The first full combined Chromium run finished with 595 passed and one failed (NAV-025 search navigation), zero skips/flaky outcomes/report errors. Its report and trace are retained; investigation remains open. The trace requested the correct search route successfully and the final snapshot contained the expected results despite the URL assertion reporting the prior record route. Twenty focused repetitions against the RC8 JAR passed; they do not erase the original failure. No timeout, retry or assertion changes were made. Fresh RC8-version checks passed all 1,913 units, types, lint/license, production export, Maven packaging and existing bundle budgets. All 193 packaged dashboard files match the export; version markers and pinned font are verified. The actual RC8 JAR passed 16 affected desktop/mobile workflows with zero failures/skips/flaky outcomes/report errors: API specification visibility/download/keyboard actions, contrast/live theme changes, exact LONG/DECIMAL persistence and composite metadata-editor submission. The fresh full RC8 run at source `f0f0eae98e4b6c883475956a7c59e060d2e5f0a5` finished with **588 passed / 8 failed**, zero skips/flaky outcomes/report errors. The failed cases were QRY-007, QRY-009, two QRY-001 theme cases, QRY-052, QRY-053, REC-063 and QRY-071. macOS power events overlap 98–100% of each failure interval, including a browser-launch timeout; test and teardown timers advanced during host sleep. The run is retained as failed, not converted to passing evidence. This explains its abnormal duration but does not prove every affected workflow correct. An uninterrupted hosted run is required, along with public delivery checks. The earlier NAV-025 search failure remains independently recorded. At that historical checkpoint, no RC8 tag or public artifacts existed.
LONG regression coverage establishes frontend schema/component behavior, including correction and submission of exact digits. It does not establish a new backend range-metadata contract.
## Historical September 30 follow-up verification
The first hosted RC8 candidate at `14d6966` completed its primary Chromium job with **594 passed / 2 failed** (REC-053, desktop API label clipping in both themes). General CI and image build passed. Secondary acceptance jobs remain in progress. Publication is blocked until the corrected combined candidate has fresh release evidence.
A wider-font regression reproduced label overflow at 210px content versus 198px available. The reviewed correction uses RapiDoc public CSS parts scoped to the wide overview. It passed **14 desktop/mobile API workflows**, 1,913 units, types, lint/license (five existing warnings), clean export and bundle budgets. Desktop/mobile light/dark screenshots were inspected. The reviewed search correction independently passed **601 full Chromium workflows and all 391 required feature rows**, with three approved external-service exclusions and zero failures/skips/flaky outcomes/report errors; its unit total is 1,919 and it also passed 34 focused browser cases. These are separate source checks; combined candidate verification is next. Earlier failed runs remain retained.
## Corrected candidate verification and publication
Corrected source `cd3d473` passed **1,919 unit tests**, types, lint/license (five existing warnings), clean static export, Maven packaging and bundle budgets. All 193 packaged dashboard files match the export. The actual local JAR passed **24 affected desktop/mobile workflows**, with zero failures/skips/flaky outcomes/report errors. Local full Chromium and [hosted primary acceptance](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36798799502) each passed **602 cases and all 391 required feature rows**, with three approved external-service exclusions and zero failures/skips/flaky outcomes/report errors. [General CI](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36798799444) and [image build](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36798799498) passed. Corrected WebKit finished **600 passed / 2 failed** (NAV-034 legacy process-URL redirect timeout; REL-005 internal navigation error), zero skips/flaky/report errors. Reports/traces are retained and causes remain unproven. Corrected Firefox finished **600 passed / 2 failed** (NAV-014 font-download diagnostics during breadcrumbs; REC-009 save-success toast not observed after updated record display), zero skips/flaky/report errors. The corrected touch step is marked cancelled; its retained artifact has no final report or gate. No passing touch total is claimed. At artifact collection, GitHub still reported the job in progress despite every exposed step being completed. The cancellation cause is unconfirmed; all four candidate artifacts were archived before the later documentation push.
The original candidate finished WebKit 596/0, mobile 557/0 and tablet 556/1 (REL-005 internal WebKit navigation error). Its unfinished Firefox job was cancelled by the owner-requested candidate push; no passing Firefox result is claimed. Earlier failures remain retained as history and are not erased by the corrected primary passes.
Signed RC8 tag publication started CircleCI Maven jobs 141 and 142, [image publication36800964056](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36800964056), and [tag acceptance36800964105](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36800964105). CircleCI jobs 141 and 142 and image publication 36800964056 completed successfully. Maven Central initially returned HTTP 404 after upload; the public JAR is now available and verified (October 1). Its SHA-1 matches Central, the signature matches publishing key `68A49476754103988EA3ED924B88CBB3D6DCB237`, and a fresh Maven consumer resolves identical bytes. The public JAR passed all **28 affected desktop/mobile workflows**, zero failures/skips/flaky outcomes/report errors. SHA-256: `238c9ae80beea336717be759219f0d65a1d614e8e7d6a145a16374cc392a73a1`. The anonymous public image manifest/pull passed (AMD64/ARM64); the downloaded ARM64 image served its route, all 21 scripts, both stylesheets and the pinned font successfully. Manifest digest `sha256:2be3b74f614b1d038d79aba2894b210b988025000e86a572364a902fb0106c25`. This is an image delivery/assets check, not a replacement for backend workflows.
The immutable tag's Chromium job in run 36800964105 also passed **602 cases and all 391 required rows**, with three approved exclusions and zero failures/skips/flaky outcomes/report errors. Its complete artifact is retained separately in `rc8-tag-ci-chromium/`; tag WebKit completed **601 passed / 1 failed** (PRC-021 process display after a failed `/qqq/v1/manageSession` request with a WebKit internal error), with zero skips/flaky outcomes/report errors. Its complete artifact is retained in `rc8-tag-ci-webkit/`; cause unproven. Tag Firefox finished **600 passed / 2 failed** (NAV-015 and SEC-043 font-download diagnostics), with zero skips/flaky outcomes/report errors. No user-isolation assertion failure is reported for SEC-043; the font diagnostics still fail the test and their cause is unproven. The complete artifact is retained in `rc8-tag-ci-firefox/`. Tag mobile passed **563 cases**; tablet passed **559 and failed 4**: NAV-025 Clear-search focus in light/dark, REL-005 WebKit internal navigation error, and SEC-043 recent-record visibility after same-user reload before the user switch. Zero skips/flaky outcomes/report errors. Full artifacts are retained in `rc8-tag-ci-touch/`; these failures remain open.
## Remaining requirements
Complete embedded report/query equivalence, remaining input configurations, origin sub-path hosting, real Google Drive provider verification, secondary-browser failures and whole-product visual approval remain open. Original Next remains the visual reference. Do not infer full compatibility from the current acceptance matrix.
Use the QQQ 4.1.0-SNAPSHOT development backend with report-variant and configured Google Drive support. Record the application's exact backend commit. This candidate does not release QQQ 4.1.
## Local owner preview
A separate RC8 preview is running at `http://127.0.0.1:18781/app/person/` using the public ARM64 container and an isolated sample backend. Entry/grid/branding/custom-component checks passed with five Person rows and zero page errors in light mode. The fixture proxy routes acceptance assets and sample branding; the container uses explicit fixture CSP sources. This preview check does not claim full workflow coverage. RC7 remains separately available at port 18779.
Additional diagnostic evidence: candidate Firefox REC-009 captured the save-success toast before an approximately 7.45-second automation delay preceding later checks. Its four-second toast had expired before the subsequent visibility check; save/refetch requests succeeded quickly. This establishes that the notification appeared, while the automation delay remains unexplained. A separate Chromium probe of the local RC8 JAR verified that a preview popup carries temporary sort and stored criteria, matches SQL row order, and leaves the saved report unchanged. These findings do not alter the failed-run totals or the 602-case gate.
+41
View File
@@ -0,0 +1,41 @@
# Next UI 1.0.0-RC.9
Release candidate for application testing, October 1, 2026. The [GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.9) records public artifact availability and final delivery verification; a source tag alone is not delivery confirmation. Final 1.0 remains held for owner application testing and explicit approval.
## Changes from RC8
RC9 integrates the GitHub tracking policy, current roadmap links, and updated developer/agent documentation. Application code and dependencies are unchanged from RC8. This candidate does not claim fixes for the open functional or visual findings.
[QRun-IO/qqq#713](https://github.com/QRun-IO/qqq/issues/713) is the source of truth for current status and remaining work. Original issue reports and historical acceptance evidence remain available. See the [tracking policy](../ISSUE-TRACKING.md).
## Verification baseline and known issues
The documentation source `354ee2709d8ee553de5e3ad823ce9d1190887d4c`, merged through PR #16, passed general CI (typecheck, lint, unit coverage, mocked Chromium E2E, Storybook and bundle budgets) and both container architecture builds. Real-backend [run 36861609503](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36861609503) passed all 602 Chromium cases and all 391 required rows, with three approved real-provider exclusions.
Its secondary reports remain failed: Firefox 599 passed / 3 failed (NAV-014, NAV-016 and REC-056 legacy-font diagnostics, [#951](https://github.com/QRun-IO/qqq/issues/951)), WebKit 601 / 1 (WID-073 metadata access-control diagnostic, [#961](https://github.com/QRun-IO/qqq/issues/961)), and combined touch 1,124 / 2 (tablet NAV-025 and REL-005). Reports contain zero skips/flaky outcomes/report errors. These are pre-RC9-version source results; final tag results and public artifact receipts are recorded separately below. No all-browser pass is claimed.
Current open work includes embedded report/query equivalence, remaining input configurations, subpath hosting, real Auth0/Google Drive/QuickSight checks, browser investigations, original-Next visual approval, real-application soak and final release approval. See [#713](https://github.com/QRun-IO/qqq/issues/713) and [browser evidence](https://github.com/QRun-IO/qqq-frontend-next/issues/12) for individual issues. Material remains the behavior reference; the original Next design remains the visual reference.
## Versioned local verification
RC9 passes all 1,919 unit tests across 181 files, typechecking, lint/license (zero errors, five existing warnings), production export, Maven packaging and unchanged bundle budgets. All 193 packaged dashboard files are present; the Maven version is RC9 and no mock service worker is included. The actual locally packaged RC9 JAR passes 28 affected desktop/mobile browser cases with zero failures/skips/flaky outcomes/report errors. These checks cover API controls/theme/search focus, exact numeric persistence and composite field submission against backend e0d57dd31. They do not replace public artifact verification or certify other profiles.
## Installation
Maven coordinate: `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.9`.
Container: `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.9` (AMD64/ARM64 verified). The container backend origin is fixed at build time; the published image targets `host.docker.internal:8000`. Use the JAR or rebuild for another origin.
As with RC8, verification uses the QQQ 4.1 development backend. The locally verified backend source is `e0d57dd31b979b17215370546ef4e26cd8a38d21`; hosted workflows resolve their configured QQQ ref separately. This release does not publish QQQ 4.1 or certify every real consumer/backend combination.
## Public delivery — October 1, 2026
[RC9](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.9) was published at 19:25:28 UTC as a prerelease, latest=false. PR16 and PR14 are merged. The signed immutable tag points to `c029ad2a70948aac6469d8a71ccefc80c6e00d74`.
- Public JAR: 1,864,121 bytes; SHA256 `969034566c6b30a5034680dcf2d46aec327240db6cdd3fb7b31f1fae7cb9f17d`; SHA1 `3943c8641c14554999206cf52357c32b8f397b7b`.
- Release signature verified against fingerprint `68A49476754103988EA3ED924B88CBB3D6DCB237`; a clean ordinary Maven consumer resolved identical bytes without manual installation.
- Actual public JAR: all 28 targeted desktop/mobile cases passed, zero failures/skips/flaky outcomes/report errors. All 193 dashboard files and RC9 version markers are present, without the mock service worker.
- Public image: `sha256:c2c684733deb98fd8355d4a9e2a53bf4af429bd16d9e7802641f9ef9d44e1017`. Anonymous AMD64/ARM64 pulls and both architecture startup, route, JavaScript, pinned-font and version checks passed.
- Merged-source general CI36912597438 passed. Fresh full dependency audit reports zero advisories across1,038dependencies; GitHub marked all four outstanding alerts fixed, none dismissed. Issues #647/#698 are closed with that evidence.
Full immutable-tag acceptance [36912641399](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36912641399) completed after publication: Chromium 602 passed / 0 failed; desktop WebKit 602 / 0; Firefox 600 / 2; mobile 563 / 0; tablet 560 / 3. All reports contain zero skips, flaky outcomes and report errors. Firefox failed NAV-016 legacy-font diagnostics and REC-048 validation-toast visibility; tablet failed NAV-015 JavaScript loading, NAV-016 root navigation (both WebKit internal errors), and NAV-025 Clear-search focus in dark mode. The overall run failed; later passing cases do not resolve older investigations. Follow [frontend#12](https://github.com/QRun-IO/qqq-frontend-next/issues/12) for individual failure tracking and [#713](https://github.com/QRun-IO/qqq/issues/713) for the live backlog. Final 1.0 remains held.
+33
View File
@@ -0,0 +1,33 @@
# Next UI — unreleased
[RC36 preparation](1.0.0-RC.36.md) collects every change merged since [RC35](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.35). RC35 is published and immutable. Changes merged after the RC36 preparation commit are listed below. Central tracking: [qqq#713](https://github.com/QRun-IO/qqq/issues/713), [browser investigations #985](https://github.com/QRun-IO/qqq/issues/985), and [QQQ Roadmap Project 12](https://github.com/orgs/QRun-IO/projects/12).
## Features
- **Tools:** the `QQQ_TOOLS` widget provides a shared setup workspace for CLI/TUI, mobile scanners and Print Client, with platform-specific install commands, copyable scripts and instance connection details. An account-menu shortcut follows visible Tools metadata. Requires the Tools Qbit; existing device enrollment and permissions remain unchanged.
- **UI API 1.6.0 (QQQ 4.1.0-RC.15).** Next reads the typed `clientCapabilities` of table metadata for saved views, sharing, bulk processes and audits, applies `field.validation` (required, length, inclusive and exclusive bounds, patterns) before a request, reads client-neutral `uiSettings` setting by setting with the deprecated `materialDashboard` values as fallback, and shows the process error `traceId`. The full `materialDashboardTheme` still wins over the `uiSettings.theme` subset. Error messages keep their order (`userFacingError`, `error`, then the new `message`). Audits fall back to the permission-filtered instance metadata when a table's capability is unavailable, since RC.15 marks unaudited tables unavailable although explicit audits can exist. Off-spec calls stay at 0.
- **What's new:** a paged release history with new badges, safe links and screenshots, notification deep links, and an unread count in the user menu. Uses UI API extension `release-notes` 1.0.0; requires qbit-release-notes 0.1.0-RC.1+.
- **Profile photo and avatars everywhere** (QQQ 4.1.0-RC.17, UI API 1.8.0 `avatars`):
- The core **Profile** preferences section renders generically. Its photo is a `FILE_UPLOAD` field with `valueIsUploadRef`, so you can drag and drop or choose a picture, crop it square (drag, arrow keys or the zoom slider), preview it and remove it. The cropped PNG goes to `POST /uploads`, and the ref saves with the form.
- One `UserAvatar` (photo via one batched `POST /avatars/lookup` and `GET /avatars/{avatarId}`, otherwise initials) shows people in the user menu, presence, comments, mention candidates, the people picker, activity and your own assistant messages. A missing avatar is remembered for the session, so it isn't requested again.
- **Privacy:** branding `gravatarDefault` no longer sends your email hash to Gravatar. A Gravatar appears only if you turn it on in your Profile, and the server serves it.
- Image fields take a full form row at every width. e2e coverage is `tests/e2e/profile-avatars.spec.ts`, including the 6-width light/dark screenshot matrix.
## Product fixes
- **A preference action that connects another service takes you there.** A provider that starts an OAuth connection (Connect Slack, for example) answers with the address of the service's consent page in the action result's `message`, since the UI API has no redirect field. That address was shown as the text of a success toast: long, not clickable and gone in seconds. A message that is nothing but one absolute `https:` address is now where the browser goes, in the same tab, and is never shown. A message that only mentions an address is still a message. Plain `http:`, addresses with credentials and every other scheme are not followed.
- **Preferences says how a connection ended.** A flow that left the dashboard returns to `/app/preferences?section=<name>&result=<code>` when it succeeded or `&error=<code>` when it did not. The section now shows the outcome once, above its form, as the section's label and the code as words (`Slack: connected.`, `Slack: authorization denied.`), as a status or an alert, and takes it off the address so a reload does not repeat it. Only a fixed lowercase code is read from the address, never a sentence.
- **Links in process HTML look like links.** An `HTML` process component rendered links in the text color with no underline, so a link the backend put on a screen (a bulk load template, a "Continue to Slack" link) could not be told from the words around it. They now use the link color and an underline; a link's own inline style still wins.
- **The Ask button's activity dot sits on its icon.** While a reply was being written, the dot was placed in the bottom-right corner of the button's hit area, which has no visible edge and, from 1440 px, extends past the "Ask" label, so the dot floated under and to the right of the label. The dot and the unread badge are now positioned against the sparkles icon, at every width and with the panel open or closed. The bell and Jobs badges use the same anchor and keep their place.
- **Logout no longer races an in-flight request.** Logout ends the API client's session generation first: every request still in flight (axios and the realtime and assistant streams) is aborted, and any response of the old generation that still arrives is discarded, so none can refresh the `sessionId` cookie the logout expires or change client auth state (SEC-037, SEC-048).
- **The error banner says what failed, and no longer stacks.** The banner for a failed request used to read "Something went wrong" for every failure without an expected status, and a polling request that kept failing added one banner per attempt. It now reads `No response from the server`, `The server took too long to respond`, the server's own message, or `Request failed (HTTP 409)`, and names the request below it (`POST /qqq/v1/table/order/query`, without the query string, with ids shown as `{id}`). A repeat of the same failure updates the banner on screen; a lost connection shows one banner whichever requests met it. The 403, 404 and 5xx banners keep their wording and gain the request line; a 403 without a server message reads `Permission denied`. A cancelled request, an aborted `fetch` and a 401 show no banner. Each banner logs its status, axios code, error name and request to the console once (`[API] Request failed:`), never a message, body, header or query string.
## Browser testing and infrastructure
None yet.
Final 1.0 remains held for real-application soak and explicit owner approval under [#712](https://github.com/QRun-IO/qqq/issues/712). This work does not authorize a QQQ 4.1 release. See the [testing-period policy](1.0-testing.md) and [issue lifecycle](../ISSUE-TRACKING.md).
+22
View File
@@ -0,0 +1,22 @@
# Jobs screenshots
The mocked browser spec is `tests/e2e/jobs-tray.spec.ts`. Capture the actual UI with:
```powershell
$env:QQQ_E2E_SERVER='production'
$env:QQQ_JOBS_SCREENSHOTS='docs/screenshots/jobs-tray'
$env:QQQ_E2E_PORT='3117'
pnpm exec playwright test tests/e2e/jobs-tray.spec.ts --workers=1 --reporter=list
```
Captured 2026-10-14 from the production build (`QQQ_E2E_SERVER=production`), 1000 px tall:
| View | 390 light | 390 dark | 1440 light | 1440 dark |
| --- | --- | --- | --- | --- |
| Live job | ![](live-390-light.png) | ![](live-390-dark.png) | ![](live-1440-light.png) | ![](live-1440-dark.png) |
| Open tray | ![](tray-390-light.png) | ![](tray-390-dark.png) | ![](tray-1440-light.png) | ![](tray-1440-dark.png) |
| Jobs page | ![](page-390-light.png) | ![](page-390-dark.png) | ![](page-1440-light.png) | ![](page-1440-dark.png) |
The spec also checks horizontal overflow, popover bounds, keyboard focus restoration, axe at 1440
light, and reduced motion; its lifecycle case runs an inline process, detaches it, watches it
finish in the tray and reopens the finished view.
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+343
View File
@@ -0,0 +1,343 @@
# Next UI security review (QRun-IO/qqq#696)
## Current status — October 4, 2026
This addendum supersedes the historical disposition statements below where noted.
The September 25 review remains intact as evidence of its original source and scope.
Current findings and release decisions are tracked in
[QQQ #696](https://github.com/QRun-IO/qqq/issues/696#issuecomment-5982040326).
This is a scoped security review, not a certification of ASVS Level 2 compliance.
| Finding | Current disposition |
| --- | --- |
| NUI-04: dashboard headers | [#695](https://github.com/QRun-IO/qqq/issues/695) is closed for the Javalin static-export scope, with current-source provider tests and browser evidence. Standalone qualification is separate. |
| NUI-05: readable session cookie | [#733](https://github.com/QRun-IO/qqq/issues/733) is closed. Next resumes through the backend-owned cookie; the legacy endpoint correction is merged. Material's explicit readable-cookie compatibility mode remains supported. |
| NUI-06: previous-user browser state | The additional query-filter and saved-view-pointer retention finding is fixed in [#1006](https://github.com/QRun-IO/qqq/issues/1006), merged in frontend `f367756`. Global visual preferences are preserved. |
| NUI-09: standalone headers | [#734](https://github.com/QRun-IO/qqq/issues/734) and the metadata-derived CSP correction [#1017](https://github.com/QRun-IO/qqq/issues/1017) are closed after scoped qualification and integration. The paired implementation passed 48 standalone first-attempt cases across five profiles. [Frontend PR #40](https://github.com/QRun-IO/qqq-frontend-next/pull/40) merged as `138c3c6d`, tree `328bc322`, exactly matching the qualified combined candidate. Its separate full Javalin acceptance passed 3,015/3,015 first attempts against backend `1d16a70c`. HEAD/304, bounded metadata and Java-customizer limits remain documented in the closeout. |
| NUI-11: custom CSS | Current code treats application `customCss` as trusted developer configuration and inserts it with `textContent`. It is not filtered by the historical deny-list or sanitized by DOMPurify. CSP and application configuration remain separate controls. |
| Auth0 token verification | [#1008](https://github.com/QRun-IO/qqq/issues/1008) confirms issuer/audience validation and cache-boundary findings. [Backend PR #1012](https://github.com/QRun-IO/qqq/pull/1012) contains the correction; 164 focused tests passed on its combined backend tree. The exact-head CI passed and the correction merged into backend develop as `fc67393b`. An unconfigured audience remains supported; no audience enforcement is claimed in that configuration. |
| Signed back-channel logout | [#909](https://github.com/QRun-IO/qqq/issues/909) remains open for stable release delivery. [Backend PR #1021](https://github.com/QRun-IO/qqq/pull/1021) merged the qualified correction into stable main as `9168e434`; its merge tree equals the qualified candidate and the automatic post-merge reactor/sample/static gates passed. Published 4.0.0 remains affected; planned 4.0.1 is not published or artifact-qualified. PR #1018 is closed as superseded, not merged; its original Mongo failure is retained. The corrected 4.1 RC line remains distinct. |
The other scoped finding dispositions remain as reconciled in #696. NUI-10's
runtime-style exception remains deliberate; it does not allow inline scripts.
Negative security tests intentionally produce denied-resource and CSP diagnostics.
A passing test run does not mean those diagnostic arrays are all empty.
### Evidence limits
The reference remains ASVS 4.0.3. Owned identity-provider fixtures do not qualify a
real Auth0 tenant, MFA or step-up policy, provider-grant revocation, or deployment
TLS, HSTS and proxy trust. The approved real-provider exclusion remains explicit.
Configurable TABLE_BASED idle expiration does not prove a 12-hour active / 30-minute
idle policy. Cookie-prefix and shared-host suitability, generic CSRF/Origin
protection, all-session revocation, and application-sensitive storage/cache policy
also remain outside the demonstrated guarantees. These limits are not newly
confirmed High/Critical exploits and must not be presented as universal compliance.
The 48-case standalone result covers its selected security scope and paired
candidate backend. Its analytics case uses an owned plugin through the real
application hook, not a vendor SDK or real account. It does not
replace full-browser acceptance. The separate 3,015-case Javalin gate qualifies
the merged PR40 tree; neither result resolves separately tracked browser failures
or certifies a real provider or deployment. Final 1.0 still requires owner visual
acceptance and real-application soak.
## Historical review — September 25, 2026
Independent review of the QQQ Next admin UI against OWASP ASVS 4.0.3 Level 2, for the
Next UI 1.0 milestone (epic QRun-IO/qqq#713). Reviewed on 2026-09-25 at
`feature/GH-649-next-acceptance` (`2b6569f`, release 0.2.0) with the backend at
`feature/GH-649-next-default` (`f7bf8c4ac`). Fixes are on `feature/next-1.0-security`
(frontend), `feature/next-1.0-security-backend` (qqq) and, for TABLE_BASED,
`feature/next-1.0-security-auth-backend` (qqq, on top of `feature/next-1.0-auth-backend`).
## Scope
- Sign-in lifecycle for every supported authentication type: MOCK, FULLY_ANONYMOUS,
OAUTH2 (authorization code + PKCE, code redeemed by the backend) and AUTH_0
(authorization code + PKCE as a public client). TABLE_BASED (password sign-in, being added
in QRun-IO/qqq#700) was reviewed at the backend: session validation, password attempts
and the protection of its user and session tables (NUI-12 to NUI-14).
- Token and session storage in the browser: the `sessionUUID` / `sessionId` cookies, the
localStorage auth-metadata cache and stored identity, the sessionStorage PKCE attempt.
- Logout and server-side revocation (QRun-IO/qqq#674, #675).
- Post-login `returnTo` handling (QRun-IO/qqq#669).
- HTML rendering: every `dangerouslySetInnerHTML` / `innerHTML` sink (field values, grid
cells, help content, widgets, blocks, process steps, banners, the rich text editor) and
branding `customCss`.
- Permission gating in the UI (QRun-IO/qqq#671) and its server-side enforcement.
- Response headers for the dashboard (QRun-IO/qqq#695, delivered with this review).
Out of scope: the QQQ backend authentication modules themselves (token validation, session
store lifetime), the Material dashboard, and deployment concerns (TLS, HSTS at the proxy).
## Method
Manual code review of `src/lib/auth`, `src/lib/api`, `src/app/(auth)`, the dashboard layout
and every HTML sink, the backend session and cookie code in `qqq-middleware-javalin`, and the
built static export (inline scripts, runtime style injection, external origins). Each
finding was reproduced (unit test or browser) before it was fixed, and every fix has a
regression test: a Vitest unit test and, where the behavior is visible end to end, a row in
the real-backend acceptance matrix (`tests/acceptance/matrix/security.json`), run in
Chromium, Firefox, WebKit and the phone profile.
## Summary
| ID | Finding | ASVS 4.0.3 | Severity | Disposition |
|----|---------|------------|----------|-------------|
| NUI-01 | `returnTo` paths with dot segments normalize to `//host` (open redirect) | 5.1.5 | Medium | Fixed |
| NUI-02 | Session cookies had no `SameSite` and no `Secure` | 3.4.1, 3.4.3 | Medium | Fixed (backend) |
| NUI-03 | Sanitized HTML could still add forms, password inputs, style sheets and page-covering overlays | 5.2.1 | Medium | Fixed |
| NUI-04 | No Content-Security-Policy, framing, referrer or permissions headers on the dashboard | 14.4.3, 14.4.6, 14.4.7 | Medium | Fixed (#695) |
| NUI-05 | `sessionUUID` is readable by script (not `HttpOnly`) | 3.4.2 | Medium | Follow-up QRun-IO/qqq#733 |
| NUI-06 | Recently viewed records and the stored identity survive a session expiry and are shown to the next user of the browser | 8.2.3 | Low | Fixed |
| NUI-07 | The login page displays arbitrary `?error=` text (content spoofing) | 7.4.1 | Low | Fixed |
| NUI-08 | Unused browser auth SDKs and cookie/crypto helpers in the dependencies | 14.2.2 | Low | Fixed |
| NUI-09 | The standalone (container image) Next server sends none of the #695 headers | 14.4.3 | Low | Follow-up QRun-IO/qqq#734 |
| NUI-10 | `style-src` allows `'unsafe-inline'` | 14.4.3 | Info | Accepted |
| NUI-11 | Branding `customCss` is filtered with a deny-list | 5.2.1 | Info | Accepted |
| NUI-12 | TABLE_BASED: the inactivity timeout was not checked for 30 minutes after each validation | 3.3.2 | Medium | Fixed (backend) |
| NUI-13 | TABLE_BASED: no rate limiting or lockout of password attempts | 2.2.1 | Medium | Fixed (backend) |
| NUI-14 | TABLE_BASED: the user table (password hashes) and session table (session ids) were readable by any session unless the application protected them | 4.1.3, 8.3.4 | High | Fixed (backend) |
No Next UI finding allowed script execution, session theft without prior script execution,
or a permission bypass: the backend enforced every permission the UI hides (SEC-001 to
SEC-015 prove the refusals by API and SQL), DOMPurify removed all active content, and React
19 blocks `javascript:` URLs. The most serious finding is in the TABLE_BASED backend module
(NUI-14): its default metadata left the session table, and with it other users' session ids,
readable by any signed-in session.
## Findings
### NUI-01 Open redirect through dot segments in `returnTo` (fixed)
`safeReturnTo()` rejected `//host` and `/\host` before parsing, then returned the parsed
`pathname`. The WHATWG URL parser removes dot segments, so `/.//evil.example`,
`/..//evil.example`, `/app/..//evil.example` and `/app/../\evil.example` passed the check and
came back as `//evil.example`, which `router.replace()` treats as another origin. The login
page and the OAuth callback redirect to this value.
Fix: `src/lib/auth/return-to.ts` also rejects a normalized path that starts with `//` or
`/\`. Tests: `src/lib/auth/return-to.test.ts` (seven bypass shapes, harmless dot segments
still normalize); acceptance SEC-024 (new negative scenario, `headers.spec.ts`).
### NUI-02 Session cookies without `SameSite` and `Secure` (fixed, backend)
`sessionUUID` and `sessionId` were set with `context.cookie(name, value, maxAge)`: no
`SameSite`, no `Secure`. Chromium treats that as `Lax`, but Firefox and Safari send such
cookies with cross-site POSTs, and several legacy endpoints accept form-encoded and multipart
bodies, so another site could submit requests with the user's session (CSRF, ASVS 4.2.2).
Over HTTPS the cookies could also be sent over plain HTTP.
Fix: `QJavalinImplementation.setSessionCookie()` sets both cookies (legacy and v1
manageSession, the per-request `sessionId` refresh) with `Path=/`, `SameSite=Lax`, and
`Secure` when the request came over HTTPS (the connection or `X-Forwarded-Proto`). Plain
HTTP development keeps working. The OAuth return from the identity provider is a top-level
GET, which Lax cookies accompany. Tests: `ManageSessionSpecV1Test.testSessionCookieAttributes`;
acceptance SEC-044.
### NUI-03 Stored HTML could impersonate the application (fixed)
All sinks used DOMPurify's default profile. It removes scripts, handlers and `javascript:`
URLs, but keeps `<style>`, `<form>`, form controls and inline `position: fixed`. An HTML field
value (any user who can edit the record), a widget or a process step could therefore show a
full-page "session expired" overlay with a password field that posts elsewhere, or restyle
the whole page, inside the trusted origin.
Fix: one sanitizer, `src/lib/utils/sanitize-html.ts`, used by every sink (FieldValue,
DataCell, HelpContent, SafeHtml and the widgets that use it, BlockWidget, HtmlComponent and
ProcessStepScreen, Banner, RichTextField). It keeps DOMPurify's defaults and additionally
removes `style`, `form`, `input`, `button`, `textarea`, `select`, `option`, `optgroup`,
`datalist` and `dialog`, and drops inline `position` other than `static`/`relative`
(including `var()` values). Formatting, links, images, tables and other inline styles stay.
The Content-Security-Policy's `form-action 'self'` blocks off-site form posts as a second
layer. Tests: `src/lib/utils/sanitize-html.test.ts`; acceptance SEC-042 (a stored form,
password input, style element and fixed overlay render as plain content; the database value
is unchanged); REC-020 still passes.
### NUI-04 No security headers on the dashboard (fixed, QRun-IO/qqq#695)
`NextDashboardRouteProvider` sent only `X-Content-Type-Options`. It now sends, per
`NextDashboardSecurityHeaders`:
```
Content-Security-Policy: default-src 'self'; script-src 'self' <custom component origins> 'sha256-<each inline script>';
style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:;
connect-src 'self' <identity provider origins>; frame-src 'self' <QuickSight when used>;
worker-src 'self' blob:; manifest-src 'self'; media-src 'self' data: blob:; object-src 'none';
base-uri 'self'; form-action 'self'; frame-ancestors 'none'
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: accelerometer=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), hid=(),
magnetometer=(), microphone=(), midi=(), payment=(), serial=(), usb=()
X-Content-Type-Options: nosniff
```
- The static export has two inline scripts per page (the router payload bootstrap). The
provider hashes the inline scripts of each exported HTML file once and lists exactly those
hashes, so no other inline script, inline event handler or `javascript:` URL runs, and no
`'unsafe-eval'` is needed.
- Origins come from metadata: OAUTH2 and AUTH_0 `baseUrl` (browser discovery and the Auth0
token exchange), `https://*.quicksight.aws.amazon.com` when a quickSightChart widget
exists, and each customComponent widget's `componentSourceUrl` origin.
- Override hook: `QApplicationJavalinServer.withNextDashboardSecurityHeadersCustomizer(...)`
(or `NextDashboardRouteProvider.withSecurityHeadersCustomizer`) receives the headers after
those additions, can add, replace or remove directives and headers, switch to report-only,
and runs again on hot swap. Values are validated (no `;`, `,` or line breaks).
- Referrer: other origins get only the application origin, never a path or query (the
`/token?code=` URL included).
Tests: `NextDashboardRouteProviderTest` (exact headers, hashes computed independently,
instance origins, customizer and hot swap, report-only/disabled, rejected values) and
`QApplicationJavalinServerTest`; acceptance SEC-038 (every document type, hashes match the
served document, framing refused, injected inline script and handler refused and reported),
SEC-039 (OAUTH2 and AUTH_0 sign-in under the policy, other origins refused), SEC-040 (the
acceptance application's override hook and the metadata-derived origins). The diagnostics
fixture now fails any test on a `securitypolicyviolation` event, in every frame and browser,
so the whole suite proves the policy breaks nothing.
### NUI-05 `sessionUUID` readable by script (follow-up QRun-IO/qqq#733)
The backend sets `sessionUUID` without `HttpOnly` because both dashboards read it:
the Next UI resumes OAUTH2/AUTH_0 sessions by posting the value to `manageSession`, and the
Material dashboard does the same. Making it `HttpOnly` needs a backend resume-from-cookie
path and changes in both UIs, which is outside this review. Mitigations in place: the
Content-Security-Policy blocks injected script (NUI-04), the sanitizer (NUI-03), `SameSite`
and `Secure` (NUI-02), and server-side logout invalidation (SEC-033).
### NUI-06 Per-user browser data survived session expiry (fixed)
Logout cleared the recently viewed records and stored identity, but a session that expired
(or was revoked) left them in localStorage; the next person to sign in on the browser saw the
previous user's record labels on the home page, and an OAUTH2 resume without identity values
could show the previous user's name.
Fix: `claimClientData()` (`src/lib/auth/auth-storage.ts`) binds that data to the signed-in
identity; the auth provider calls it before storing a new identity (session creation, resume
and the OAuth callback), and a different identity clears it. Tests:
`src/lib/auth/auth-storage.test.ts`; acceptance SEC-043.
### NUI-07 Login page content spoofing (fixed)
`/login?error=<text>` rendered `Sign-in failed (<text>).` for any value, so a link could put
arbitrary instructions ("call this number") on the sign-in page. React escaped it, so this
was spoofing, not injection.
Fix: `src/lib/auth/callback-errors.ts` explains known OAuth and callback codes, shows other
well-formed codes (`^[a-z][a-z0-9_]{0,63}$`) as a code, and shows `Sign-in failed.` for
anything else. Tests: `src/lib/auth/callback-errors.test.ts`; acceptance SEC-041.
### NUI-08 Unused auth dependencies (fixed)
`@auth0/auth0-react`, `oidc-client-ts`, `universal-cookie` and `ts-md5` were declared but
never imported: the flows are implemented once in `src/lib/auth/oidc.ts`. They were not in
the bundle, but looked like a second auth stack to reviewers and dependency scanners. They
are removed; `src/lib/auth/auth-dependencies.test.ts` keeps them out. (Other unused runtime
dependencies, such as `lodash` and `date-fns`, are not security-relevant and are left to the
dependency work in QRun-IO/qqq#698.)
### NUI-09 Standalone server headers (follow-up QRun-IO/qqq#734)
The container image runs the Next standalone server, which does not go through
`NextDashboardRouteProvider`. It needs the same headers; the script hashes have to be
computed from the prerendered pages after the build. Applications served by QQQ (javalin
mode, the default) have them now.
### NUI-10 `style-src 'unsafe-inline'` (accepted)
The UI creates style elements at runtime: the dialog scroll lock (react-remove-scroll /
react-style-singleton, used by every Radix dialog), sonner toasts, React's style hoisting and
branding `customCss`, and the prerendered pages carry style attributes. Hashes cannot cover
runtime styles and a per-response nonce does not fit a static export. Inline styles cannot
run script; the injection paths for style elements from data are closed by the sanitizer
(NUI-03), and image exfiltration through CSS is limited to HTTPS origins.
### NUI-11 `customCss` deny-list (accepted)
Branding `customCss` comes from the application's metadata (developer-controlled, not user
data). The layout strips `</style>`, `expression(`, `@import`, `javascript:` and `data:` URLs
and applies it through `textContent`. With the new policy it cannot load script, fonts or
frames from other origins. Accepted as trusted configuration.
### TABLE_BASED authentication (NUI-12 to NUI-14)
Added to the review scope from the TABLE_BASED work (QRun-IO/qqq#700, branch
`feature/next-1.0-auth-backend`). The fixes are on `feature/next-1.0-security-auth-backend`,
which is based on that branch and must be merged after it.
**NUI-12 Inactivity timeout not enforced promptly (fixed).**
`TableBasedAuthenticationModule.isSessionValid` trusted a validation for 30 minutes without
looking at the session's idle time, so an `inactivityTimeoutSeconds` below about 30 minutes
was not enforced; the table's access time was also only refreshed on revalidation, which
could expire an active session with a short timeout. Now the last request per session is
kept next to the last validation, a request after more than the timeout of idleness is
checked against the table again (using the later of the table's and this server's last
activity), and a validation is trusted for at most half the timeout (still at most 30
minutes). Expired validations are forgotten. Tests: `TableBasedAuthenticationModuleTest`
(`testShortInactivityTimeoutIsEnforcedPromptly`, `testActiveSessionIsNotExpiredEarly`,
`testValidationIntervalIsAtMostHalfTheTimeout`).
**NUI-13 No password attempt limiting (fixed).** Password sign-in (Basic credentials on v1
`manageSession` and every other Basic-auth path into the TABLE_BASED module) had no limit.
A soft lockout now refuses a username for `signInLockoutSeconds` (default 900) after
`maxFailedSignInAttempts` (default 5) failures within that window, before the password is
checked, with "Too many failed sign-in attempts. Try again later." Unknown usernames are
counted the same way (no enumeration; the unknown-user path already hashes). A success
resets the count; both settings are on `TableBasedAuthenticationMetaData`, and 0 disables
the lockout. The count is kept per server in memory; per-address limiting belongs at the
proxy. Tests: `TableBasedAuthenticationModuleTest` (`testSignInLockout`,
`testSignInLockoutResetAndDisable`), `TableBasedSessionSpecV1Test.testRepeatedFailuresLockTheUsernameOut`
(over HTTP; no session row is created).
**NUI-14 Authentication tables readable (fixed).** With the instance's default permission
rules (often `NOT_PROTECTED`), any signed-in session could query the user table (password
hashes) and the session table (whose ids are bearer tokens for other users' sessions).
`QInstanceEnricher` now protects the configured user and session tables of every
TABLE_BASED provider when the application has not set permission rules on them:
`READ_INSERT_EDIT_DELETE_PERMISSIONS` with `DenyBehavior.HIDDEN`, so only sessions granted
`user.read` (and so on) see them. The password hash field is always hidden, so the API never
returns it even to such users. The module reads the hash itself with hidden fields
included, and works on both tables with an internal session that holds every permission.
Applications that set their own rules keep them. Tests:
`TableBasedAuthenticationModuleTest.testAuthenticationTablesProtectedByDefault`.
## Verified without findings
| Area | What was checked | Evidence |
|------|------------------|----------|
| PKCE | S256; 32-byte verifier (43 chars); 16-byte state; state removed before comparison (single use); verifier removed after use and on failure; fixed redirect URI `{origin}/token` | `src/lib/auth/oidc.ts`, `CallbackContent.tsx`; SEC-025, SEC-026 (forged state rejected before any token exchange) |
| OAUTH2 code exchange | Redeemed by the backend with its client secret and the verifier; the browser never holds tokens | SEC-025 |
| AUTH_0 tokens | Public client with PKCE; the access token is only posted to `manageSession` and never stored; the ID token is used for display only and the backend verifies the access token against the provider keys | `auth-provider.tsx`; SEC-029, SEC-033 |
| Anonymous types | MOCK and FULLY_ANONYMOUS create a session through `manageSession`; a denied session is an error, never a silent anonymous fallback | SEC-020, SEC-023, SEC-031 |
| Logout | Backend session deleted and every issued cookie expired (#674, #675); provider end-session called; client caches, identity and recent records cleared; the tab stays signed out | SEC-021, SEC-027, SEC-033 |
| Expiry and revocation | Any 401 outside the session endpoints clears the query cache and returns to login with `returnTo`; the automatic re-authentication loop stops after three attempts a minute | `client.ts`, `auth-storage.ts`; SEC-022, SEC-028 |
| Browser storage | localStorage: auth metadata (public configuration, 10 minute TTL), display identity, recent records, UI preferences; sessionStorage: one PKCE attempt and the signed-out marker; no tokens or secrets | `auth-storage.ts`, `api/auth.ts`, `oidc.ts` |
| Links | React 19 replaces `javascript:` URLs; link adornments and file URLs are limited to http(s) and same-origin paths; every `target="_blank"` link has `rel="noopener noreferrer"` | `adornment-utils.ts`, `FieldValue.tsx`, `BlockSlot.tsx` |
| Permission gating | Tables, records, processes, reports, widgets and apps are hidden or disabled from metadata permissions and capabilities; the backend refuses the same actions | `permissions.ts`; SEC-001 to SEC-015 |
| Secrets in metadata | Widget secrets (QuickSight keys) never reach the browser | WID-015 |
## Evidence
Recorded 2026-09-25 on macOS, sample `4.1.0-SNAPSHOT` built from
`feature/next-1.0-security-backend`, static export of `feature/next-1.0-security`, the
policy on and the diagnostics fixture failing on any CSP violation:
- `QQQ_ACCEPTANCE_BROWSERS=chromium,firefox,webkit,mobile node scripts/acceptance.mjs`:
chromium 337/337, webkit 337/337, mobile 15/15, firefox 336/337. The one Firefox failure
was REC-045 (a possible-value search result not listed within 15 s under machine load; no
diagnostics or policy violation); it passed 5 of 5 on repeat and in a rerun of the records
area. Every new row (SEC-038 to SEC-044) passed in every project that runs it.
- Unit: `pnpm test` 96 files / 957 tests; `pnpm typecheck`; `pnpm lint` (0 errors).
- Backend: `mvn -pl qqq-middleware-javalin verify` 473 tests, checkstyle 0, coverage met;
`mvn -pl qqq-backend-core verify` (TABLE_BASED branch) 1988 tests, checkstyle 0, coverage
met; `TableBasedSessionSpecV1Test` 3/3.
## Residual risk and recommendations
- Session lifetime (ASVS 3.3.2) is owned by the backend authentication modules and the
session store; cookies last 24 hours. TABLE_BASED now enforces its inactivity timeout
(NUI-12); it has no absolute lifetime, and the other modules follow their provider.
- The TABLE_BASED lockout (NUI-13) is per server and in memory; clustered deployments get
one budget per node, so put per-address rate limiting at the proxy as well.
- HSTS belongs at the TLS terminator in front of the application.
- NUI-05 and NUI-09 are tracked in QRun-IO/qqq#733 and #734.
Loaded 100 of 1338 files, more files were not shown because too many files have changed in this diff. Show more