Give the frontline a progress phrase, attachments and a card worth showing - #115
Conversation
`duly_task.progress` is four preset phrases plus the free-text `note` beside it, so reporting progress is one tap instead of a typed sentence; the hook stamps `last_update_at` when it changes, because picking a phrase is somebody working the task. `duly_task.attachments` is the platform file field, optional on every path and never a completion gate — pinned in the metadata and against a booted engine. The list, the board card and the record page's three sections are the deck's p7/p16/p17 layout. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
…finding The board is the owner's own: dragging a card writes `status`, and "managers do not enter status" has to be true of the data and not only of where the nav puts the link. It also keeps the lens inside one fetched page — unscoped, the first 100 of 186 rows sorted by due date were all `done` and the Open and In-progress columns rendered empty. Swimlanes by source are authorable (`grouping`, not `kanban.swimlaneField`) and are deliberately left off: measured in a browser, turning them on renders the status column-header row at height 0. The demo's in-flight tasks carry a phrase so the new column and card face show something; stalled and done rows stay blank, and each blank says something. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Reviewed — merging. Open question: A, no swimlanes.Gates, re-run by me on the head merged with current A. A board whose column headers render at height 0 is not a board — on a pre-sales screen a viewer must be able to tell Open from Done without dragging a card. The attachments asymmetry is the best decision in the PR. An upload does not move Two changes the card did not ask for, both accepted:
The zh-CN Generated by Claude Code |
Fixes #108
The three things deck p7 promises the frontline, plus the p16/p17 list and card. All metadata — no action, no handler, no customization.
Verified in a browser against a live
pnpm demo(own port 4108, Chromium 1194). Gates below were run on the merged head3b717e0.1 · A progress phrase, one tap
duly_task.progress— four preset phrases (on_time/distributed/awaiting_feedback/in_hand) beside the free-textnote, which stays as "write your own". No default: a dispatched task has reported nothing yet, and blank is the honest value for that.task.hook.tsnow stampslast_update_atwhen it changes. That was the point of putting it on the list — reporting progress is the product's headline gesture, and if it did not count as movement then a task nudged every week would still drift into "Not moving", which is the one list a manager is told to trust.Red-first, and measured as such. With the hook's field list reverted to
['status','note','skip_reason'](mutation confirmed on disk both ways) the two new assertions fail and the other 62 in the file stay green:Live proof through the console, not just the unit test — inline-select on a row of
My week, thenSave All:⛔ Not a metric. The on-time verdict is
completed_late, stamped by the server fromcompleted_atagainstlate_after.on_timehere is a person's own words about their own work; reading it as evidence would make the phrase a scored field, and then nobody picks the honest one. Said in the object header, and no dataset names the column.2 · Attachments, and never a gate
duly_task.attachments— the platform's ownfilefield,multiple: true, noacceptlist.It works with zero configuration, measured rather than assumed.
fileis a first-classFieldType, it is inMULTI_CAPABLE_TYPESsomultiplemakes it an array, andstorageis inPLATFORM_ALWAYS_ON_CAPABILITIES— the CLI's serve command mountsStorageServicePluginfrom@objectstack/service-storagewhether or not a stack asks for it. The boot banner lists it among the 41 plugins with nothing declared inobjectstack.config.ts. A real upload through the record form:Never a completion gate — the product invariant, pinned twice rather than asserted once.
test/invariants.test.tscovers the three shapes the mistake would take (required, arequiredWhenondone, a validation rule naming the column);test/task-hook.test.tscompletes a fileless task against a booted engine, completes one that has files without treating them as evidence, and strips the last file off a done task without a refusal.And an attachment deliberately does NOT move the stagnation clock.
progressis on the hook's list,attachmentsis not. An upload that silently refreshed the clock would make "attach something" the cheapest way to look busy — one step from the evidence gate the invariant forbids. Confirmed live: after the upload above,last_update_atwas still the timestamp the earlier phrase edit left.3 · The list, the card, the record page
my_weekcarries the deck's columns in the deck's order — 状态 · 工作事项 · 来源 · 到期 · 最新进展 · 附件 — and is inline-editable. The manager lenses (late,stalled,by_unit) deliberately are not: managers do not enter status is a product invariant, and an editable status cell on a team lens breaks it one row at a time.Relative dates need no key. Measured on console 17.2.0: the date cell defaults to
format: 'relative'and derives "due-like" from the field NAME (a regexdue_datematches), so it already rendersOverdue 7din red,In 3 days,Tomorrow, and an absolute date outside a ±7-day window. The card's fallback — alate_aftercolumn standing in for 逾期 N 天 — is therefore not needed, andlate_afterstays on thelatelens that filters by it.The board shows subject · due · owner · source · progress, with real column counts.
The record page is 基本 / 进展与附件 / 历史, via
fieldGroups+Field.group.historyis exactly the set ofreadonlyserver-owned stamps — a rule, not a taste, sotest/invariants.test.tsfails if a new stamp is filed anywhere else (it would otherwise appear mid-form, reading as a field somebody forgot to make editable).Chinese
#18's coverage gate is green, and green there only means the keys exist — so the screens were opened in a zh-CN console as well. Column headers 最新进展 / 附件, phrase chips 待反馈 / 已下发各部门 / 处理中, relative dates 逾期 7 天 / 3天后, section headings 基本 / 进展与附件 / 历史, board columns 待办 / 进行中 / 已完成.
A field-group heading turned out to have a bundle slot the walk did not know about, and the two halves of the platform disagree about it — recorded in
authored-text.tsbecause only one half has a reader:translateObject(obj, bundle, { locale })does not rewritefieldGroups[].label. Fed_sections.basics.labelit returns the authored English untouched, while the object's own label and every field label beside it do translate.deriveFieldGroupLayout, emits one section per group withnameset to the groupkey, and resolves the heading throughsectionLabel→objects.<object>._sections.<name>.label. So the key is real, and 基本 / 进展与附件 / 历史 render.Three measurements worth keeping
kanban.cardFieldsis not authorable;kanban.columnsis.KanbanConfigSchemais a strict object with exactlygroupByField,summarizeField,columns, socardFieldsis refused bypnpm validaterather than silently ignored — the good failure, and the opposite of the gantt block's passthrough trap. The relay readscardFields: kanban.cardFields || kanban.columns || …, socolumnslands on the card. Nothing to file.Swimlanes are authorable, and are deliberately left off — objectstack-ai/objectui#7303. The key is the view-level
grouping, notkanban.swimlaneField(strict schema rejects that one). Authored, it works: lanes rendered, headed▼ CATALOG (19). It also takes the column headers with it — in swimlane mode the status header row measures height 0, the titles sit in the DOM at y=233 inside a collapsedflex … pl-36 overflow-x-auto, and a hit test at a title's own centre returns the lane's collapse button behind it. With the key absent the same board renders them as real<h3>s (OPEN 5 · IN PROGRESS 1 · DONE 20 · SKIPPED 0). A kanban whose columns are unlabelled is not a board, and this is a p0 pre-sales screen — so lanes off,sourceon every card face instead, one line in the view comment to flip the day #7303 lands.ObjectView, whose kanban branch forwards nogrouping), and these views do not go through it. The browser is what corrected it."最新进展 (= progress 或 note)" is two things, not one. No authorable way to say "this or that" in a column: a stored
latest_progressis the maintained-flag shape AGENTS.md rule 5 forbids, and a formula field is virtual and would render the storedon_timerather than the label. So the grid carriesprogress(short, tappable) andnotestays on the record in the same group.Two changes the card did not ask for
Both are named here because neither is invisible, and both are one line to revert.
owner = {current_user_id}). Dragging a card writesstatuson the task under it, so a board of other people's rows hands every viewer a one-gesture way past managers do not enter status — the board already sits under "My work" for that reason, and this makes its data agree with its placement. It also fixes a live defect: unscoped, the first 100 of 186 rows sorted by due date were alldone, so Open and In-progress rendered "No cards / 0" on a board whose whole job is live work. Scoped, it is 26 rows, one page, every count true.on_timeon 151 finished rows would put a self-reported phrase besidecompleted_late: truewherever the drift ran over.Gates
Run through the shared verify lock on the merged head
3b717e0, after mergingorigin/main(#110 and #112 landed first; no conflicts):Screenshots live on the throwaway branch
screenshots/issue-108— delete it with the PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Generated by Claude Code