Repository navigation
console: suggestions from a full HotCRM browser pass on 18.2.0: clickable path steps, chart labels and empty charts, record-page Delete, list inline edit without edit rights, public web forms, and phone layouts #27355
Description
Suggestions from HotCRM's full browser pass on platform 18.2.0 (hotcrm#2320): six testers, 115 UI suggestions. The HotCRM maintainer ruled at 2026-10-29T02:08Z that the platform parts go to objectstack as suggestion cards. This card holds the console parts. Setup and Studio are a separate card.
Each row cites its track report on hotcrm#2320 and the finding id there (screenshots are attached to each report). An id such as S14 is the 14th suggestion in that track's report.
Already carded: not asked again
The pass ran on 18.2.0 (published 2026-10-27 06:20Z). These findings match objectstack cards that closed after that, so HotCRM will take them with the next release:
| Finding | Card |
|---|---|
The path draws Unqualified / Rejected / Unsubscribed as later steps (Sales S1, Revenue S3, Marketing S7) |
#26612, #26694 |
| The path ticks stages the record skipped (Service S1, Revenue S3) | #26581 |
| On a phone the opportunity path overflows the right edge (Sales S32) | #26897 |
| A unique refusal does not name the field (Sales S11) | #26891 |
| Time-series charts skip empty days and months (Service S14, Revenue S18) | #26893 |
| The phone list and record page (Sales S30, Revenue S22) | #27058 (open) |
New suggestions
| # | Observed on 18.2.0 | Report | Suggestion |
|---|---|---|---|
| 1 | Path steps cannot be clicked. | Sales S1 | Let a step request that status change, subject to the field's validation and state machine. |
| 2 | Chart data labels are not formatted: 174000 sits beside a 400K axis. |
Marketing S14, Revenue S18 | Format data labels with the measure's field format, as the axis is. |
| 3 | A dashboard table cuts off its last column, and the tallest bar of a bar chart loses its value label. | Marketing S14, Revenue S18 | Fit or scroll the table columns; keep the label of the tallest bar inside the plot. |
| 4 | A chart whose values are all zero draws an empty axis. | Service S13 | Show the widget's empty state. |
| 5 | The record page has no Delete. It is only in the list row menu. | Sales S14, Revenue S13 | Offer Delete in the record header's overflow menu when the user may delete. |
| 6 | A service agent is offered inline edit on an opportunity list where the agent cannot edit any row. | Setup S13 | Offer list inline edit only when the user may edit the object (the record page now asks via #27138 / #27328; the list grid does not). |
| 7 | A manager opening a deal they may not read sees "the record does not exist or has been deleted". | Sales S25 | Consider "you do not have access to this record" where revealing existence is acceptable, or say both are possible. |
| 8 | A calendar view's footer count counts the records in the loaded window, not the records the view matches. | Marketing S6 | Label it as the window's count, or count the view. |
| 9 | Public web forms (/forms/:name) carry no branding. The submit button is blue while the app's color is orange, and the description placeholder is monospace. |
Sales S6, Service S3 | Let a public form take the app's brand color and logo; use the body font for placeholders. |
| 10 | A refused public-form submit shows a toast and a banner but does not mark the fields. | Sales S6 | Mark each refused field, as the record dialog does. |
| 11 | On a phone, Sales Home's four KPI tiles take a row each and fill the first screen; the page tab strip overflows. | Sales S29 | Two KPI tiles per row at phone width; scroll the tab strip with a cue. |
| 12 | On a phone, the pipeline kanban opens on an empty first stage. | Sales S31 | Open on the first non-empty column, or show column counts in a header strip. |
| 13 | On a phone, a gantt bar has no label, and a long campaign title is cut with no ellipsis. | Marketing S19 | Label bars; ellipsize titles. |
Acceptance
Each row is either taken, with its own card if it needs one, or answered "will not do", with the reason. HotCRM needs no metadata change for rows 1–13.
Filed by the repo:hotcrm lane seat (session session_01J8VVAnPkgK6CWrJa6t7Fk5) from the #2320 UX summary, on the maintainer's ruling. Dedupe: read the titles of every open domain:ui card and every domain:ui card closed since 2026-10-26, and the open issue list. The matches are in the first table. The HotCRM-side parts of the same themes are HotCRM cards.
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p3 kept ·
domain:ui·area:records· 2026-10-29T02:57Z⛔ Not a claim.
pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
Seat note ·
domain:ui#2·session_01JQwxbXpF772qWJjojJvRXw· 2026-10-29T03:18Z⛔ Not a claim on this card.
Row 6 (list inline edit without edit rights) is carded on its own as #27365, per triage's per-row rule (6486648770), and seat 2 dispatched it.
Measured: no data is written. The server refuses every cell save. The defect is that the grid's cells read only the object-level grant, while the row kebab already reads the per-row explain verdict (
useRecordCrudVerdicts). This is not a security boundary, so the card is p2, not a security card.The other rows are still open for any seat whose area they fall in.
objectstack-fleet commentedon Oct 29, 2026
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
Seat note ·
domain:ui#5· 2026-10-29T04:02Z⛔ Not a claim on this card.
Following triage's per-row rule, rows 2–4 (chart data-label format, the tallest bar's clipped label and a dashboard table's last column, and the all-zero chart) are carded together as #27372, and seat 5 dispatched it. Row 4 leans toward "will not do", because zero is a value and the all-zero bar was measured and deliberately left alone. #27372 will give the final answer per row.
Rows still open for any seat whose area they fall in: 1, 5, 7, 8, 9, 10, 11, 12 and 13. Row 6 is #27365 (seat 2).
Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
Seat answer ·
domain:ui#5· 2026-10-29T05:51Z⛔ Not a claim on this card.
#27372 answered rows 2–4, and PR #27407 merged (45c9be5). Each row was measured in real Chromium on a fixture dashboard rendered by
DashboardRenderer(dev report 6488743428):174Kbeside a400Ktick, and an authoredyAxis[].formatstill wins.scrollWidth910 vs 566, and 971 vs 564), and the last column scrolls into view at 1280, 768 and 390px. What looks like a clip is a hidden or overlay scrollbar: headless Chromium and macOS hide scrollbars, anddata-tableshows one only on hover. If HotCRM can point to a widget where the column cannot be scrolled to, reopen with that widget's schema.AdvancedChartImpl.tsxstands.HotCRM picks rows 2 and 3 (tallest bar) up with the next release. Rows 1, 5, 7, 8, 9, 10, 11, 12 and 13 are still open. Row 6 is #27365.
Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
Seat note ·
domain:ui#5· 2026-10-29T06:00Z⛔ Not a claim on this card.
Following triage's per-row rule, rows 9–10 (the public web form's branding and refused-field marks) are now carded as #27417, and rows 12–13 (the phone kanban's empty first stage and gantt bar labels) as #27418. Seat 5 dispatched both.
Rows still open for any seat whose area they fall in: 1, 5, 7, 8 and 11.
Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
Seat answer ·
domain:ui#5· 2026-10-29T07:32Z⛔ Not a claim on this card.
Rows 9–10 and 12–13 are answered. #27417 landed as PR #27443 (ddbd08e) and #27418 as PR #27435 (7af2145). Each row was measured in real Chromium.
RichTextField) on every host. Its placeholder now uses the sans stack, and typed source stays mono. A form opened inside an app (/apps/:app/forms/:name) already takes the app's brand colour. Measured:rgb(124,59,237)on the showcase. Open: the app-less/forms/:nameand/f/:slughave no app, so there is no brand to read. Giving them one needs a spec/server door, for example the form naming its app. HotCRM can ask for that as its own card if it needs it (dev report 6490216631, open question).aria-invalidand its reason under the control. This covers client required fields, serverVALIDATION_FAILEDfields[]andUNIQUE_VIOLATION, read by the record dialog's ownextractFieldErrors. A tabbed or wizard form opens the first refused field's tab or step.scrollLeftwent from 16 (empty Prospecting) to 756 (Proposal). It never moves after the viewer touches, scrolls or types.Rows still open for any seat whose area they fall in: 1, 5, 7, 8 and 11.
Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
Claim: PM loop round 1
Session:
session_01Utp6CaJoinvGQSbHeDaWBeAccount:
os-billBranch:
claude/project-thread-xgi39j(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27355Domain:
domain:uiSeat:
domain:ui#3File surface:
ui/packages/plugin-calendar/**(footer count), the Home KPI / page tab strip, the record not-found copy, i18n packs for a changed sentence, tests beside, root changeset (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT(dispatch-gates --tieron178b5238: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility: ui code produces the defect | no platform path covers it | every Studio / console author reaches it today
Thread-read: 6490776608
Premise: holds
Serial constraints cleared: none (rows 2–4, 6, 9–10, 12–13 landed by other seats)
Scope of this claim: triage of the open rows 1, 5, 7, 8 and 11 by the seat's order (rows 2–4, 6, 9–10 and 12–13 are answered by other seats). Ui-only rows are taken in one PR; rows that need a new key, spec or server change are filed as their own cards by the seat. Row 5 is held if the record header lives in seat 2's record page v4 PR files. Seat 4's in-flight PR files (#27893, #27907, #27912, #27793), seat 2's record page v4 PRs and #27058 are off limits.
Dispatched 2026-10-30T08:52Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.
Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:ui#3 · os-bill · session_01Utp6CXNi3EtSwLHRSornGu
os-dev-report
{ "issue": 27355, "status": "done", "branch": "claude/project-thread-xgi39j", "pr": "https://github.com/objectstack-ai/objectstack/pull/28060", "session": "session_01Utp6CXNi3EtSwLHRSornGu (card thread; seat session_01Utp6CaJoinvGQSbHeDaWBe)", "premise_still_valid": true, "summary": "Rows 1, 5, 7, 8, 11 re-measured on origin/main 3f02da10 (merged with main 5aee2065 before the PR; head 8686ef11). Row 11 TAKE for the tiles: a layout grid with a bare columns count of 4 or more draws grid-cols-2 below sm and the authored count from md (was 1 column below sm), the dashboard's own phone layout for metric tiles; bare 1-3 and authored breakpoint objects unchanged. Row 11 tab strip ALREADY DONE by #27849 (page:tabs scroll + edge fade; absent at the 18.2.0 release commit 27f38ada, present at d07e8d1b). Row 8 TAKE: a windowed calendar footer reads 'N records loaded for these dates' (new count family list.calendarRecordCount, all CLDR slots, ten packs + LIST_DEFAULT_TRANSLATIONS); grid, board and inline-data calendar keep 'N records'. Row 7 TAKE: getData reads with the caller context and throws recordNotFoundError on no row, so an unreadable record answers 404 RECORD_NOT_FOUND like a missing one; empty.recordNotFoundDescription and detail.recordNotFoundDescription now name 'or you may not have access to it' in ten packs, DETAIL_DEFAULT_TRANSLATIONS and the RecordDetailView inline fallback follow. Row 1 CARD (below). Row 5 ALREADY DONE on the generic path: the record header overflow ends with sys_delete (present at 18.2.0 too), gated by the same delete affordance row and per-record verdict as the list row menu. PR body says Part of #27355; the seat flips it at ACCEPT once row 1 is filed.", "tests": "head 8686ef11: targeted set (check-i18n-call-site-keys, DetailView, five grid pins, schema-catalog grid-columns-key, calendar footer + window files, RecordDetailView.recordReadFailure-11902, RecordDetailView.deleteConfirmDialog-11001, defaults-maps-mirror-en-pack, packages/i18n/src/__tests__/): Test Files 104 passed (104), Tests 1675 passed | 13 skipped (1688), VERDICT command-exit 0. Affected packages whole at 40a769cc (packages/components/, packages/i18n/, plugin-list and plugin-detail __tests__, examples/schema-catalog/, app-shell files): Test Files 977 passed | 2 skipped, 1 red (DetailView asserting the old sentence), fixed in 40a769cc. Ablation 1 via scripts/ablation-replace.mjs (anchor 'if (baseCols >= 4) {' -> 'if (false) {', grid.tsx resolved from source): grid-phone-tiles-27355 Tests 5 failed | 3 passed (8), restored blob == HEAD 41b7636b1aa5, git diff HEAD empty. Ablation 2 (anchor list.calendarRecordCount call -> list.recordCount call, ListView from source): ListView.calendarFooterCount-27355 Tests 2 failed | 2 passed (4), restored blob == HEAD 8866406e8d92, git diff HEAD empty. type-check of components, plugin-list, plugin-detail, i18n :: exit 0. pnpm run lint in the same four :: exit 0, 0 errors. ui check-i18n-en-drift :: exit 0 (2 en values changed, 18 pack values followed).", "gates": "dispatch-gates --commands at 8686ef11: 34 commands, each :: exit 0, incl. node scripts/check-sdui-manifest.mjs :: exit 0 and --self-test :: exit 0, pnpm check:type-check-coverage :: exit 0, pnpm check:type-check-debt :: exit 0, pnpm check:nul-bytes :: exit 0, pnpm --dir ui exec vitest run scripts/ :: exit 0 (Test Files 169 passed | 2 skipped (171)). dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Outside derivation: pnpm --dir ui run check:new-line-citations :: exit 0 (0 new). Ran list: project file ui-seat3/27355-gates-ran.txt. CI check-runs: not read at report time; the seat reads them.", "line_budget": "382 changed lines (+343 / -39), 23 files; under the 3000 human-merge threshold", "files_changed": [ ".changeset/27355-console-walkthrough-rows.md", "ui/examples/schema-catalog/test/grid-columns-key.test.tsx", "ui/packages/app-shell/src/views/RecordDetailView.recordReadFailure-11902.test.tsx", "ui/packages/app-shell/src/views/RecordDetailView.tsx", "ui/packages/components/src/__tests__/grid-breakpoint-columns-7097.test.tsx", "ui/packages/components/src/__tests__/grid-dom-attribute-whitelist.test.tsx", "ui/packages/components/src/__tests__/grid-flat-columns-retired-11505.test.tsx", "ui/packages/components/src/__tests__/grid-phone-tiles-27355.test.tsx", "ui/packages/components/src/renderers/layout/grid.tsx", "ui/packages/i18n/src/locales/ar.ts", "ui/packages/i18n/src/locales/de.ts", "ui/packages/i18n/src/locales/en.ts", "ui/packages/i18n/src/locales/es.ts", "ui/packages/i18n/src/locales/fr.ts", "ui/packages/i18n/src/locales/ja.ts", "ui/packages/i18n/src/locales/ko.ts", "ui/packages/i18n/src/locales/pt.ts", "ui/packages/i18n/src/locales/ru.ts", "ui/packages/i18n/src/locales/zh.ts", "ui/packages/plugin-detail/src/__tests__/DetailView.test.tsx", "ui/packages/plugin-detail/src/useDetailTranslation.ts", "ui/packages/plugin-list/src/ListView.tsx", "ui/packages/plugin-list/src/__tests__/ListView.calendarFooterCount-27355.test.tsx" ], "deviations": "File surface widened beyond the claim's naming, each announced to the seat thread before the edit and accepted there: the calendar footer lives in ui/packages/plugin-list/src/ListView.tsx (record-count-bar), not ui/packages/plugin-calendar/**; the KPI tiles land in ui/packages/components/src/renderers/layout/grid.tsx; row 7 touches ui/packages/plugin-detail/src/useDetailTranslation.ts and one defaultValue line of ui/packages/app-shell/src/views/RecordDetailView.tsx, which check-i18n-call-site-keys (default-value drift) requires; #27793 that held that file had merged, and the only open PR touching it (#27713) has no hunk near that line. Existing test pins changed to the new ramp or sentence: grid-breakpoint-columns-7097, grid-flat-columns-retired-11505, grid-dom-attribute-whitelist, schema-catalog grid-columns-key, RecordDetailView.recordReadFailure-11902, DetailView. PR attribution block uses this card thread's link from the harness session context, not the seat-thread link in the dispatch order (the harness block says to keep its link exactly). Harness post-PR assign/review step skipped per the dispatch order. The harness commit-attribution reminder names a model in the Co-Authored-By trailer; commits use the model-free pair AGENTS.md requires.", "dogfood_battery": "n/a - no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 - relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no assignees, no labels) via repository_dispatch run 41046272573, body read back 10706/10706 bytes identical; this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/27355/comments). Plus git pushes of the branch (not REST).", "open_questions": [], "out_of_scope_findings": [ "CARD row 1 · class: a · reach: HotCRM measured on 18.2.0 (card body, Sales S1); current-main code read: record-path.tsx header comment records the path as a readout with no handler and says click-to-advance is a separate feature; its only channel (useRecordContext) exposes no record-field write · lane: domain:spec first (needs:contract-review, an opt-in key on RecordPathProps), then domain:ui · landing: packages/spec/src/ui/component.zod.ts (RecordPathProps) + ui/packages/plugin-detail/src/renderers/record-path.tsx (write channel, confirm, refusal display) · TITLE: Record path: a stage can be clicked to request that status, judged by the field's validation and state machine · BODY: The record:path block draws the status field's stages as a progress rail and nothing on it can be pressed, so a user who wants to move a deal from one stage to the next opens the edit form and changes the picklist. The server already judges every update against the object's state_machine validation rule (ADR-0020) and field-level edit rights, so no server change is needed: a stage press would send an ordinary update of the status field with the record's concurrency token and show the server's refusal (an illegal transition, a missing required field) in place. What is missing is a contract door and a write path in the renderer: the spec row declares statusField, stages and aria only, so a page cannot opt a path into being pressable, and the renderer's record context carries no field write. Proposed: an opt-in key on RecordPathProps (name for review), off by default so existing readouts keep their non-interactive look; when on, each reachable stage is a button gated by the reader's edit verdict on the status field, asks for confirmation, and writes through the same update path record:details uses. · dedupe words: record path click stage; click-to-advance; path stage status change; record:path interactive; state machine path", "observation, not filed · Row 5: HotCRM's report says the record page has no Delete, but on the generic path the header overflow carries sys_delete under the same gates as the list row menu, at 18.2.0 and on main; the cause on HotCRM's page was not measurable here (HotCRM's metadata is outside this session's repositories). Reopen with the object, role and page if it reproduces · carrier: seat" ] }objectstack-fleet commentedon Oct 30, 2026
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
Rows 1, 5, 7, 8, 11: ACCEPT → PR #28060 comment 6521550505 · 2026-10-30T11:28Z. Rows 7, 8 and 11 are taken there; row 5 is already on the generic path (header ⋯
sys_delete); row 1 is carded as #28061 (domain:spec). With rows 2–4, 6, 9–10 and 12–13 answered above, the PR saysFixes #27355.Generated by Claude Code