feat(app): close deferred-item gaps + demo-client branding preview#49
Merged
Conversation
Surfaces a demo client's configured brand back to the admin: a new DemoClientThemePreview (reuses resolveTheme() + the escaped --app-logo-url background from BrandThemeProvider) renders colour swatches + logo on the demo-clients list (compact, configured fields only), the detail page (full resolved brand), and live in the edit form. Closes the gap where an admin could set four theme fields and see nothing back. Also refreshes the stale development-plan.md header (P6/P7 were merged but read "not started") and records the 2026-06-07 deferred-item audit + the five gap-fills in the decisions log. Item 0 + Item 1 of the deferred-gaps plan. App surface — no CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The live per-turn persistence (persistTurn) wrote a refined answer's corrected value via the upsert seam but never appended to AppAnswerSlot.refinementHistory — only the F4.4 preview route did. Real respondent sessions therefore silently dropped the "evolved across turns" provenance trail that P8 analytics/exports read. persistTurn now takes the full F4.4 path for refinements: loadAnswerSlot → applyRefinement → persistRefinement, so the history grows with the pre-change value/provenance/source. A refinement targeting a slot with no captured answer (shouldn't happen) falls back to a plain refined-provenance upsert rather than skipping or throwing. Item 2 of the deferred-gaps plan. App surface — no CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When an invited email already had an account, accept returned a dead-end
409 ACCOUNT_EXISTS (claim-via-login was deferred to P7 and never built).
Now an existing email claims the invitation by signing in: the supplied
password is verified via signInEmail (a wrong one is 401 INVALID_CREDENTIALS,
binding nothing) and the invitation binds to that existing account. Binding
moved to after sign-in so a failed credential never half-registers.
metadata now reports accountExists so the landing form asks for the existing
password ("sign in to claim") instead of offering to set a new one.
Security: accountExists is disclosed only to a valid token-holder for their
invited email; password attempts are bounded by acceptInviteLimiter + the
better-auth sign-in limiter; binding requires a verified sign-in.
Item 3 of the deferred-gaps plan. App surface — no CHANGELOG entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds POST /api/v1/app/questionnaires/:id/clone-for-client + a "Clone for client" dialog on the detail page: duplicate a questionnaire's current version (launched else latest) into a new draft questionnaire attributed to a chosen demo client, so the same questionnaire is re-usable for the next prospect. Closes the P2.5-deferred clone-for-client, unblocked since P3 gave it F2.2 tags + F3.1 config. Extracts the version deep-copy (sections/slots/tags/config) from fork.ts into a shared _lib/copy-version-graph.ts, single-sourced by the version-fork and the clone route so they can't drift (fork's tests are the regression net). Copies goal/audience + the newest source-doc provenance; does not copy sessions, invitations, evaluation runs, or extraction-change records. Item 4 of the deferred-gaps plan. App surface — no CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the F7.1-listed attachment input, which shipped out-of-scope blocked on an F4.2 capability extension that was itself deferred in F6.1 (voice was wired; attachments never were). - Answer-extractor capability accepts an `attachments` arg: the prompt builder turns the user turn into multimodal content parts (text + image/document parts, mirroring the platform chat message builder), and a pre-call assertModelSupportsAttachments gate returns a typed attachments_not_supported error rather than silently dropping the attached answer. redactProvenance records only an attachmentCount. - The /messages route + orchestrator TurnState thread attachments through to the extractor. - The chat composer gains a flag-gated paperclip affordance (file chips, 10-file / 5MB client caps); the stream hook sends attachments in the body. - Dark-launch sub-flag APP_QUESTIONNAIRES_ATTACHMENT_INPUT_ENABLED (seed 024, off), ANDed with master + live-sessions. With it off the affordance is hidden and the route ignores sent attachments — the paid multimodal path stays shut. Item 5 of the deferred-gaps plan. App surface — no CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the F4.3 cost knob that was deferred "to F4.6's real loop" and never landed: detection ran every turn (windowed by contradictionWindowN, which is a comparison window, not a cadence). - Additive config column contradictionEveryNTurns (Int @default(1); 1 = every turn) + migration (plain ADD COLUMN, no pgvector DDL). - shouldRunDetection gains an optional cadence arg ({ everyNTurns, turnIndex }): for phase 'turn' it runs only when turnIndex % everyNTurns === 0; the completion sweep ignores cadence (the final gate never skips). - The orchestrator passes the zero-based selectionRound as the turn index, so a higher N trades per-turn immediacy for lower cost. - Config editor gains a "Detection cadence (every N turns)" field + FieldHelp. Migration applied via db:migrate:deploy + db:drift-check at deploy (the create-only/strip flow; this column needs no stripping). Item 6 of the deferred-gaps plan. App surface — no CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR5 made the (protected) and (public) respondent pages call
isAttachmentInputEnabled(), but their vi.mock of @/lib/app/questionnaire/
feature-flag didn't export it — 18 page tests failed at runtime ("No
isAttachmentInputEnabled export is defined on the mock"). Caught by /pre-pr's
full-suite run. Follow-up to the attachment-input PR; test-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ce, tests) Follow-ups from the /code-review + /test-review gate pass on this branch. C — refinement now updates confidence: applyRefinement carries decision.confidence onto RefinedSlotState and persistRefinement writes it, so a live (and preview) refinement can improve a low-confidence capture instead of freezing the original score (code-review #3). The next turn's coverage + refiner context see the updated value. B — chat composer rewired to the platform attachment primitives: replaces the hand-rolled file handlers in questionnaire-chat.tsx with <AttachmentPickerButton> + useAttachments (code-review #1). This fixes the mismatched 5MB-raw cap vs the server's base64 cap, adds the missing combined-size guard, gains object-URL cleanup + paste support, and removes the duplicate handlers/types — MessageAttachment is now an alias of the platform ChatAttachment. A — mechanical: restore the displaced TurnState JSDoc (code-review #8); add invite-form submit-path tests (validation, accept error, success + claim redirect), the accept non-ProviderError attachment-gate arm, the name-fallback arm, success===false on the invitation error-envelope assertions, and attachmentInputEnabled propagation to both respondent page-test stubs (test-review findings). App surface — no CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the deferred-item gaps surfaced by a 2026-06-07 audit of
development-plan.mdagainst the merged code (PRs #10–#48): five items that were deferred to a home feature which has since shipped but were never actually delivered — the same shape as the admin upload-UI (#48) that had fallen between features with no owner. Also delivers a demo-client branding preview in the admin UI and refreshes the stale plan header.Everything is app surface (
lib/app/questionnaire/**,app/api/v1/app/**, app pages/components) — no Sunrise platform-surface change, so perVERSIONING.mdthere's deliberately no CHANGELOG entry.What's in here (one commit per item)
DemoClientThemePreview(reusesresolveTheme()+ the escaped--app-logo-urlbackground fromBrandThemeProvider) shows colour swatches + logo on the demo-clients list (compact, configured fields only), the detail page (full resolved brand), and live in the edit form. Plus the staledevelopment-plan.mdheader refresh (P6/P7 were merged but read "not started") + the audit decisions-log entry.refinementHistoryappend — the live turn loop now takes the full F4.4 path (loadAnswerSlot→applyRefinement→persistRefinement), so real sessions keep the provenance trail (and confidence — see gate fixes) instead of dropping it. P8 analytics/exports read this.signInEmail,401 INVALID_CREDENTIALSon a wrong password, bound only after a verified sign-in) instead of hitting a409 ACCOUNT_EXISTSdead-end.metadatareportsaccountExiststo drive the form.POST …/clone-for-client+ a detail-page dialog duplicate a questionnaire's current version into a new draft attributed to another demo client. Extracts the version deep-copy into a shared_lib/copy-version-graph.ts, single-sourced with the F2.1 version-fork.assertModelSupportsAttachmentsgate), threaded through/messages+ the orchestrator; the chat composer gains a flag-gated attachment affordance built on the platformuseAttachments+<AttachmentPickerButton>. New dark-launch sub-flagAPP_QUESTIONNAIRES_ATTACHMENT_INPUT_ENABLED(seed 024, off).every_n_turnscontradiction cadence — additivecontradictionEveryNTurnsconfig column + migration;shouldRunDetectiongains a cadence arg the orchestrator drives off the turn index; config-editor field.Migration
20260607120000_add_config_contradiction_cadence— a plainADD COLUMN contradictionEveryNTurns INTEGER NOT NULL DEFAULT 1(no pgvector DDL). Applied viadb:migrate:deploy+db:drift-check(11/11 probes pass).Quality gates (all run on this branch)
/pre-pr— type-check / lint / format / drift clean; caught + fixed 18 page-test mock failures./security-review— no vulnerabilities (invite-claim, clone, attachments, logo-CSS, flag-gating reviewed)./test-review— 19 findings ≥80 (.reviews/tests-branch-close-deferred-gaps.md); actionable ones fixed./code-review high— 10 findings; top fixes folded in (see below).Gate follow-ups applied
applyRefinement/persistRefinementnow carrydecision.confidence, so refining can improve a low-confidence capture rather than freezing the original score.useAttachmentshook — fixes a mismatched per-file cap and a missing combined-size guard, and removes duplicated handlers/types.Test status
Full suite green: 1144 files, 21,926 passed, 0 failed.
🤖 Generated with Claude Code