Ship-safe check · Agent-built demo
Margin
Decision. The local prototype can demonstrate a scripted conversation. Address on-device persistence and provide a removal path before asking people to enter real work material. A user may share a browser with someone else.
Scope. Clean Chromium contexts at 1440 × 900 and 390 × 844, local production build of demo/after/ai-chat, revision c0686e7, 8 November 2026. Tested the thread index, Field notes conversation and preferences. Hosting logs, operator identity, markets, intended age range, data retention and production providers are unverified.
This is not legal advice.
Evidence
- E1. Search for an unmatched fictional thread exposed “Show all threads”. In Field notes, Tab reached the labeled composer and Send. The test sent “What is the source?” and received a scripted reference to the studio brief, proof 03. The confirmation said the reply was saved. The prompt and reply survived reload. Desktop · Phone
- E2. Initial device storage was empty. After sending,
margin:field-notesexisted. Saving concise replies addedmargin:concise, which persisted after reload. Manually removing only the fictional thread key erased its turn after reload and preserved the preference. Each context had zero cookies. No off-origin request, WebSocket or request during Send was observed. Browser receipt - E3. The composer validates a nonempty draft, accepts at most 1,800 characters and selects a response with local rules. Saving preferences and conversation turns writes JSON to localStorage. The settings checkbox governs reply length. The observed forms do not send data to an external AI provider. Forms and response handler · Storage implementation · Declared packages
Browser receipt
{
"source": "demo/after/ai-chat",
"revision": "c0686e7b0f8b795f39c0d97a7cb7d60b88259a3e",
"runAt": "2026-11-08T08:54:04.444Z",
"runtime": "Next production build, Chromium 156.0.8078.80",
"views": [
{
"viewport": [
1440,
900
],
"pages": {
"/threads": {
"heading": "Studio threads",
"controls": [
{
"kind": "input",
"type": "search",
"label": "Search threads",
"maxLength": -1
}
],
"forms": 0,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [],
"localKeys": [],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 20,
"issues": []
}
},
"/chat/field-notes": {
"heading": "Field notes",
"controls": [
{
"kind": "textarea",
"type": "textarea",
"label": "Message Margin",
"maxLength": 1800
},
{
"kind": "button",
"type": "submit",
"label": "Send message",
"maxLength": null
}
],
"forms": 1,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [
{
"file": "/art/binding.svg",
"alt": "Folded signatures in the binding brief"
}
],
"localKeys": [],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 22,
"issues": []
}
},
"/chat/field-notes after reload": {
"heading": "Field notes",
"controls": [
{
"kind": "textarea",
"type": "textarea",
"label": "Message Margin",
"maxLength": 1800
},
{
"kind": "button",
"type": "submit",
"label": "Send message",
"maxLength": null
}
],
"forms": 1,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [
{
"file": "/art/binding.svg",
"alt": "Folded signatures in the binding brief"
}
],
"localKeys": [
"margin:field-notes"
],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 26,
"issues": []
}
},
"/settings": {
"heading": "Settings",
"controls": [
{
"kind": "input",
"type": "checkbox",
"label": "Use concise replies",
"maxLength": -1
},
{
"kind": "button",
"type": "submit",
"label": "Save preference",
"maxLength": null
}
],
"forms": 1,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [],
"localKeys": [
"margin:field-notes"
],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 13,
"issues": []
}
},
"/settings after reload": {
"heading": "Settings",
"controls": [
{
"kind": "input",
"type": "checkbox",
"label": "Use concise replies",
"maxLength": -1
},
{
"kind": "button",
"type": "submit",
"label": "Save preference",
"maxLength": null
}
],
"forms": 1,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [],
"localKeys": [
"margin:concise",
"margin:field-notes"
],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 13,
"issues": []
}
},
"/chat/field-notes after manual removal": {
"heading": "Field notes",
"controls": [
{
"kind": "textarea",
"type": "textarea",
"label": "Message Margin",
"maxLength": 1800
},
{
"kind": "button",
"type": "submit",
"label": "Send message",
"maxLength": null
}
],
"forms": 1,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [
{
"file": "/art/binding.svg",
"alt": "Folded signatures in the binding brief"
}
],
"localKeys": [
"margin:concise"
],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 22,
"issues": []
}
}
},
"keyboardToComposer": [
{
"label": "M\nMargin",
"outline": "solid 3px",
"matched": false
},
{
"label": "Threads",
"outline": "solid 3px",
"matched": false
},
{
"label": "Field notes",
"outline": "solid 3px",
"matched": false
},
{
"label": "Ink density",
"outline": "solid 3px",
"matched": false
},
{
"label": "Paper order",
"outline": "solid 3px",
"matched": false
},
{
"label": "Settings",
"outline": "solid 3px",
"matched": false
},
{
"label": "← Threads",
"outline": "solid 3px",
"matched": false
},
{
"label": "Message Margin",
"outline": "solid 3px",
"matched": true
}
],
"keyboardToSend": [
{
"label": "Send message",
"outline": "solid 3px",
"matched": true
}
],
"confirmation": "Reply saved to this thread.",
"blockedOutsideHosts": [],
"actionRequests": [],
"webSocketAttempts": []
},
{
"viewport": [
390,
844
],
"pages": {
"/threads": {
"heading": "Studio threads",
"controls": [
{
"kind": "input",
"type": "search",
"label": "Search threads",
"maxLength": -1
}
],
"forms": 0,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [],
"localKeys": [],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 15,
"issues": []
}
},
"/chat/field-notes": {
"heading": "Field notes",
"controls": [
{
"kind": "textarea",
"type": "textarea",
"label": "Message Margin",
"maxLength": 1800
},
{
"kind": "button",
"type": "submit",
"label": "Send message",
"maxLength": null
}
],
"forms": 1,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [
{
"file": "/art/binding.svg",
"alt": "Folded signatures in the binding brief"
}
],
"localKeys": [],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 15,
"issues": []
}
},
"/chat/field-notes after reload": {
"heading": "Field notes",
"controls": [
{
"kind": "textarea",
"type": "textarea",
"label": "Message Margin",
"maxLength": 1800
},
{
"kind": "button",
"type": "submit",
"label": "Send message",
"maxLength": null
}
],
"forms": 1,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [
{
"file": "/art/binding.svg",
"alt": "Folded signatures in the binding brief"
}
],
"localKeys": [
"margin:field-notes"
],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 17,
"issues": []
}
},
"/settings": {
"heading": "Settings",
"controls": [
{
"kind": "input",
"type": "checkbox",
"label": "Use concise replies",
"maxLength": -1
},
{
"kind": "button",
"type": "submit",
"label": "Save preference",
"maxLength": null
}
],
"forms": 1,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [],
"localKeys": [
"margin:field-notes"
],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 8,
"issues": []
}
},
"/settings after reload": {
"heading": "Settings",
"controls": [
{
"kind": "input",
"type": "checkbox",
"label": "Use concise replies",
"maxLength": -1
},
{
"kind": "button",
"type": "submit",
"label": "Save preference",
"maxLength": null
}
],
"forms": 1,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [],
"localKeys": [
"margin:concise",
"margin:field-notes"
],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 8,
"issues": []
}
},
"/chat/field-notes after manual removal": {
"heading": "Field notes",
"controls": [
{
"kind": "textarea",
"type": "textarea",
"label": "Message Margin",
"maxLength": 1800
},
{
"kind": "button",
"type": "submit",
"label": "Send message",
"maxLength": null
}
],
"forms": 1,
"links": [
"/threads",
"/chat/field-notes",
"/chat/ink-density",
"/chat/paper-order",
"/settings"
],
"images": [
{
"file": "/art/binding.svg",
"alt": "Folded signatures in the binding brief"
}
],
"localKeys": [
"margin:concise"
],
"sessionKeys": [],
"cookieNames": [],
"contrast": {
"sampleCount": 15,
"issues": []
}
}
},
"keyboardToComposer": [
{
"label": "M\nMargin",
"outline": "solid 3px",
"matched": false
},
{
"label": "Threads",
"outline": "solid 3px",
"matched": false
},
{
"label": "Settings",
"outline": "solid 3px",
"matched": false
},
{
"label": "← Threads",
"outline": "solid 3px",
"matched": false
},
{
"label": "Message Margin",
"outline": "solid 3px",
"matched": true
}
],
"keyboardToSend": [
{
"label": "Send message",
"outline": "solid 3px",
"matched": true
}
],
"confirmation": "Reply saved to this thread.",
"blockedOutsideHosts": [],
"actionRequests": [],
"webSocketAttempts": []
}
]
}
Findings
- Privacy policy
- Gap
The composer says “Local demo · studio brief” but gives no notice about persistence or removal. Request operator/hosting facts, disclose local retention by the composer and link a verified notice. Recheck before typing.
- Terms
- Owner input
Thread navigation contains no terms. Establish product scope, acceptable use, ownership, operator and support obligations before publishing approved terms. Replay access from the composer and Settings.
- Refund policy
- Outside tested flow
E1/E3 show no purchase, subscription, fee or checkout. Revisit before any paid plan, then compare the displayed refund path to a sandbox transaction.
- Cookie policy
- Gap
E2 found two localStorage keys with no expiry in E3, despite no cookies. List each key, its purpose, event, persistence and removal options in a device-storage notice, then verify fresh and returning sessions.
- Consent banner
- Owner input
E2 distinguishes message persistence from a requested response preference. The operator must classify each purpose under applicable local rules. Test any required prior choice, refusal and withdrawal without bundling unrelated purposes.
- Form consents
- Observed
E3 shows one reply form and one preference form. “Use concise replies” affects output length only. Keep that preference separate from marketing or storage permissions. Test Save unchecked as well as checked.
- Data minimization
- Gap
Free text is capped at 1,800 characters but the full message and reply persist. Decide whether history is needed by default. Describe its scope and give users control before persisting sensitive work notes. Replay with one fictional turn.
- Third-party SDKs
- Observed
E2/E3 found no model SDK, remote script, request on Send or WebSocket. Bundled React/Next/icons draw the fixture. Confirm server logging and inventory again if a model provider is introduced.
- Dark patterns
- Gap
Saving a turn is straightforward, but the reviewed controls offer no equivalent removal path. Add discoverable Clear thread plus confirmation and cancel. Ensure cancel preserves the conversation.
- Hidden fees
- Outside tested flow
E1/E3 have no price, checkout or subscription. Before monetization, compare starting price, renewal and final total in a sandbox.
- Fake reviews
- Outside tested flow
The thread list and sample answers contain no reviews or star counts. If testimonials are added, demand provenance, permission and disclosure of incentives.
- Unsupported claims
- Observed
“Local demo” and the source brief describe the fixture. E3 shows fixed replies, with no generative model used. Keep external-AI or training claims out of copy until the service proves them.
- Alt text
- Observed
Binding artwork has the descriptive alternative “Folded signatures in the binding brief”. Check that it stays accurate when artwork changes. Binding artwork
- Color contrast
- Observed
E2 checked 22 visible text pairs on the initial desktop conversation and 15 on phone. No sampled failure. Measure remaining focus, disabled and styled-image states before broader accessibility claims.
- Keyboard navigation
- Observed
E1/E2 show a 3 px focus outline through the conversation navigation, composer and Send at both widths. Enter submitted the test turn. Settings and any future dialog still need a complete keyboard replay.
- Business details
- Owner input
“Margin” and “Print studio” do not identify a legal operator. Request verified entity/contact information and place an accessible link in persistent navigation.
- Age consent
- Owner input
No audience facts or age gate. Obtain intended markets and age range and whether children’s information is knowingly collected. Do not infer an age threshold from the print-studio fixture.
- Unsubscribe link
- Outside tested flow
No email collection, marketing SDK or commercial message was present. Revisit when there is a mailing list and test a seed unsubscribe plus the next scheduled send.
- Stock image licenses
- Observed
E1 uses the repository’s original binding illustration, with no remote stock image delivered. Retain generator revision and icon supplier notices when redistributing. Asset records
- Data deletion
- Gap
Manual removal of the thread key succeeded and preserved the preference, but requires browser tooling. Add Clear this thread as an approved UI proposal. Test cancel, confirm, reload and preservation of other threads/preferences. Document any server-side limits separately.
Implementation handoff
- Product owner and designer. Decide whether history should persist by default. Add a short notice before Send stating exactly where the conversation stays, retention and how to remove it. If the EU device-access rules apply, check the purpose and national implementation before choosing the control. ePrivacy Directive, article 5(3)
- Product engineer. Implement a scoped clear action only after the owner authorizes a storage change. Reuse a synthetic thread, confirm cancellation preserves it, confirm removal survives reload and verify other keys remain. If server records later exist, test their deletion independently.
- Operator. Confirm identity, support channel, markets, hosting logs, retention, audience and any future model/subprocessors. Review privacy/terms wording against that inventory and applicable rights, then test links from composer and settings. GDPR, articles 3, 13 and 17

