Repository navigation
Commit bb61872
feat(app-shell,components): Open the full form carries an AI chat draft in router state, marked AI suggestion until edited (#31445)
<!-- claude-projects-attribution: {"github_login":"os-zhuang"} -->
_Requested by **Sam** · [project
thread](https://claude.ai/code/project/chan_01GsBogozeZG5sdpAARrN7HP?thread=cmsg_01GsBogozeZG5sdpAARrN7HP4Tej82cK86WWZKKTHrPtsN)_
Closes #28104
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- 改了什么:AI 聊天里新建记录卡片上的「打开完整表单」,现在把卡片的字段值放在路由 state
里带到新建表单,不再拼进网址;表单里这些字段标「AI 建议」,用户一改就消失,表单上方有一句提示「保存前不会写入任何内容」,绝不自动提交。
- 为什么改:之前姓名、电话、邮箱等联系人信息直接出现在地址栏、浏览器历史和复制出去的链接里;卡片要求的「AI 建议」标记也没有。原卡写的
`record_draft` 卡片已随 cloud#3565 退役,这里做的是它在现行 `create_record` 卡片上仍然缺的那一半。
- 风险与回滚:只动界面三包(app-shell、components、i18n),没有 spec 或元数据改动;回滚即撤销本
PR,按钮回到网址预填。
- 先看:`ui/packages/app-shell/src/views/RecordFormPage.tsx`(state
与网址预填的优先级)、`ui/packages/components/src/renderers/form/form.tsx`(标记何时出现、何时消失)、`ui/packages/app-shell/src/utils/recordFormAiDraft.ts`。
- 两分钟验证:在 AI 聊天里说「新建一个线索:张伟,电话 13900001111」,在确认卡片上点「打开完整表单」,应看到地址是不带 `?`
的 `/new`,姓名和电话已填好并标「AI 建议」,改一下电话标记消失,不点保存就没有记录。
- 需要你做的:无,席位落地。
## What changes
- **Premise, checked on `main` first.** The card asked for a
`status:'record_draft'` card. That producer is gone: cloud#3565 replaced
`draft_record` with the `create_record` / `update_record` proposals
(`packages/spec/src/system/constants/platform-tool-names.ts:57-69` on
this repo; on cloud main
`service-ai-studio/src/__tests__/ask-tool-surface.test.ts:305` asserts
`draft_record` is no longer a tool, and `plugin.ts:80` retires the
`record_drafting` skill). `plugin-chatbot` already renders the
replacement record-write card with its dropped reasons
(`ChatbotEnhanced.tsx:3615-3740`, `recordWrite-3565.test.ts:94` pins
that a `record_draft` result is not a card). So work item 1 is dropped
as done. What was still missing is the rest of the card on that live
path: the card's "Open the full form" (`AiChatPage.tsx`
`openRecordForm`) put every value in the New page's **query string**
(`recordFormPrefill.ts`), and `RecordFormPage` only read the query
string (`RecordFormPage.tsx:100-108` on main). No field was marked.
- **`@object-ui/app-shell`.** `utils/recordFormAiDraft.ts` builds the
navigation: a create goes to the bare `/apps/APP/OBJECT/new` with the
values in router state (types kept, empty values left out); an update
still opens the edit page on the stored record. `RecordFormPage` reads
that state in create mode only, merges it under any query-string prefill
(which still wins per field, so a related-list "+ New" keeps naming its
parent) and over the form view's `defaults`, names the fields the draft
really fills through `SuggestedFieldsContext`, and shows one notice
above the form. `recordFormPrefill.ts` and its test are removed; nothing
else used them.
- **`@object-ui/components`.** New host slot `SuggestedFieldsContext` (a
set of field names). The `form` renderer marks each named field "AI
suggestion" inside its description, so the control's `aria-describedby`
announces it, while the field still holds its opening value (the same
empty-ish comparison the dirty check uses, so a widget normalizing on
mount does not read as an edit). The first edit removes the mark. It is
not a schema key; with no provider nothing changes.
- **`@object-ui/i18n`.** `form.aiSuggested` and `form.aiDraftNotice` in
all eleven packs. The `de` notice adds one correctly paired „…“ span, so
`de-quote-pairing-3876.test.ts`'s census moves 109 to 110.
- **Never auto-submit.** Nothing on the page or in the form submits on
mount; only Save writes. The card cancels its pending write before
navigating (unchanged), so a record saved here is written once.
- **Reload.** The card said a reload drops the draft. With the console's
`BrowserRouter`, router state lives in the tab's history entry, so
reloading or going Back to that same entry reopens the draft; a copied
link, a new tab or another device opens an empty form, because nothing
is in the URL. The values never leave the browser tab.
## Why
The draft carries contact details pasted from a chat. A query string is
kept in the address bar, the browser's history list, server and proxy
logs, and every link copied from the page. The marker tells the person
which values are the AI's guess until they have looked at them.
## Tests
-
`ui/packages/app-shell/src/utils/__tests__/recordFormAiDraft-28104.test.ts`:
the create target has no `?` and no value in the URL, the state
round-trips typed values, empty values are dropped, an update carries no
draft, malformed state reads as none.
-
`ui/packages/app-shell/src/views/RecordFormPage.aiDraft-28104.test.tsx`:
the draft seeds the create over the form defaults and names every
drafted field; a query-string prefill wins and is not named; one notice;
the draft arrives through a real `navigate(to, { state })` from another
route with the URL left bare; CONTROL: no draft or an edit names
nothing.
-
`ui/packages/components/src/renderers/form/__tests__/form-aiSuggestedMark-28104.test.tsx`:
the mark sits in the description the control names; the first edit
removes it; unnamed fields and a form with no provider carry none;
mounting submits nothing and Save submits the values.
Suites at head `05c19b1e` (run under `os-verify-lock`, shared 4-core
box):
- `@object-ui/i18n`: Test Files 99 passed (99), Tests 1804 passed, 13
skipped.
- `@object-ui/components`: Test Files 478 passed, 1 skipped (479), Tests
4578 passed, 24 skipped.
- `@object-ui/app-shell`: Test Files 2217 passed, 1 skipped (2218),
Tests 20191 passed, 9 skipped.
- `type-check` on all three packages: exit 0 (app-shell runs its
`tsconfig.test.json` program too, so the new tests are compiled).
<details><summary>Ablation, one mutation per tested behaviour,
implementation committed first, restored by blob hash</summary>
Each leg via `scripts/ablation-replace.mjs` (anchor hit 1 to 0, blob
changed, restore blob equals HEAD, `git diff HEAD` empty). The tests
resolve these packages through the vitest `src` aliases, so no `dist/`
leg applies.
- A. `form.tsx`: `suggestedFields?.has(name) &&` to `false &&` (the
renderer ignores the slot). RED as predicted: "marks a named field…" and
"the first edit takes the mark away…"; the three CONTROL /
no-auto-submit cells stay green. 2 failed, 3 passed.
- B. `RecordFormPage.tsx`: `{ ...aiDraftValues, ...prefillValues }` to
`{ ...prefillValues, ...aiDraftValues }` (the draft beats the query
string). RED: "a query-string prefill wins per field…". 1 failed, 5
passed.
- C. `RecordFormPage.tsx`: `readRecordFormAiDraft(location.state)` to
`undefined` (the page reads no router state). RED: the seed, precedence,
notice and real-navigate cells; both CONTROL cells green. 4 failed, 2
passed.
</details>
## Acceptance notes
- An **update** card's "Open the full form" still opens the edit page
without the proposed values. That is objectstack#28106 and is not
touched here.
- `SuggestedFieldsContext` is an additive export from
`@object-ui/components` (hence `Clause-②: yes (widening)`); no spec, Zod
or authorable key changes.
## Verification
<details><summary>Derived gates (`node scripts/pm/dispatch-gates.mjs
--commands`, 39 families) and reconciliation</summary>
All 39 derived commands ran locally, each with its exit code captured
before any pipe, plus `pnpm check:changeset-fixed` (its roster sits
under `.changeset/`). All exit 0. One was red on the first head and
fixed in `05c19b1e`: `pnpm check:test-source-alias` asked the new form
test to pay the `@object-ui/react-runtime` load at collection
(module-top import added), re-run exit 0. The `de` quote census in
`de-quote-pairing-3876.test.ts` moved 109 to 110 for the one new,
correctly paired „…“ span.
`dispatch-gates --ran`: "39 derived famil(ies) accounted for — 39 run, 0
NOT-MEASURED (a DERIVED zero — all 39 recorded an exit code and none of
them is 3)."
NOT MEASURED locally, owned by CI: the type-check lanes (`Type Check
UI`, workspace), repo `pnpm lint`, `test:repo`, the artifact-roster and
wide-population families.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (CI owns the repo lint)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: not touched.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the 维护者速读 block is the
seat-required Chinese summary).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QUs3BtDrV2BdBwtFsUGswG)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QUs3BtDrV2BdBwtFsUGswG
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1ff9944 commit bb61872
24 files changed
Lines changed: 645 additions & 113 deletions
File tree
- .changeset
- ui/packages
- app-shell/src
- console/ai
- __tests__
- utils
- __tests__
- views
- components/src
- renderers/form
- __tests__
- i18n/src
- __tests__
- locales
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
149 | | - | |
| 149 | + | |
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
| |||
1747 | 1747 | | |
1748 | 1748 | | |
1749 | 1749 | | |
1750 | | - | |
1751 | | - | |
1752 | | - | |
| 1750 | + | |
| 1751 | + | |
| 1752 | + | |
| 1753 | + | |
| 1754 | + | |
1753 | 1755 | | |
1754 | 1756 | | |
1755 | 1757 | | |
| |||
1760 | 1762 | | |
1761 | 1763 | | |
1762 | 1764 | | |
1763 | | - | |
1764 | | - | |
1765 | | - | |
1766 | | - | |
1767 | | - | |
1768 | | - | |
1769 | | - | |
1770 | | - | |
| 1765 | + | |
| 1766 | + | |
1771 | 1767 | | |
1772 | 1768 | | |
1773 | 1769 | | |
| |||
This file was deleted.
This file was deleted.
| 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 | + | |
| 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 | + | |
0 commit comments