unvibed.
Menu
Free UI audit

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-notes existed. Saving concise replies added margin: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
Margin test capture at 1440 pixels wide
Desktop · 1440 × 900
Margin test capture at 390 pixels wide
Phone · 390 × 844
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

  1. 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)
  2. 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.
  3. 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

Compare this demo · UI Rescue pricing