Repository navigation
The three task actions from #4 do not render anywhere in the UI — one-click completion has no button #60
Description
Found by booting the app and driving it in a real browser. First browser verification of this project; this is what it produced.
duly_task_complete, duly_task_undo and duly_task_skip (merged in #41) are unreachable from the UI. #4's 35 tests pass — they assert metadata shape, handler registration under the dispatch key, and engine-level dispatch. None of that says a human can click the thing.
Measured, on main at 7c30a39, Chromium 1194
| Declared location | Surface | What actually renders |
|---|---|---|
list_toolbar (bulk, declarative bulkActionDefs) |
list, rows selected | ✅ Complete · Skip — works end to end |
list_item |
grid row ⋯ menu | ❌ Edit, Delete only |
record_header |
detail header | ❌ Edit + ⋯ only |
record_more |
detail ⋯ overflow | ❌ Share, Delete only |
All three locations are valid members of ACTION_LOCATIONS in @objectstack/spec 17.2.0, and the predicates match the records under test (an open task; visible is status == "open" || status == "in_progress").
Zero console errors, zero page errors, zero 4xx across every screen.
One earlier probe of mine was a false positive and is retracted: a substring search found "Skip" on the detail page, but that was "Skipped" in the built-in status pipeline, not the action. Opening the overflow menu properly is what settled it.
What does work
- The declarative bulk path, verified end to end in the browser: select two rows → Complete → confirm → the row leaves My week (6 rows → 5) → the server shows
status: donewithcompleted_atstamped by the Task lifecycle hook — completed_at and last_update_at stamping #3 hook. No errors. - The built-in status pipeline on the record detail page (Open / In progress / Done / Skipped / Cancelled) — a platform affordance that comes free with a
trackHistoryselect, not one of our actions.
So the product is not unusable. But the interaction the product is designed around — a one-click tick on the row, with undo — is not there.
The uncomfortable part
#4's dev had to hand-write these three handlers because the platform has no declarative row-level field write (objectstack#14092). The half they were able to build as pure metadata renders and works; the half they had to write by hand does not render at all.
That is the metadata-first rule earning its keep in a way none of us predicted: it did not merely produce less code, it produced the half that survives contact with the UI.
Scope
- Root-cause why an object-bound
type: 'script'action withlocations: ['list_item']is not picked up by the grid renderer. objectui honourslist_iteminRelatedList.tsx(child rows on a detail page) andrecord_header/record_moreincontainers.tsx/DetailView.tsx, so the code paths exist — something about these declarations is not reaching them. Establish that before changing anything. - If the cause is ours, fix it. If it is a platform gap, file it upstream and say so on this card — do not work around it with a second implementation.
- Add a check that fails when a declared action is not reachable. Every existing assertion is satisfied by an action nobody can click, which is exactly how this shipped green.
Note for #16 (mobile)
Swipe-to-complete was going to reuse these actions. Whatever the answer here is, it decides that too.
Activity
os-warren commentedon Sep 1, 2026
Root-caused, escalated upstream, and this card is now
pm:on-hold— no workaround. PM seat, round 5.Maintainer instruction this round: "如果平台有问题,给平台报 issue 就可以." So this is filed and parked rather than engineered around.
The gap is broader than this card said
#59 merged, and it gave the decisive test.
duly_catalog_apply_to_peopleis object-bound, declareslocations: ['list_toolbar'], is registered under the dispatch key, and carries 14 passing placement assertions. Booted on mergedmainatde28918, the catalog list toolbar renders:No Apply. So it is not the three task actions and not
list_item:list_toolbar— object-boundtype: 'script'actionlist_itemrecord_headerrecord_morebulkActionDefson the view — not anactionSix declared actions, four locations, zero buttons. The one thing that renders is the declarative bulk write attached to the view.
That matches what #27's dev measured independently:
meta.loadMany('action')returns an empty array while the REST route resolves the same actions throughql.registry.getItem('action', …)and dispatches them fine. Two stores — the runtime registry has them, the store the Console reads does not.Filed as objectstack-ai/objectstack#14145.
Restart-when: closed objectstack-ai/objectstack#14145What the product runs on meanwhile
Both paths work today and neither is a workaround — they are what the platform actually supports:
donewithcompleted_athook-stamped.trackHistoryselect.So a user can complete a task. What is missing is the one-click row tick the product was designed around, and the undo that pairs with it.
Do not build a second implementation — no
type: 'api'PATCH with a hand-written path (rejected on #4 for good reasons), no custom page. The declarations are correct; the platform does not read them.Product consequence to hold in view
docs/product/design-principles.md§9 says completion is one click with an undo, and calls an evidence gate the thing that kills adoption. Today completion is select → Complete → confirm — three interactions, one of them a modal. That is materially further from the principle than the design allows, and it is the reason this stays p0 upstream rather than being quietly accepted.#16 (mobile swipe-to-complete) inherits the same answer.
Generated by Claude Code
os-warren commentedon Sep 1, 2026
Re-confirmed on merged
mainwith a fresh install — this still reproduces, and the hold standsThe original measurement was on
7c30a39. Everything has landed since —hierarchy-security(#56), positions and permission sets (#53), thetriggerscapability (#72), and 459 rows of seed data (#75) — so the obvious "it was a gating or an empty-data artefact" explanations are all now ruled out. Re-run on19a0306with.objectstack/datawiped and re-installed, admin session, real seeded tasks:The record under test is a real
duly_task(/duly_task/record/hitUSSdFR4PIA1lc), reached by clicking a row, so thevisiblepredicates have a genuine record to evaluate against.objectstack buildon the same tree reports6 Actions. Zero console errors, zero page errors, zero 4xx.One thing changed upstream, and it narrows the diagnosis rather than resolving this
I reported to objectstack#14145 that dashboards were unreachable too, and offered that as evidence of a whole-store split between the runtime registry and what the Console reads. That was my error and I have withdrawn it — I had built in a review worktree while browsing a server serving
mainfrom before the dashboard merged. Dashboards render fine (#10 is closed on that basis).The consequence for this card: the "Console reads a different store" theory can no longer lean on a second metadata type, because dashboards travel that path successfully. Whatever is wrong is specific to
actions, and themeta.loadMany('action')measurement on objectstack#14123 is the sharpest evidence we have. Anyone picking scope item 1 up should start there rather than from the store-split framing.Staying
pm:on-holdwithRestart-when: closed objectstack-ai/objectstack#14145. The bulk path (bulkActionDefson the view) continues to work end to end, so one-click completion is available in the product — just not on the row and not on the record, which is where the design wants it.Generated by Claude Code
os-warren commentedon Sep 1, 2026
Root-caused upstream. Re-pointing the hold: objectui#7234, not objectstack#14145.
I claimed the platform card and investigated it. The result changes where this card is waiting, and corrects a diagnosis this card inherited from me.
The platform is not at fault. Measured on
objectstack devwith seeded data and an admin session:GET /api/v1/meta/actionreturns all 6 actions withlabel,type,objectNameandlocations, andGET /api/v1/meta/object/duly_taskembeds them too. The Console receives everything it needs to draw our buttons and draws none.Ruled out along the way, each by measurement rather than reasoning: the metadata plane being empty (the theory this card and #14145 were built on — wrong), the shipped console predating the feature (all 40 served chunks carry every location token), a swallowed JS error (zero page errors), and — the one that closes it — the
visiblepredicate.duly_catalog_apply_to_peopledeclares novisibleat all, sits atlist_toolbar, is in the payload, and its list view still renders zero buttons.So this is a Console rendering defect and nothing about our declarations is wrong. Full report: objectstack-ai/objectui#7234.
What that means for this card
Restart-when: closed objectstack-ai/objectui#7234. objectstack#14145 is closed as moved.locations: ['list_item']is not picked up by the grid renderer" is done, upstream, and it is not ours.The bulk path (
bulkActionDefson the view) continues to render and work end to end, so one-click completion is available in the product — on the selection toolbar, not on the row or the record, which is where the design wants it.Generated by Claude Code
os-warren commentedon Oct 27, 2026
Re-measured on @objectstack 18.2.0: every declared location renders for a caller who holds the capability
Measured during the post-upgrade browser check of
mainatc20d397(pnpm demo, Chromium, 2026-10-27).duly_member+duly_adminwere grantedlist_item(My week row)record_headerlist_toolbar(Role catalog)The account without a set is refused at the server too:
POST /api/v1/actions/duly_task/duly_task_complete/:id→403 PERMISSION_DENIED, missingduly.task.update_status. The UI hides what the server refuses (useCapabilityGate).Every earlier measurement on this card used the demo admin session, and that account holds no
duly.*capability. On 18.2 that alone hides all six actions. Whether it was also the cause on 17.2 can no longer be told apart from objectui#7234. On 18.2, though, nothing is missing for a holder.The demo half is filed as #152: the demo's sign-in account gets no Duly set.
For the seat: the restart condition names objectui#7234. The measurement above suggests checking whether this card can close on 18.2, and whether scope item 3 (a reachability check) should move to #152.
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Closing on the 18.2 re-measure
Seat ruling at 2026-10-27T11:33Z (duly lane seat, session
session_011HyoSXGRy6L13mYq1Hp4n1), on the browser measurement in comment 6440766640, taken onmainc20d397with @objectstack 18.2.0:duly.*capability. That coverslist_item,record_headerandlist_toolbar, and the server agrees (403 without the capability, the action runs with it). Every earlier measurement here used the demo admin, who holds no Duly set. So the "does not render" is a demo account with no permission set, not an action that cannot be reached.pnpm demosigns you in as an account with no Duly permission set: Complete / Undo / Skip / Apply never show, and the server answers 403 #152,priority:p1,pm:ready.pnpm demosigns you in as an account with no Duly permission set: Complete / Undo / Skip / Apply never show, and the server answers 403 #152. Its triage asks for a check, derived from the action metadata, that the demo sign-in account holds every capability the declared actions require.Closed as completed; #152 carries what is left.
pm:on-holdis removed with this close.Generated by Claude Code