Skip to content

Commit ea74ebf

Browse files
fix(record-page): each task action drawn once with a reason on the greyed Archive; one date format, aligned cards and readable comments (#28495)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoS1PGrJjJvkgDrLpK8ck5SJ)_ Part of #27520 (rows 9 and 14, the record page; the card's other rows are landed or carded elsewhere, and the seat closes it) Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:任务记录页的动作只画一次(页头一排;正文按钮条只放页头不画的「Recalculate Estimate」),灰掉的 Archive 说明原因「Mark the task done first.」(中文「请先将任务标记为完成。」);项目记录页日期统一带年份、追加的附件卡片/页脚与页面卡片对齐、评论正文改用前景色。 - 为什么改:18.3.0 RC 走查第 9、14 行:同一组按钮出现两次、禁用提示只说「Not available for this record」、同一页两种日期格式、附件卡片比其他卡片宽、评论文字灰得看不清。第 9 行两半都出自 showcase 示例自身的元数据(页面重复声明、动作未写 disabledReason),按契约优先改生产者,不在渲染器里去重。 - 风险与回滚:渲染侧只改 3 处显示(详情区日期带年份、追加区块加水平内边距、评论正文颜色),不改任何契约或数据;showcase 动作的 locations 调整后六个位置仍各有宿主,平台清单 action-location-matrix 同步改写。回滚即 revert 本 PR。 - 先看:`examples/app-showcase/src/ui/pages/task-detail.page.ts` 与 `src/ui/actions/index.ts` 的 locations;`ui/packages/app-shell/src/views/RecordDetailView.tsx` 的 `RECORD_PAGE_APPEND_INSET`;`ui/packages/plugin-detail/src/DetailSection.tsx` 的 `alwaysShowYear`。 - 两分钟验证:`pnpm dev` 后打开任意未完成任务:页头 Mark Done / Log Time / Archive 各一个,正文只有 Recalculate Estimate,悬停 Archive 看到「Mark the task done first.」;打开项目 Website Relaunch:Start Date 读作「Oct 2, 2026」,Attachments 卡片与上方卡片同宽。 - 需要你做的:无,席位落地。 ## What changes **Row 9, task record page (showcase example metadata, the producer):** - `task-detail.page.ts` drew Mark Done, Log Time and Archive twice: its `page:header` lists them and its `record:quick_actions` bar named the same three. The bar now names the task's `record_section` set alone, `showcase_recalc_estimate`, which the header does not draw. A renderer dedupe would have overridden an explicit `kind: 'full'` composition, so the page is fixed instead. - `actions/index.ts`: Mark Done, Log Time and Archive drop `record_section`; Recalculate Estimate declares `record_section` only (it was `record_more` + `record_section`), so it is drawn once, in the bar, not again under the header overflow. Every record location keeps a stock host on `showcase_task` (header: the three; overflow: open_docs, recalc_selection; related: log_time; section: recalc_estimate). - `ArchiveTaskAction` declares `disabledReason: 'Mark the task done first.'` (the `ActionSchema.disabledReason` door), plus the zh-CN copy key. The console drew the generic fallback only because the specimen declared none. - `docs/qa/platform-checklist/areas/records-forms.json`, `records-forms.action-location-matrix`: fixtures.requires, the record_section variant and the CLOSED-gap note re-pointed to the new hosts; revision 18. **Row 14, project record page (renderer):** - `plugin-detail` `DetailSection`: a section row passes `alwaysShowYear` to the date cell, as `HeaderHighlight` already does, so the strip ("Dec 31, 2026") and a section ("Oct 2" before, "Oct 2, 2026" now) read one way. Grid cells keep the face that drops the current year. One call site. - `app-shell` `RecordDetailView`: the appends drawn after an authored page (Approvals fallback, Attachments fallback, provenance footer) take the page body's horizontal inset (`px-3 md:px-4 lg:px-6`, the `page` renderer's padding), so they no longer draw 24 px wider on each side. - `plugin-detail` `RecordActivityTimeline`: a feed entry's body uses `text-foreground` (was `text-muted-foreground`), as a reply's body already does; the meta line stays muted. ## Tests - New: `plugin-detail` `DetailSection.dateYear-27520` (3: section with year, strip and section alike, grid control), `RecordActivityTimeline.commentBodyContrast-27520` (2: body foreground, meta-line control), `app-shell` `RecordDetailView.appendInset-27520` (2: Attachments fallback and footer carry the inset read off the real page root), showcase `archive-disabled-reason` (2), and in showcase `record-action-location-hosts` a record_section block (3: bar parses and names the whole record_section set, no action drawn by both header and bar) plus "every record location keeps a stock host". - Ablations through `scripts/ablation-replace.mjs`, each restored with blob == HEAD and `git diff HEAD` empty: drop `alwaysShowYear` in DetailSection, 2 failed / 1 passed; body back to muted, 1 failed / 1 passed; inset constant emptied, 2 failed; delete the `disabledReason` line, 1 failed / 1 passed; bar names the three header actions again, 2 failed / 11 passed. ## Acceptance notes - Row 9's two halves were measured as producer facts, not renderer gaps: the `disabledReason` door already exists and every action surface falls back to the generic text only when it is absent. - Removing the bar outright (the first proposal) would have left `record_section` with no stock host, which the P1 checklist item requires; the seat chose to keep the bar on an action the header does not draw. - `examples/app-showcase/src/ui/views/task.view.ts`'s `legacy_row_actions` comment still says recalc_estimate is declared at "`record_more` / `record_section`"; it is now `record_section` only. The comment's point (not a `list_item` action) still holds; the file is outside this PR's surface. - The showcase's other `disabled` specimen, `showcase_zoo_disabled_gate`, still declares no `disabledReason`; row 9 named the task's Archive only. - On the project page the Attachments fallback reads "Loading attachments…" until it scrolls into view; that is the deliberate lazy read, not a stuck load. - `RecordDetailView.tsx` is also touched by PR #28490 (the delete confirm); the hunks do not overlap. ## Verification <details><summary>Live re-measure on main 9b4d193 and after-read at this branch (showcase --fresh, admin, 1440x900, Console from source)</summary> Before (main): task page buttons Mark Done / Log Time / Archive in the header and again in the body; Archive aria-describedby "Not available for this record". Project page: highlights "Dec 31, 2026", section "Oct 2"; Attachments card x=280 w=1136 against the other cards x=304 w=1088; comment body class `text-muted-foreground`, rgb(100, 116, 139). After (this branch): task page header Mark Done / Log Time / Archive, body Recalculate Estimate only; Archive aria-describedby "Mark the task done first.". Project page: "Dec 31, 2026" and "Oct 2, 2026"; Attachments x=304 w=1088; comment body `text-foreground`, rgb(2, 8, 23). </details> <details><summary>Local runs (head beac4b2; the package suites ran at 6b17724, and the later commits add the changeset and a merge of origin/main 8c3f9d9)</summary> - After merging origin/main (8c3f9d9, which brought PR #28490's RecordDetailView hunk), every `RecordDetailView*` test plus the two new plugin-detail tests reran: 94 files, 587 tests passed (VERDICT command-exit 0). - `pnpm --dir ui exec vitest run packages/plugin-detail/` under the lock: Test Files 357 passed, 1 skipped; Tests 3172 passed, 18 skipped (lock VERDICT command-exit 0). - App-shell and console record-page slice (every `RecordDetailView*` test plus each test importing plugin-detail, `DetailSection` or `RecordActivityTimeline`): 116 files, 862 tests passed (VERDICT command-exit 0). The whole app-shell suite is left to CI. - `pnpm --dir examples/app-showcase run test`: 42 files, 511 tests passed. `typecheck` exit 0. `objectstack validate` exit 0, no warning names a changed action. - `type-check` of `@object-ui/plugin-detail` and `@object-ui/app-shell`: exit 0. - `dispatch-gates --commands`: 60 derived, each run with stdin from /dev/null; `--ran`: 60 derived, 60 run, 0 NOT-MEASURED. `check:dual-build-cjs-loads` first exited 3 (PREREQUISITE NOT MET: 7 packages without dist); after building them it exited 0. Includes `node scripts/check-sdui-manifest.mjs` exit 0, `pnpm check:platform-checklist` exit 0, `pnpm --dir ui exec vitest run scripts/` (169 files passed, 2 skipped). - `pnpm --dir ui check:new-line-citations`: 0 new. eslint on the changed files: 0 errors. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01L9qibvWX6aMbbnnsKtDC5k)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 755794f commit ea74ebf

13 files changed

Lines changed: 494 additions & 24 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
'@object-ui/app-shell': patch
4+
---
5+
6+
fix(record-page): one date format per record page, appended cards line up with the page's own, and comment text reads in the foreground colour
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
Three record-page readability fixes from the 18.3.0 RC walkthrough:
12+
13+
- **One date format.** A `record:details` section row now writes the year on a current-year date, as the highlights strip above it already does. A project page read "End Date Dec 31, 2026" in the strip and "Start Date Oct 2" in a section; both now read with the year. Grid cells keep the face that drops the current year.
14+
- **Aligned cards.** The panels the record page appends after an authored page (the Attachments and Approvals fallbacks, and the "Created … Updated …" footer) take the same horizontal inset as the page body, so they no longer draw wider than the page's own cards.
15+
- **Comment contrast.** A feed entry's body text uses the foreground colour, as a reply's body already did. The actor, the timestamp and the other meta text stay muted.

‎docs/qa/platform-checklist/areas/records-forms.json‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2056,7 +2056,7 @@
20562056
"title": "Action buttons surface at exactly their declared locations — list toolbar, list row, record header/more/related/section — and each dispatches for real",
20572057
"since": "v15",
20582058
"status": "active",
2059-
"revision": 17,
2059+
"revision": 18,
20602060
"priority": "P1",
20612061
"surface": "browser",
20622062
"personas": [
@@ -2065,7 +2065,7 @@
20652065
"fixtures": {
20662066
"app": "showcase",
20672067
"requires": [
2068-
"the per-location action fleet on showcase_task (examples/app-showcase/src/ui/actions/index.ts): showcase_bulk_reassign (list_item+list_toolbar, flow), showcase_quick_view (list_item, modal), showcase_mark_done (list_item+record_header+record_section, a declarative write on the default script route — operation 'update', patch {done: true, progress: 100} — visible 'has(record.done) && record.done != true'), showcase_log_time (record_header+record_related+record_section, form), showcase_open_docs (record_more, url), showcase_recalc_selection (record_more, api — declares record_more ONLY, which is what keeps it off the toolbar; objectui#3142), showcase_new_task (locations: [] — the fleet's headless specimen, read by the empty-locations probe), showcase_reopen_task (list_item only, operation 'update' patch {done: false}, visible 'has(record.done) && record.done == true' — Mark Done's mirror, so a done task's row menu offers Reopen where a not-done one offers Mark Done; a click on it is refused by the showcase_guard_task_reopen hook by design, which records-forms.script-action-hook-refusal-toast drives)",
2068+
"the per-location action fleet on showcase_task (examples/app-showcase/src/ui/actions/index.ts): showcase_bulk_reassign (list_item+list_toolbar, flow), showcase_quick_view (list_item, modal), showcase_mark_done (list_item+record_header, a declarative write on the default script route — operation 'update', patch {done: true, progress: 100} — visible 'has(record.done) && record.done != true'), showcase_log_time (record_header+record_related, form), showcase_open_docs (record_more, url), showcase_recalc_estimate (record_section only, api — the Task Detail quick-actions bar's action), showcase_recalc_selection (record_more, api — declares record_more ONLY, which is what keeps it off the toolbar; objectui#3142), showcase_new_task (locations: [] — the fleet's headless specimen, read by the empty-locations probe), showcase_reopen_task (list_item only, operation 'update' patch {done: false}, visible 'has(record.done) && record.done == true' — Mark Done's mirror, so a done task's row menu offers Reopen where a not-done one offers Mark Done; a click on it is refused by the showcase_guard_task_reopen hook by design, which records-forms.script-action-hook-refusal-toast drives)",
20692069
"seeded tasks in both done and not-done states for the CEL visibility both-sides check"
20702070
],
20712071
"provisioning": {
@@ -2074,7 +2074,7 @@
20742074
},
20752075
"knownGaps": [
20762076
"NO PIN COVERS A FULL CLAUSE — `automated` was dropped at revision 13, so this P1 item is hand-driven (Tier 2). The two objectui live specs it cited are adjacent: e2e/live/list-row-action-cel.spec.ts opens the first task row's menu and asserts only that at most one Mark Done renders (not both sides of acceptance[3], nor its fail-closed half), and e2e/live/action-modal.spec.ts only that the host's row Edit opens and closes a modal (one dispatch kind of acceptance[2], on no declared action). Tier 1 run #24598 scored every clause not-run on them. Re-add `automated` only with a pin that asserts a full clause.",
2077-
"[CLOSED by #22030 (PR #22036, 60ccda5a24), lifted by #22039 — the gap text is rewritten to say where each location now lives] a stock showcase boot hosts all six locations on showcase_task, including the three this gap used to block. record_header and record_more: Task Detail (showcase_task_detail, the record page the console opens for any task), whose first node is a page:header. That header renders the action ids it lists, each placed by its own locations, followed by the host's Edit / Share / Delete. Inline (record_header) on a desktop viewport, where the header's default inline budget is 3 (1 on mobile; a record_header action past the budget folds into ⋯): showcase_mark_done (script, shown only while the task is not done), showcase_log_time (form) and showcase_archive_task (script, disabled until the task is done). Under the ⋯ overflow (record_more without record_header): showcase_open_docs (url), showcase_recalc_estimate (api) and showcase_recalc_selection (api). The Task Detail quick-actions bar (record_section) names its actions explicitly (actionNames: mark_done, log_time and archive_task, in examples/app-showcase/src/ui/pages/task-detail.page.ts), so showcase_recalc_estimate, which declares record_section too, is absent there by page authoring, not a missing placement (run #25817). record_related: Project Detail (showcase_project_detail, the record page for any project) → the Related tab (?tab=related), which holds the Tasks related list, a record:related_list for showcase_task through its project FK; open a project that has tasks. It is an authored node, not the synthesizer's strip: the page's tabs override still replaces that strip, and this tab puts the list back with no actions key, so its rows carry showcase_log_time beside the task's list_item actions, and its header renders the list_toolbar action (Reassign…), as in 18.1 (run #25817): that list's own toolbar placement, not an extra one. The Tasks tab beside it is the record:line_items grid, which hosts no record_related action. An expected action missing from one of these hosts is a missing placement, never blocked(fixture). The hosts were read from objectui source at pin a58626c88dc8 and from the served metadata, not from a browser render, so the first run against them is their first browser evidence",
2077+
"[CLOSED by #22030 (PR #22036, 60ccda5a24), lifted by #22039 — the gap text is rewritten to say where each location now lives] a stock showcase boot hosts all six locations on showcase_task, including the three this gap used to block. record_header and record_more: Task Detail (showcase_task_detail, the record page the console opens for any task), whose first node is a page:header. That header renders the action ids it lists, each placed by its own locations, followed by the host's Edit / Share / Delete. Inline (record_header) on a desktop viewport, where the header's default inline budget is 3 (1 on mobile; a record_header action past the budget folds into ⋯): showcase_mark_done (script, shown only while the task is not done), showcase_log_time (form) and showcase_archive_task (script, disabled until the task is done). Under the ⋯ overflow (record_more without record_header): showcase_open_docs (url) and showcase_recalc_selection (api). The Task Detail quick-actions bar (record_section) names the task's record_section set, showcase_recalc_estimate (api), in examples/app-showcase/src/ui/pages/task-detail.page.ts; no action the header draws is also in the bar, so each action renders once on the page (#27520 row 9). record_related: Project Detail (showcase_project_detail, the record page for any project) → the Related tab (?tab=related), which holds the Tasks related list, a record:related_list for showcase_task through its project FK; open a project that has tasks. It is an authored node, not the synthesizer's strip: the page's tabs override still replaces that strip, and this tab puts the list back with no actions key, so its rows carry showcase_log_time beside the task's list_item actions, and its header renders the list_toolbar action (Reassign…), as in 18.1 (run #25817): that list's own toolbar placement, not an extra one. The Tasks tab beside it is the record:line_items grid, which hosts no record_related action. An expected action missing from one of these hosts is a missing placement, never blocked(fixture). The hosts were read from objectui source at pin a58626c88dc8 and from the served metadata, not from a browser render, so the first run against them is their first browser evidence",
20782078
"CLOSED by the qaact-placement-specimen recipe (#26625) for acceptance[4] half (a) and acceptance[3]'s fail-closed half. A runtime-authored action is still not joined into the served object's actions[] (QA run #23163 A4: its positive control, the same probe with a location, rendered nowhere), and authoring refuses a throwing `visible` (QA run #26456), so ⛔ do not author either probe at runtime. The recipe installs both specimens through the package loader, whose actions the served object does carry. Score half (a) on qaact_no_locations and the fail-closed half on qaact_visible_throws, each only on a surface where the recipe's qaact_control renders: the list row menu and the Project Detail Related-tab row menus. Task Detail's header, ⋯ overflow and quick-actions bar list their action ids, so the control is absent there and they are no evidence for either half (the recipe's knownGaps[0]). Half (b) stays on showcase_new_task as before."
20792079
]
20802080
},
@@ -2084,7 +2084,7 @@
20842084
"record_header — showcase_mark_done / showcase_log_time in the detail title bar",
20852085
"record_more — showcase_open_docs / showcase_recalc_selection under the ⋯ overflow",
20862086
"record_related — showcase_log_time on each row of the related-list section inside a record (the Tasks related list on a showcase_project record), and on no row of the showcase_task list view",
2087-
"record_section — showcase_mark_done / showcase_log_time in the Task Detail quick-actions bar (record:quick_actions resolves through the location filter)",
2087+
"record_section — showcase_recalc_estimate in the Task Detail quick-actions bar (record:quick_actions resolves through the location filter)",
20882088
"empty-locations semantics probe — an action with NO locations key renders on NO surface, and `locations: []` likewise (objectui#3142 collapsed the renderers onto one membership predicate, `actionRendersAt`; placement is a declaration, which is why recalc_selection has to name record_more)"
20892089
],
20902090
"steps": [
@@ -2247,6 +2247,12 @@
22472247
"date": "2026-10-31",
22482248
"change": "fixtures.requires[0], steps[5] and acceptance[3]'s clause name Mark Done's real predicate and list showcase_reopen_task. From QA run #27441 (W3-C3b, at c4c515ad; wave anchor #27377, wave-3 accuracy slice S1). showcase_mark_done's `visible` has been 'has(record.done) && record.done != true' since #8990 (has() guards the sparse list_item face, and != true never faults on a projected null), not '!record.done', and it is a declarative operation 'update' write on the default script route. showcase_reopen_task, the hook-refusal specimen, is its list_item-only mirror ('has(record.done) && record.done == true'), so a done task's row menu shows Reopen where the not-done one shows Mark Done; steps[5] says not to count it. Read at base a0f865491d: examples/app-showcase/src/ui/actions/index.ts. The both-sides contract is unchanged",
22492249
"ref": "#27441"
2250+
},
2251+
{
2252+
"revision": 18,
2253+
"date": "2026-11-01",
2254+
"change": "The Task Detail page draws each task action once (#27520 row 9: the walkthrough saw Mark Done, Log Time and Archive both in the header and as a button row in the body). The quick-actions bar now carries the task's record_section set alone, showcase_recalc_estimate, which declares record_section only, and Mark Done, Log Time and Archive drop record_section. fixtures.requires[0], the record_section variant and knownGaps[1] re-pointed: record_section is still hosted on showcase_task, by Recalculate Estimate in the bar, and the ⋯ overflow keeps open_docs (url) and recalc_selection (api). Read from the examples source at this branch, not a browser run; no clause changed",
2255+
"ref": "#27520"
22502256
}
22512257
],
22522258
"enumSource": {

‎examples/app-showcase/src/system/translations/index.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,11 @@ export const ShowcaseTranslationBundle = {
543543
showcase_quick_view: { label: '快速查看' },
544544
showcase_log_time: { label: '记录工时' },
545545
showcase_new_task: { label: '新建任务' },
546-
showcase_archive_task: { label: '归档', successMessage: "{{ record.title | default:'任务' }}已归档(演示——未修改任何数据)。" },
546+
showcase_archive_task: {
547+
label: '归档',
548+
successMessage: "{{ record.title | default:'任务' }}已归档(演示——未修改任何数据)。",
549+
disabledReason: '请先将任务标记为完成。',
550+
},
547551
},
548552
// Section headings of the six form-view projections in
549553
// `ui/views/task.view.ts` (edit / tabbed / wizard / split / quick).

‎examples/app-showcase/src/ui/actions/index.ts‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,9 @@ export const MarkDoneAction = defineAction({
9090
// still reads an unset `done` as "not done". `has()` alone is therefore the
9191
// whole guard here. Rule: `packages/objectql/src/declared-fields.ts`.
9292
visible: 'has(record.done) && record.done != true',
93-
// `record_section` so the Task Detail page's `record:quick_actions` bar
94-
// (which names this action) resolves it — the engine location-filters even
95-
// explicitly-named actions, mirroring the platform's own sys-user pages.
96-
locations: ['list_item', 'record_header', 'record_section'],
93+
// No `record_section`: the Task Detail header already draws it, and its
94+
// quick-actions bar carries only what the header does not (one action row).
95+
locations: ['list_item', 'record_header'],
9796
refreshAfter: true,
9897
});
9998

@@ -190,7 +189,11 @@ export const RecalcEstimateAction = defineAction({
190189
execution: 'perRecord',
191190
// Names the task (see MarkDoneAction's note on the hole and its fallback).
192191
successMessage: "Estimate recalculated for {{ record.title | default:'the task' }}.",
193-
locations: ['record_more', 'record_section'],
192+
// `record_section` only: the Task Detail page's `record:quick_actions` bar
193+
// names it, and the engine location-filters even explicitly-named actions.
194+
// Not `record_more` too, so the page draws it once, in that bar, and not
195+
// again under the header's ⋯ overflow.
196+
locations: ['record_section'],
194197
// The endpoint is record-scoped and rejects a body without an id. On a
195198
// record surface the page supplies it; invoked from a LIST ROW (see the
196199
// `legacy_row_actions` view) nothing else can, so name the body key the
@@ -268,8 +271,7 @@ export const LogTimeAction = defineAction({
268271
// implicitly claims the `default` key), so pointing a form action there opens
269272
// a list as a form — now a build error (#2554).
270273
target: 'showcase_task.form',
271-
// `record_section` so it surfaces in the Task Detail quick-actions bar too.
272-
locations: ['record_header', 'record_related', 'record_section'],
274+
locations: ['record_header', 'record_related'],
273275
refreshAfter: true,
274276
});
275277

@@ -496,7 +498,12 @@ export const ArchiveTaskAction = defineAction({
496498
// Disabled while the task is not done — visible either way. #8990: `has()`
497499
// for the sparse face; `!=` against a literal cannot fault, so no `!= null`.
498500
disabled: 'has(record.done) && record.done != true',
499-
locations: ['record_header', 'record_section'],
501+
// What the greyed button says while `disabled` holds: why it is
502+
// unavailable and what unlocks it. Without it the console falls back to
503+
// "Not available for this record", which names neither. Display only; it
504+
// never gates the dispatch.
505+
disabledReason: 'Mark the task done first.',
506+
locations: ['record_header'],
500507
refreshAfter: false,
501508
});
502509

‎examples/app-showcase/src/ui/pages/task-detail.page.ts‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import { definePage } from '@objectstack/spec/ui';
1313
* • `record:alert` — a conditional banner shown only while the task is
1414
* In Review (demonstrates the ADR-0089 component-node
1515
* `visibleWhen` predicate, `has()`-guarded).
16-
* • `record:quick_actions` — object Actions surfaced as inline buttons.
16+
* • `record:quick_actions` — object Actions surfaced as inline buttons: the
17+
* task's `record_section` set, which the header
18+
* does not draw, so each action shows once.
1719
* • `record:highlights` + `record:details` — the standard compact + section
1820
* layout.
1921
* `kind: 'full'` — this page fully owns the record layout, as the Project
@@ -57,9 +59,8 @@ export const TaskDetailPage = definePage({
5759
'showcase_mark_done',
5860
'showcase_log_time',
5961
'showcase_archive_task',
60-
// record_more only — the ⋯ overflow (url, api, api).
62+
// record_more only — the ⋯ overflow (url, api).
6163
'showcase_open_docs',
62-
'showcase_recalc_estimate',
6364
'showcase_recalc_selection',
6465
],
6566
},
@@ -110,10 +111,10 @@ export const TaskDetailPage = definePage({
110111
properties: {
111112
location: 'record_section',
112113
align: 'start',
113-
// showcase_archive_task is the `disabled`-predicate specimen:
114-
// visible on every task, greyed until `record.done` (contrast with
115-
// showcase_mark_done's `visible`, which HIDES once done).
116-
actionNames: ['showcase_mark_done', 'showcase_log_time', 'showcase_archive_task'],
114+
// The task's `record_section` set, and nothing the header above
115+
// draws: one action row per action, never the same buttons twice.
116+
// Mark Done, Log Time and Archive live in the header only.
117+
actionNames: ['showcase_recalc_estimate'],
117118
},
118119
},
119120
{

0 commit comments

Comments
 (0)