Repository navigation
Commit 54521f0
qa(checklist): a hook-refused script action shows exactly one error toast carrying its sentence — item, showcase fixture, and one run (#21609)
Fixes #21596
Clause-②: no
## What this does
Gives objectui#9151's re-observation a home: one platform-checklist
item, the smallest showcase fixture it needs, and one recorded run of it
against a console built at this repo's `.objectui-sha`.
**Run verdict: PASS** (1/1 item, 4/4 clauses, reproduced on two finished
tasks on fresh page loads). The console showed exactly one `error` toast
carrying the hook's sentence, tied to a `400` from the action route, and
the record was left unchanged.
## The fixture (measured absent, so added)
`examples/app-showcase` had no hook that refuses a write.
`src/data/hooks/index.ts` held four hooks: trim, stamp, and two log-only
after-hooks, none of which throw. Added:
- `showcase_guard_task_reopen` (`src/data/hooks/index.ts`,
`GuardTaskReopenHook`): a `beforeUpdate` hook on `showcase_task`. Its
condition is `previous.done == true && record.done != true` and its
sandboxed body throws `TASK_REOPEN_REFUSAL` = "A finished task cannot be
reopened. Create a follow-up task instead." with `onError: 'abort'`.
- `showcase_reopen_task` (`src/ui/actions/index.ts`,
`ReopenTaskAction`): a `script` action, `perRecord`, offered only on
finished tasks. It appears in the task list's row menu only
(`list_item`). The Task Detail page is `kind: 'full'` and draws no
`record_header` bar, so declaring that location would have been inert.
Its body writes `{ id, done: false }` through `ctx.api`.
- zh-CN label for the action (`check:i18n-coverage` ratchet).
- `test/task-reopen-hook-refusal.test.ts` (5 tests). It uses the
production harness: real `ObjectQL`, `SqlDriver` and
`QuickJSScriptRunner`, with the app's real objects and hooks. The tests
check four things. The reopening write is refused with the sentence and
the row is unchanged. An edit of a finished task that leaves `done`
alone still lands. With the hooks unbound, the same write lands (reverse
check). The action body writes exactly the refused shape.
## The checklist item
`docs/qa/platform-checklist/areas/records-forms.json`, id
`records-forms.script-action-hook-refusal-toast` (revision 1, P2,
browser). It sits in records-forms, beside the area's action-surface
items (`action-location-matrix`, `action-param-widgets`,
`object-hook-lifecycle`). It has four clauses:
- **network:** the action route answers 4xx carrying the sentence.
- **dom:** exactly one new toast, `data-type=error`, containing the
sentence. It is counted inside sonner's 4s auto-dismiss window,
excluding `data-removed` nodes.
- **network:** the toast is tied to its request. That request is the
only non-GET request in the window, and its message matches the toast.
- **api:** the record is unchanged.
It is mapped under `action` and `hook` in `coverage.json`.
`records-forms.object-hook-lifecycle` was bumped from revision 2 to 3,
roster only, with no clause moved. Two of its knownGaps became false
once the fifth hook landed: "no two showcase hooks share an
object+event" and "the fixtures never throw". Both now name the new hook
and say why it does not close that item's ordering clause or its
beforeInsert abort-rollback clause.
`pnpm check:platform-checklist` passes with exit 0: 15 areas, 270 items,
coverage 39 kinds mapped and 2 waived, symbol anchors 650/660.
## The run
- **Durable run record: #21610** (the `qa-run` issue, RUNNER's record of
this run).
- Record:
`docs/qa/platform-checklist/runs/2026-10-03-script-action-hook-refusal-toast.json`.
RUNNER's location; git-ignored, so it is not in this diff. The `qa-run`
issue body is in the report comment on #21596.
- Environment: framework `4a428c11a0` (the tree served), objectui pin
`89cad75d55702cc4f267bead5bf267de575d5842`. The console was built by
`pnpm objectui:build` (exit 0, stamped objectui@89cad75d5570). The
server was an isolated `objectstack dev --ui --seed-admin` on its own
port with its own file DB. Driven by Playwright through
`/opt/pw-browsers/chromium`, signed in through the form as the seeded
admin.
- Both attempts returned `POST
/api/v1/actions/showcase_task/showcase_reopen_task` with status `400
VALIDATION_ERROR`, message "hook 'showcase_guard_task_reopen' threw:
Error: A finished task cannot be reopened. Create a follow-up task
instead."
- Live toasts went from 0 before the click to 1 after
(`data-type=error`, same text). A MutationObserver installed before the
click saw exactly one toast node added in the following 6s.
- The only non-GET request in the window was that POST.
- `done`, `progress` and `updated_at` were identical before and after.
## Acceptance notes
- **Not scored against the item:** the action route hands the user the
sandbox debug wrapper. The toast reads "hook
'showcase_guard_task_reopen' threw: Error: " followed by the sentence.
The same refusal through `PATCH /api/v1/data/showcase_task/ID` answers
`400` with the bare sentence. The wrapper is what
`packages/qa/dogfood/test/hook-error-format.dogfood.test.ts` says
belongs in server logs, not in the toast. This is reported to the PM in
the report and is not fixed here: no runtime change is in scope.
- **Changeset:** none. The only package this touches is
`@objectstack/example-showcase`, which is `"private": true`. Its earlier
showcase-only fixes (`393ae878d3`, `99398542b3`) also carried none.
`docs/qa/**` does not publish. `skip-changeset` applies.
## Verification (head `ff3d71c7cf`)
- `pnpm --filter @objectstack/example-showcase run typecheck` passed
(exit 0); `--listFiles` includes the new test.
- `pnpm --filter @objectstack/example-showcase exec vitest run
--maxWorkers=2`: 32 files and 399 tests passed.
- `node scripts/pm/dispatch-gates.mjs --commands` derived 45 commands;
all 45 exited 0. `--ran` reconciliation: 45 derived, 45 run, 0 unrun.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0721848 commit 54521f0
6 files changed
Lines changed: 373 additions & 8 deletions
File tree
- docs/qa/platform-checklist
- areas
- examples/app-showcase
- src
- data/hooks
- system/translations
- ui/actions
- test
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
154 | 155 | | |
155 | 156 | | |
156 | 157 | | |
157 | | - | |
| 158 | + | |
| 159 | + | |
158 | 160 | | |
159 | 161 | | |
160 | 162 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
119 | 119 | | |
120 | 120 | | |
121 | 121 | | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
122 | 162 | | |
123 | 163 | | |
124 | 164 | | |
| 165 | + | |
125 | 166 | | |
126 | 167 | | |
127 | 168 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
494 | 494 | | |
495 | 495 | | |
496 | 496 | | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
497 | 500 | | |
498 | 501 | | |
499 | 502 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
87 | 124 | | |
88 | 125 | | |
89 | 126 | | |
| |||
457 | 494 | | |
458 | 495 | | |
459 | 496 | | |
| 497 | + | |
460 | 498 | | |
461 | 499 | | |
462 | 500 | | |
| |||
Lines changed: 183 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
0 commit comments