tui: adorned grid cells, text primary keys on create; verified live on the qqq fix branch

Found on the fix-branch sample (8bf6a5a66, private server on 8091): grid
cells showed REVEAL secrets and raw RENDER_HTML; they are now drawn like the
record view (masked, text, CHIP brackets). A WIDGET-adorned field with a
plain value shows the value. Create forms ask for a text primary key (the
archive table's file name) and leave out generated numeric keys. Parity
notes record what was verified live there: weekday criteria (QRY-085
pass), case-insensitive matching, AdornmentLab adornments, the composite
association, the locker refusal, uploads and downloads, sharing, saved
report setup, help. NAV-030 and PRC-020 stay blocked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
james.maesandClaude Opus 5.5 committed 2026-10-31 21:49:37 -05:00
1 parent ad09a469f3
commit ebe700bcba
7 files changed
+80 -30

No files matched your search

+25 -25
View File
@@ -31,13 +31,13 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| NAV | 40 | 20 | 0 | 0 | 1 | 19 |
| PRC | 60 | 55 | 0 | 0 | 1 | 4 |
| PRF | 5 | 5 | 0 | 0 | 0 | 0 |
| QRY | 70 | 60 | 0 | 0 | 1 | 9 |
| QRY | 70 | 61 | 0 | 0 | 0 | 9 |
| REC | 65 | 59 | 0 | 0 | 0 | 6 |
| REL | 9 | 9 | 0 | 0 | 0 | 0 |
| RPT | 20 | 20 | 0 | 0 | 0 | 0 |
| SEC | 43 | 30 | 0 | 0 | 1 | 12 |
| WID | 69 | 56 | 0 | 0 | 0 | 13 |
| **all** | 401 | 330 | 0 | 0 | 4 | 67 |
| **all** | 401 | 331 | 0 | 0 | 3 | 67 |
## Rows
@@ -72,7 +72,7 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| NAV-027 | Developer page reports the backend metadata counts and the running framework versions | pass | pass | qqq whoami: apiVersion, features, counts; TUI: F9 › Connection: UI API version, features, extensions, metadata generation and age, client build; live |
| NAV-028 | Reports in the app tree are listed under their app (sidebar and app home Reports group) and open the report pa | pass | pass | qqq report list (with app) and apps show list reports in the app's Reports group, report run opens them; unit-tested and stub-server tested (the sample has no reports); TUI: reports listed under their app and in the tree, palette and F9; Enter runs one (format chosen, process screens, file saved) (unit-tested only; the sample has no reports) |
| NAV-029 | A user who may access no apps sees the Dashboard entry and the no-apps message (Material NoApps parity) | n/a | n/a | browser shell |
| NAV-030 | Global search (header box, '/' dialog, search page) lists the records a backend record search (POST /qqq/v1/se | pass | blocked | qqq search over POST /search; TUI: the palette calls POST /search; the sample lists recordSearch with no provider (server finding 21) |
| NAV-030 | Global search (header box, '/' dialog, search page) lists the records a backend record search (POST /qqq/v1/se | pass | blocked | qqq search over POST /search; TUI: the palette calls POST /search; the sample lists recordSearch with no provider (server finding 21); live on fix branch 8bf6a5a66: recordSearch is no longer advertised and the palette says so; POST /search still has no provider (finding 21) |
| NAV-031 | Record search respects table read permissions and record security locks, in the UI and the API | pass | pass | server-enforced; refusals shown; TUI: server-enforced; the palette shows only what the server returns |
| NAV-032 | Without searchable tables (the backend declares no searchFields) global search stays local and never calls a s | n/a | n/a | browser shell |
| NAV-033 | Login branding before sign-in: logo, app name, accent color, favicon and title from GET /qqq/v1/metaData/authe | n/a | n/a | browser shell |
@@ -107,7 +107,7 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| PRC-017 | Dynamic step list from processMetaDataAdjustment.updatedFrontendStepList | pass | pass | updatedFrontendStepList followed; TUI: kernel runner |
| PRC-018 | Updated field metadata from processMetaDataAdjustment.updatedFields | pass | pass | updatedFields applied; TUI: kernel runner |
| PRC-019 | Async job progress in the live job view (message, N / M, determinate progress bar, elapsed) then continue | pass | pass | message, N/M, elapsed on stderr; TUI: bar, N/M, message, elapsed |
| PRC-020 | Cancel from a screen and mid-job runs the cancel step and leaves | pass | blocked | Ctrl-C during a job or a prompt calls POST …/cancel, exit 130; TUI: Esc asks, then POST cancel; the sample accepts the cancel and keeps the run (server finding 13) |
| PRC-020 | Cancel from a screen and mid-job runs the cancel step and leaves | pass | blocked | Ctrl-C during a job or a prompt calls POST …/cancel, exit 130; TUI: Esc asks, then POST cancel; the sample accepts the cancel and keeps the run (server finding 13); still blocked on the fix branch (finding 13) |
| PRC-021 | Step errors: user-facing messages directly, internal errors behind a detail toggle, Close | pass | pass | userFacingError shown; TUI: failure message shown; F5 re-runs |
| PRC-022 | Retry after an error restarts with the original record selection | n/a | pass | retry is re-running the command; TUI: F5 or r runs again with the same records |
| PRC-023 | Completion: Next/Submit labels, Return on the last screen or when noMoreSteps | n/a | pass | screen labels; TUI: Ctrl+S continues; the last screen closes with Enter or Esc |
@@ -162,7 +162,7 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| QRY-005 | Column widths, density and card view | n/a | pass | grid presentation; TUI: v: widths, fit or fill, zebra, density |
| QRY-006 | Opening a record and returning keeps the query; refresh shows backend changes | n/a | pass | TUI: Esc lands on the row last viewed, filter and page kept |
| QRY-007 | A failed query reports the backend error with a retry | pass | pass | error with code, status and trace id; TUI: error shown in the grid; F5 retries |
| QRY-010 | Text operators match Material labels and backend semantics | pass | pass | all operators and shorthand; text matches (~, CONTAINS, STARTS_WITH, …) carry CASE_INSENSITIVE by default, ~= / OPERATOR:CS / --case-sensitive match case (finding 11 is by design; the v1 route drops the option until finding 27, qqq#1358); TUI: operators by field type in the filter dialog |
| QRY-010 | Text operators match Material labels and backend semantics | pass | pass | all operators and shorthand; text matches (~, CONTAINS, STARTS_WITH, …) carry CASE_INSENSITIVE by default, ~= / OPERATOR:CS / --case-sensitive match case (finding 11 is by design; the v1 route drops the option until finding 27, qqq#1358); TUI: operators by field type in the filter dialog; text matches ignore case (CASE_INSENSITIVE), Match exact case on request (live on fix branch 8bf6a5a66) |
| QRY-011 | Number operators including ranges and lists | pass | pass | BETWEEN, IN lists |
| QRY-012 | Date operators and relative date expressions | pass | pass | now(), now(-7d), thisPeriod(), startOf(); TUI: date presets: now(-7d), thisPeriod(month) … |
| QRY-013 | Date-time operators convert local input to UTC and support relative values | pass | pass | --where/--group DATE_TIME values local to UTC (a date is local midnight); expressions as before; TUI: DATE_TIME criteria typed in the local zone go out as UTC instants; expressions pass through (unit-tested) |
@@ -171,7 +171,7 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| QRY-016 | AND/OR and nested groups combine as declared | pass | pass | --group with parentheses; TUI: AND/OR and groups, guided and advanced |
| QRY-017 | Filters live in the URL: reload, back/forward and Material-style JSON links | pass | n/a | --print-filter gives the JSON the web links accept; TUI: URL state is browser-only; the TUI shows the grammar and the CLI command, and qqq ui accepts the command as a route |
| QRY-018 | Backend-only operators from links render and filter correctly | pass | pass | any operator in --where; TUI: More operators… offers every operator |
| QRY-019 | Quick search combines with the advanced filter | pass | pass | --search on query, count and export: CONTAINS over the shown text fields, ORed, ANDed with the filter; sent with CASE_INSENSITIVE (--case-sensitive to match case; the v1 route drops the option until finding 27, qqq#1358); TUI: q quick search ANDed onto the filter (kernel) |
| QRY-019 | Quick search combines with the advanced filter | pass | pass | --search on query, count and export: CONTAINS over the shown text fields, ORed, ANDed with the filter; sent with CASE_INSENSITIVE (--case-sensitive to match case; the v1 route drops the option until finding 27, qqq#1358); TUI: q quick search ANDed onto the filter (kernel); q avery finds Avery (CASE_INSENSITIVE); =Text matches exact case (live on fix branch 8bf6a5a66) |
| QRY-020 | One-side exposed join: labelled columns, filters and sorts match SQL | pass | pass | --join and table.field in --fields/--where/--order, labelled "Join: Field"; verified live through an association join (person to pet); exposed joins unit-tested (the sample exposes none); TUI: joined columns and filters, labelled "Join: Field", joins planned by the kernel (unit-tested only; the sample exposes no joins) |
| QRY-021 | Many-side exposed join repeats rows and reports the distinct count | pass | pass | a many-side join repeats rows; count and --count add the distinct count (person --join pet: 21 rows, 16 distinct); TUI: many-side join: rows repeat and the status shows the distinct count (unit-tested only) |
| QRY-022 | Column statistics aggregate the filtered rows | pass | pass | qqq table stats <t> <field> [-w …] through the columnStats process when offered (unit-tested; the sample has neither QUERY_STATS nor the process); --local computes the same figures and was verified live; TUI: S: kernel column statistics of the column under the cursor with the filter (unit-tested only; no sample QUERY_STATS table) |
@@ -208,7 +208,7 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| QRY-082 | Basic mode cannot simplify repeated or nested advanced conditions and explains why | n/a | n/a | TUI: the guided builder shows every condition; no basic mode |
| QRY-083 | Advanced query shows and removes individual conditions (Next inline editor) | n/a | pass | TUI: guided builder edits and removes conditions |
| QRY-084 | Sort picker changes field and direction; clearing filters requires confirmation and keeps the sort | n/a | pass | TUI: sort in the builder; c clears |
| QRY-085 | Weekday criteria use the metadata-enabled date function and ISO weekday values | blocked | blocked | weekday(field) IN 1,7 sends the WeekdayOfDate fieldFunction with ISO weekdays (--print-filter); the sample's H2 backend answers 500 (server finding 24, qqq#1355); TUI: weekday(field) IN days sent as the WeekdayOf fieldFunction; the sample H2 backend fails it (server finding 24, qqq#1355) |
| QRY-085 | Weekday criteria use the metadata-enabled date function and ISO weekday values | blocked | pass | weekday(field) IN 1,7 sends the WeekdayOfDate fieldFunction with ISO weekdays (--print-filter); the sample's H2 backend answers 500 (server finding 24, qqq#1355); TUI: weekday(birthDate) IN 1,7 returns its 2 people (live on fix branch 8bf6a5a66; on the released sample it fails, finding 24, qqq#1355) |
| QRY-086 | Relative date presets expose their evaluated date and filter records | pass | pass | expressions |
| QRY-087 | Bulk pasted values become a multi-value filter | pass | pass | IN lists; quoted values; TUI: IN takes a pasted comma list |
| QRY-088 | Instance query help accompanies the bulk paste dialog | n/a | pass | TUI: paste values split on commas, tabs, semicolons, new lines; the instance bulkAddFilterValues help shown (unit-tested only; the sample has no such help) |
@@ -224,8 +224,8 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| REL-004 | Three-level associations: each pet shows only its own notes | pass | pass | person > pets > notes: each pet lists only its own notes; TUI: child lists three levels deep (a pet's notes under the pet); live on person 1 |
| REL-005 | Editing and deleting a child leaves no stale parent, list or unexpected rows | pass | pass | records are never cached: update or delete a child, the next get/children shows the current rows; TUI: the parent reloads on return |
| REL-006 | Denied child reads show an unavailable panel without data or actions | pass | pass | unreadable child tables shown as unavailable without data or actions; table children shows the server message; unit-tested (the sample has no denied child table, finding 17); TUI: unavailable panel (kernel associationGroups) |
| REL-007 | Aliased and composite associations bind to their widgets and list the right members | pass | pass | aliased and composite associations bound to their widget sections, members by every join value; unit-tested (the sample declares none, finding 17); TUI: kernel associationGroups binds aliased associations to their widgets |
| REL-008 | Adding through a composite association stores every join value | pass | pass | table create --parent sets every join value of a composite association; unit-tested (the sample declares none); TUI: a new child through a composite association gets every join value (unit-tested only) |
| REL-007 | Aliased and composite associations bind to their widgets and list the right members | pass | pass | aliased and composite associations bound to their widget sections, members by every join value; unit-tested (the sample declares none, finding 17); TUI: kernel associationGroups binds aliased associations to their widgets; adornmentLab tags through the composite association (live on fix branch 8bf6a5a66) |
| REL-008 | Adding through a composite association stores every join value | pass | pass | table create --parent sets every join value of a composite association; unit-tested (the sample declares none); TUI: a new child through a composite association gets every join value; a new adornmentLab tag gets adornmentLabId and labKey (live on fix branch 8bf6a5a66) |
| REL-009 | Deleting a parent removes associated children and leaves other parents intact | pass | pass | table delete of a parent: its pets are gone, other parents keep theirs; TUI: server cascade; the list reloads |
| REC-001 | Record view shows the record label, tiered sections and backend-formatted values | pass | pass | table get by section and tier; TUI: sections by tier, two columns when they fit |
| REC-002 | Empty values render a placeholder; hidden fields and hidden sections are never shown | pass | pass | empty values show --; hidden sections and fields never shown; unit-tested (the sample has none, finding 17); TUI: hidden fields and sections are left out; empty values blank |
@@ -248,22 +248,22 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| REC-019 | Zoned DATE_TIME display behaviors (fixed zone and per-record zone) | n/a | pass | display zones; TUI: DATE_TIME through the kernel displayDateTime: server-zoned display values as sent, else the viewer's zone; live on Field Lab |
| REC-020 | HTML field edits in the rich-text editor and renders sanitized | pass | n/a | HTML fields shown as text (sanitized); table update -e <field> edits the HTML source in $VISUAL/$EDITOR; verified live on fieldLab htmlValue; TUI: the rich-text editor is a browser component; HTML is edited as source in $EDITOR and shown as text |
| REC-021 | PASSWORD is masked on read and never overwritten by the mask | pass | pass | server masks; TUI: masked |
| REC-022 | BLOB upload and download without a download adornment | pass | pass | table create/update --upload and table download (base64 BLOB value) verified on fieldLab; default name into the download directory; TUI: BLOB upload through the file dialog and save from the record (Field Lab, live) |
| REC-022 | BLOB upload and download without a download adornment | pass | pass | table create/update --upload and table download (base64 BLOB value) verified on fieldLab; default name into the download directory; TUI: BLOB upload through the file dialog and save from the record (Field Lab, live); upload into uploadedFileArchive (text primary key typed on create) and download of the same 22 bytes (live on fix branch 8bf6a5a66) |
| REC-023 | Dynamic default values (user id, create day, modify day, explicit NONE) | pass | pass | server; TUI: defaults applied by FormSession and the server |
| REC-024 | Max-length policies: truncate, ellipsis, pass-through and reject | pass | pass | maxLength checked locally; TUI: local check and server policy |
| REC-025 | Case and whitespace normalization on save | pass | pass | behaviors; TUI: behaviors on save |
| REC-026 | Numeric range policies: reject, exclusive reject, inclusive clip and exclusive clip | pass | pass | ranges checked locally; TUI: local range check and server policy |
| REC-027 | Unique keys are enforced with the backend message on create and edit | pass | pass | server message; TUI: server message shown |
| REC-028 | LINK adornment: record links and URL links | pass | pass | record links name table and key, --follow opens them; URL links show the URL; TUI: links marked, Enter follows (kernel record-view) |
| REC-029 | CHIP adornment colors and icons by value | pass | pass | CHIP as [value] colored by color.<value> (or colorMap) on a terminal, icon named in the record view; unit-tested (none in the sample); TUI: CHIP values bracketed and colored by the adornment color (unit-tested only) |
| REC-030 | SIZE adornment sets the query grid column width | n/a | pass | TUI: saved widths per column; SIZE as text width |
| REC-028 | LINK adornment: record links and URL links | pass | pass | record links name table and key, --follow opens them; URL links show the URL; TUI: links marked, Enter follows (kernel record-view); adornmentLab carrier LINK (live on fix branch 8bf6a5a66) |
| REC-029 | CHIP adornment colors and icons by value | pass | pass | CHIP as [value] colored by color.<value> (or colorMap) on a terminal, icon named in the record view; unit-tested (none in the sample); TUI: CHIP values bracketed and colored by the adornment color; adornmentLab CHIP [ACTIVE] in the grid and the record (live on fix branch 8bf6a5a66) |
| REC-030 | SIZE adornment sets the query grid column width | n/a | pass | TUI: saved widths per column; SIZE as text width; adornmentLab SIZE (live on fix branch 8bf6a5a66) |
| REC-031 | CODE_EDITOR adornment: code view with formatting and a code editor on forms | pass | n/a | CODE_EDITOR values shown verbatim; table update -e <field> opens the code in $EDITOR with the language's extension; unit-tested (none in the sample; -e verified live on an HTML field); TUI: the code editor is a browser component; code is shown as text and edited in $EDITOR |
| REC-032 | RENDER_HTML adornment renders sanitized HTML | pass | pass | RENDER_HTML shown as sanitized text; unit-tested (none in the sample); TUI: RENDER_HTML shown as text with the markup stripped (kernel htmlToText) |
| REC-033 | REVEAL adornment masks, reveals and copies the value | pass | pass | REVEAL masked in the record view, query and child lists until --reveal; table get --copy <field> copies the value (pbcopy/clip/wl-copy/xclip, else OSC 52); masking unit-tested, --copy verified live; TUI: masked; Enter reveals |
| REC-034 | FILE_DOWNLOAD adornment links to the stored file with its name | pass | pass | FILE_DOWNLOAD shows the file name and qqq table download saves it through the field route; unit-tested (none in the sample); BLOB values without the adornment are decoded locally (server finding 16); TUI: Enter saves through the save dialog |
| REC-032 | RENDER_HTML adornment renders sanitized HTML | pass | pass | RENDER_HTML shown as sanitized text; unit-tested (none in the sample); TUI: RENDER_HTML shown as text with the markup stripped (kernel htmlToText); adornmentLab RENDER_HTML as text in the grid and the record (live on fix branch 8bf6a5a66) |
| REC-033 | REVEAL adornment masks, reveals and copies the value | pass | pass | REVEAL masked in the record view, query and child lists until --reveal; table get --copy <field> copies the value (pbcopy/clip/wl-copy/xclip, else OSC 52); masking unit-tested, --copy verified live; TUI: masked; Enter reveals; adornmentLab REVEAL masked in the grid and the record (live on fix branch 8bf6a5a66) |
| REC-034 | FILE_DOWNLOAD adornment links to the stored file with its name | pass | pass | FILE_DOWNLOAD shows the file name and qqq table download saves it through the field route; unit-tested (none in the sample); BLOB values without the adornment are decoded locally (server finding 16); TUI: Enter saves through the save dialog; adornmentLab FILE_DOWNLOAD (live on fix branch 8bf6a5a66) |
| REC-035 | FILE_UPLOAD adornment formats, replacing and removing a stored file | pass | pass | create/update --upload field=path replaces the file, --set field= removes it (verified live on a BLOB); FILE_UPLOAD's format (button or drag and drop) is presentation only; TUI: FILE_UPLOAD fields choose a file; Backspace clears |
| REC-036 | TOOLTIP adornment shows its text on the value | pass | pass | tooltip text in parentheses after the value; unit-tested (none in the sample); TUI: tooltip text appended by valueText |
| REC-037 | WIDGET adornment renders the field value as the named widget | pass | pass | the value rendered through the named widget's render model; unit-tested (none in the sample); TUI: a WIDGET-adorned field's value drawn as its widget (unit-tested only) |
| REC-036 | TOOLTIP adornment shows its text on the value | pass | pass | tooltip text in parentheses after the value; unit-tested (none in the sample); TUI: tooltip text appended by valueText; adornmentLab TOOLTIP (live on fix branch 8bf6a5a66) |
| REC-037 | WIDGET adornment renders the field value as the named widget | pass | pass | the value rendered through the named widget's render model; unit-tested (none in the sample); TUI: a WIDGET-adorned field's value drawn as its widget; adornmentLab WIDGET field with a plain value shows the value (live on fix branch 8bf6a5a66) |
| REC-038 | ERROR adornment highlights the value | pass | pass | ERROR values marked with !; unit-tested (none in the sample); TUI: marked by valueText |
| REC-039 | Field help content follows screen roles and formats | pass | pass | table get --help-content: field help by view-screen role, HTML/MARKDOWN as text; qqq help show lists help alone; unit-tested (the sample has no help, finding 17); TUI: field help by screen role in F1 and in the form's field hint (unit-tested only) |
| REC-040 | Section help content on the record view | pass | pass | section help under the section with --help-content; unit-tested (finding 17); TUI: section help under the section heading and in F1 (unit-tested only) |
@@ -302,8 +302,8 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| SEC-008 | Process disabled for a user (DenyBehavior.DISABLED): shown disabled, link explains the denial, backend refuses | pass | pass | a DISABLED process (hasPermission false) is listed by process list and table actions with the reason; process run refuses (exit 3) before init; stub-server tested; TUI: a disabled process is listed "(no access)" and refused with the reason; table actions show it disabled (unit-tested only) |
| SEC-009 | Widget restrictions: hidden widgets absent, disabled widgets show a permission state without loading data, bac | pass | pass | widget get shows the refusal; TUI: hidden widgets are absent; a disabled widget shows the permission state without a request; children of a parent skip denied ones (unit-tested only) |
| SEC-010 | App restrictions: hidden apps absent from navigation and unknown by link; disabled apps keep their children ga | pass | pass | TUI: hidden apps are absent and unknown by name; a disabled app explains the denial (unit-tested only) |
| SEC-011 | Record-level security (sharing demo): users see only their own or shared saved views/reports; direct links and | pass | pass | server-enforced: saved views (and bulk load profiles) are listed as the server returns them, yours first, others as shared by their owner; changing someone else's is refused locally or by the server; stub-server tested; TUI: saved views and reports: the TUI lists what the server returns; sharing through the share dialog; a refused direct link shows the server message (unit-tested only) |
| SEC-012 | Record security lock on a data table: list filtered to the key, other users' records unreachable, forged owner | pass | pass | server; TUI: record security is server-side: grids show the server's rows; a record the server refuses shows its message (403) (unit-tested only) |
| SEC-011 | Record-level security (sharing demo): users see only their own or shared saved views/reports; direct links and | pass | pass | server-enforced: saved views (and bulk load profiles) are listed as the server returns them, yours first, others as shared by their owner; changing someone else's is refused locally or by the server; stub-server tested; TUI: saved views and reports: the TUI lists what the server returns; sharing through the share dialog; a refused direct link shows the server message; sharing demo: the owner sample:alice shares her saved report (live on fix branch 8bf6a5a66) |
| SEC-012 | Record security lock on a data table: list filtered to the key, other users' records unreachable, forged owner | pass | pass | server; TUI: record security is server-side: grids show the server's rows; a record the server refuses shows its message (403); adornmentLabLocker 2 answers Permission denied, shown on the record screen (live on fix branch 8bf6a5a66) |
| SEC-013 | Field protection: hidden fields never shown or returned, password fields masked and never overwritten by the m | pass | pass | server; TUI: hidden fields never shown (grid, record, form); PASSWORD values masked; edits send only changed fields, never the masked value (unit-tested only) |
| SEC-014 | Capability restrictions: tables without insert/update/delete capabilities offer none of them and refuse direct | pass | pass | TUI: without TABLE_INSERT/UPDATE/DELETE capabilities the F-keys, menu entries and copy are off and n/e/d are refused (unit-tested only) |
| SEC-015 | Permission revoked mid-session: stale UI actions are refused by the backend with a clear message, nothing is w | pass | pass | TUI: a save refused mid-session keeps the form with what was typed and shows the server message (unit-tested only) |
@@ -418,13 +418,13 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| RPT-006 | report input fields: required validation and the value filters the rows | pass | pass | report input fields asked on a terminal or given with -v; a missing required one exits 2 naming it before the report runs; the value is sent to the step; stub-server tested; TUI: report input fields are the report process's form: required checks, values sent with the run (unit-tested only) |
| RPT-007 | report permissions: restricted report denied in UI and by the server | pass | pass | server; TUI: a restricted report is marked (no access) and refused with the reason; the server refuses too (unit-tested only) |
| RPT-008 | reports appear in app navigation and open their run page | pass | pass | reports listed by report list and under their app in apps show; report run opens them; stub-server tested; TUI: reports appear in app navigation and run from there (unit-tested only) |
| RPT-009 | saved report view shows its filter/columns and pivot setup | pass | pass | qqq saved-report show: filter, sort, columns and pivot setup; stub-server tested; TUI: saved report record: filter, sort, columns and pivot setup drawn (unit-tested only) |
| RPT-009 | saved report view shows its filter/columns and pivot setup | pass | pass | qqq saved-report show: filter, sort, columns and pivot setup; stub-server tested; TUI: saved report record: filter, sort, columns and pivot setup drawn; saved report Pet Species Report: filter, sort, columns, pivot (live on fix branch 8bf6a5a66) |
| RPT-010 | saved report renders through renderSavedReport and downloads the saved columns and rows | pass | pass | qqq saved-report run <r> [--format]: renderSavedReport with the record, the file downloaded; stub-server tested; TUI: the record actions list the table processes (renderSavedReport) and run them on the record; the DOWNLOAD_FORM file is saved (process downloads unit-tested only) |
| RPT-011 | streaming report route for reports without a process | pass | pass | report run on a report without a process (or --route) streams from GET /reports/{name}; stub-server tested; TUI: a report without a process streams from GET /reports/{name}?format= into the save dialog (unit-tested only) |
| RPT-012 | scheduled report: create a schedule for a saved report and view its cron schedule | pass | pass | qqq saved-report schedule <r> --cron --time-zone --to [--format]; saved-report schedules lists them with the server's cron description; stub-server tested; TUI: scheduled report: created on its form; the record shows the cron schedule (cronUI) (unit-tested only) |
| RPT-013 | sharing: the owner shares a saved report with a user (Read-Only) | pass | pass | qqq share add savedReport <id> user:<id> --scope read-only (insertSharedRecord); stub-server tested; TUI: share a saved report with a user Read-Only (Z, :share) (unit-tested only) |
| RPT-014 | sharing: change a share's scope to Read and Edit | pass | pass | qqq share scope savedReport <id> <share> read-write (editSharedRecord); stub-server tested; TUI: change a share to Read and Edit (unit-tested only) |
| RPT-015 | sharing: remove a share | pass | pass | qqq share remove savedReport <id> <share> (asks; --yes); stub-server tested; TUI: remove a share after a confirm (unit-tested only) |
| RPT-013 | sharing: the owner shares a saved report with a user (Read-Only) | pass | pass | qqq share add savedReport <id> user:<id> --scope read-only (insertSharedRecord); stub-server tested; TUI: share a saved report with a user Read-Only (Z, :share); shared Pet Species Report Read-Only (live on fix branch 8bf6a5a66) |
| RPT-014 | sharing: change a share's scope to Read and Edit | pass | pass | qqq share scope savedReport <id> <share> read-write (editSharedRecord); stub-server tested; TUI: change a share to Read and Edit; changed to Read and Edit (live on fix branch 8bf6a5a66) |
| RPT-015 | sharing: remove a share | pass | pass | qqq share remove savedReport <id> <share> (asks; --yes); stub-server tested; TUI: remove a share after a confirm; removed the share (live on fix branch 8bf6a5a66) |
| RPT-016 | sharing record security: another user cannot see an unshared report until it is shared | pass | pass | server-enforced record security: saved-report list shows only what the server returns (yours, shared with you); stub-server tested; TUI: unshared reports are the server's to hide; the TUI lists what it returns and shows a refused link's message (unit-tested only) |
| RPT-017 | sharing: only the owner may share | pass | pass | only the owner may share: the server's refusal shown (exit 2); stub-server tested; TUI: only the owner may share: others see Share disabled with the reason (unit-tested only) |
| RPT-018 | sharing a saved view through the share dialog | pass | pass | qqq share … savedView <id>: the same sharing processes for saved views; stub-server tested with saved reports; TUI: saved views share through the same dialog (Saved views menu) (unit-tested only) |
@@ -439,7 +439,7 @@ The TUI column is `qqq ui` (M3); its notes start with "TUI:".
| WID-074 | Standalone composite scalar controls and metadata editors | n/a | pass | standalone composite controls are browser input editors (caret, IME, widths) whose drafts stay on the page; the CLI shows the controls and their values (WID-057), and process-hosted inputs go through the form session (PRC-037); TUI: standalone composite INPUT_FIELD blocks show their seeded values read-only by type (no standalone input target in a terminal) (unit-tested only) |
| WID-075 | Standalone DATE_TIME blocks localize seeded instants without losing seconds | pass | pass | a seeded DATE_TIME input block is shown in the local zone with its seconds (kernel displayDateTime); unit-tested; TUI: standalone DATE_TIME blocks show seeded instants in the local zone with seconds (unit-tested only) |
| HLP-001 | App and widget help | pass | pass | qqq help show app/widget/instance; unit-tested (the sample carries no help content, finding 17); TUI: F1 on an app home: app help and each widget's help (slot maps) (unit-tested only) |
| HLP-002 | Table, field and section help | pass | pass | qqq help show table <t> [field]: table, section and field help; unit-tested (finding 17); TUI: table, field and section help by screen role in F1 (unit-tested only; the sample has none) |
| HLP-002 | Table, field and section help | pass | pass | qqq help show table <t> [field]: table, section and field help; unit-tested (finding 17); TUI: table, field and section help by screen role in F1 (unit-tested only; the sample has none); F1 on adornmentLab: table and section help (live on fix branch 8bf6a5a66) |
| HLP-003 | Query columns and table action help | pass | pass | field help is the column help; table action help slots printed with their slot name; unit-tested (finding 17); TUI: the grid footer shows the help of the column under the cursor; table help slots in F1 (unit-tested only) |
| HLP-004 | Process, step and record action help | pass | pass | qqq help show process <p>: process, step, HELP_TEXT and step field help; unit-tested (finding 17); TUI: F1 on a process: process, step and step field help; on a record: table, section and field help for the view role (unit-tested only) |
| HLP-005 | Help search | pass | pass | qqq help-search; TUI: type in F1 |
+2
View File
@@ -228,6 +228,8 @@ function valueTextWithoutTooltip(field: FieldMeta, record: RecordLike, options:
const widgetName = typeof widget.values?.widgetName === 'string' ? widget.values.widgetName : ''
if (options.widgets && !options.widgets[widgetName]) return { text: `Error: Could not load widget [${widgetName}]`, adornment: 'WIDGET', error: true }
if (raw === null || raw === undefined) return { text: empty, adornment: 'WIDGET' }
// a plain value (not a widget payload) is shown as it is
if (typeof raw !== 'object') return { text: str(display ?? raw), adornment: 'WIDGET' }
const text = blocksToText(widgetToBlocks(raw), { width: 80 })
return { text: text || str(raw), adornment: 'WIDGET' }
}
+6 -1
View File
@@ -64,6 +64,11 @@ function textOf(value: unknown): string {
* fields. Keys are read from the object's state (no React closures), so a burst of keys lands in
* the field that had focus when each key was read. Used by table forms and process steps.
*/
/** A primary key the server makes (a number, or not editable): not asked on create; a text key is typed in. */
function generatedKey(f: { type?: string; isEditable?: boolean | null } | undefined): boolean {
return !f || f.isEditable === false || f.type === 'INTEGER' || f.type === 'LONG'
}
export class FormEditor {
index = 0
top = 0
@@ -395,7 +400,7 @@ export class FormScreen implements Screen {
this.originals = { ...initial }
for (const f of Object.values(meta.fields)) if (f.type === 'DATE_TIME' && isInstant(initial[f.name])) initial[f.name] = utcToLocalInput(String(initial[f.name]), defaultTimeZone())
const fields = Object.values(meta.fields) as FieldMeta[]
const session = new FormSession({ baseUrl: client.server, fetch: client.fetch, scope: { table: this.tableName }, fields, initialValues: initial, tableMeta: meta as never, exclude: this.options.mode === 'edit' ? [] : [meta.primaryKeyField] })
const session = new FormSession({ baseUrl: client.server, fetch: client.fetch, scope: { table: this.tableName }, fields, initialValues: initial, tableMeta: meta as never, exclude: this.options.mode === 'edit' || !generatedKey(meta.fields[meta.primaryKeyField]) ? [] : [meta.primaryKeyField] })
for (const [k, v] of Object.entries(display)) if (session.hasChoices(k) && !isMissingValue(initial[k])) session.displayValues[k] = v
// labels for prefilled possible values (create child: the parent's id)
for (const [k, v] of Object.entries(this.options.initial ?? {})) {
+4 -1
View File
@@ -315,7 +315,10 @@ export class RecordScreen implements Screen {
const widgetFields = fields.filter((f) => f.adornments?.some((a) => a.type === 'WIDGET') && rec.values?.[f.name] && typeof rec.values[f.name] === 'object')
for (const f of widgetFields) {
rows.push({ text: ` ${f.label}`, fg: t.fg, bold: true })
rows.push(...blockLines(widgetToBlocks(rec.values![f.name]), inner - 6).map((l) => ({ ...l, text: ` ${l.text}` })))
const wv = rec.values![f.name]
// a WIDGET-adorned field whose value is plain text (not a widget payload) shows the text
if (wv !== null && typeof wv !== 'object') rows.push({ text: ` ${String(wv)}` })
else rows.push(...blockLines(widgetToBlocks(wv), inner - 6).map((l) => ({ ...l, text: ` ${l.text}` })))
}
const plainFields = fields.filter((f) => !widgetFields.includes(f))
for (let i = 0; i < plainFields.length; i += two ? 2 : 1) {
+3 -1
View File
@@ -29,6 +29,8 @@ export interface GridColumn {
type: string
width: number
numeric: boolean
/** The field's adornments: cells are drawn as the record view draws them (REVEAL masked, HTML as text…). */
adornments?: FieldMetaData['adornments']
}
/** The default column order: primary key, then section fields by tier, then the rest (hidden and heavy left out). */
@@ -55,7 +57,7 @@ export function layoutColumns(fields: FieldMetaData[], rows: QueryRecord[], avai
const numeric = ['INTEGER', 'LONG', 'DECIMAL'].includes(f.type) && !f.possibleValueSourceName
const longest = Math.max(f.label.length + 1, ...rows.map((r) => fieldCell(r, f).length), 3)
const saved = view.widths?.[f.name]
return { name: f.name, label: f.label, type: f.type, numeric, longest, width: saved ?? Math.min(40, longest) }
return { name: f.name, label: f.label, type: f.type, adornments: f.adornments, numeric, longest, width: saved ?? Math.min(40, longest) }
})
const total = () => cols.reduce((a, c) => a + c.width + gap, 0)
let spare = available - total()
+7 -2
View File
@@ -3,7 +3,7 @@
* Licensed under the Apache License, Version 2.0 (see LICENSE).
*/
import { displayDateTime } from '@qrun-io/qqq-core'
import { displayDateTime, valueText } from '@qrun-io/qqq-core'
import { glyphs } from '../theme'
/** Cuts text to a width, with an ellipsis when it was longer. */
@@ -47,7 +47,12 @@ export function cell(record: { values?: Record<string, unknown>; displayValues?:
}
/** A value as text for a field: DATE_TIME in the viewer's zone (kernel displayDateTime), else the display value. */
export function fieldCell(record: { values?: Record<string, unknown>; displayValues?: Record<string, string> } | undefined, field: { name: string; type: string }): string {
export function fieldCell(record: { values?: Record<string, unknown>; displayValues?: Record<string, string> } | undefined, field: { name: string; type: string; adornments?: unknown[] | null }): string {
// adorned fields as the record view shows them, in one cell: REVEAL masked, RENDER_HTML as text, CHIP, LINK…
if (record && field.adornments?.length) {
const text = valueText(field as never, { values: record.values ?? {}, displayValues: record.displayValues } as never, { cell: true }).text
return text.replace(/\s*\n\s*/g, ' ')
}
if (field.type === 'PASSWORD') {
const raw = record?.values?.[field.name]
return raw === null || raw === undefined || raw === '' ? '' : '••••••'
+33
View File
@@ -393,6 +393,39 @@ TUI.update({
TUI.update({
'SEC-030': (B, 'needs a real Auth0 tenant: [qqq-cli#10](https://github.com/QRun-IO/qqq-cli/issues/10); the sample has no AUTH_0 configuration ([qqq#1359](https://github.com/QRun-IO/qqq/issues/1359), finding 28); the Auth0 protocol path is SEC-029'),
})
# Verified live on a private server from the qqq fix branch 8bf6a5a66 (UI API 1.9.0, sample with
# -Dqqq.sample.sharing=true) on port 8091; :8077 runs the released sample.
FIX = 'live on fix branch 8bf6a5a66'
LIVE = {
'QRY-085': (P, f'weekday(birthDate) IN 1,7 returns its 2 people ({FIX}; on the released sample it fails, finding 24, qqq#1355)'),
'QRY-010': (None, f'text matches ignore case (CASE_INSENSITIVE), Match exact case on request ({FIX})'),
'QRY-019': (None, f'q avery finds Avery (CASE_INSENSITIVE); =Text matches exact case ({FIX})'),
'NAV-030': (None, f'{FIX}: recordSearch is no longer advertised and the palette says so; POST /search still has no provider (finding 21)'),
'PRC-020': (None, 'still blocked on the fix branch (finding 13)'),
'REC-022': (None, f'upload into uploadedFileArchive (text primary key typed on create) and download of the same 22 bytes ({FIX})'),
'REC-028': (None, f'adornmentLab carrier LINK ({FIX})'),
'REC-029': (None, f'adornmentLab CHIP [ACTIVE] in the grid and the record ({FIX})'),
'REC-030': (None, f'adornmentLab SIZE ({FIX})'),
'REC-032': (None, f'adornmentLab RENDER_HTML as text in the grid and the record ({FIX})'),
'REC-033': (None, f'adornmentLab REVEAL masked in the grid and the record ({FIX})'),
'REC-034': (None, f'adornmentLab FILE_DOWNLOAD ({FIX})'),
'REC-036': (None, f'adornmentLab TOOLTIP ({FIX})'),
'REC-037': (None, f'adornmentLab WIDGET field with a plain value shows the value ({FIX})'),
'REL-007': (None, f'adornmentLab tags through the composite association ({FIX})'),
'REL-008': (None, f'a new adornmentLab tag gets adornmentLabId and labKey ({FIX})'),
'SEC-012': (None, f'adornmentLabLocker 2 answers Permission denied, shown on the record screen ({FIX})'),
'SEC-011': (None, f'sharing demo: the owner sample:alice shares her saved report ({FIX})'),
'RPT-009': (None, f'saved report Pet Species Report: filter, sort, columns, pivot ({FIX})'),
'RPT-013': (None, f'shared Pet Species Report Read-Only ({FIX})'),
'RPT-014': (None, f'changed to Read and Edit ({FIX})'),
'RPT-015': (None, f'removed the share ({FIX})'),
'HLP-002': (None, f'F1 on adornmentLab: table and section help ({FIX})'),
}
for _rid, (_st, _extra) in LIVE.items():
_cur = TUI.get(_rid, (P, ''))
_note = _cur[1].replace(' (unit-tested only)', '').replace('(unit-tested only)', '').strip()
TUI[_rid] = (_st or _cur[0], _extra if _st else (f'{_note}; {_extra}' if _note else _extra))
rows = []
for line in open(SRC, encoding='utf-8', errors='replace'):
m = re.match(r'^\|\s*([A-Z]{3}-\d{3})\s*\|\s*(.*?)\s*\|', line)