Skip to content

feat(app): close deferred-item gaps + demo-client branding preview#49

Merged
JohnD-EE merged 8 commits into
mainfrom
feat/close-deferred-gaps
Jun 7, 2026
Merged

feat(app): close deferred-item gaps + demo-client branding preview#49
JohnD-EE merged 8 commits into
mainfrom
feat/close-deferred-gaps

Conversation

@JohnD-EE

@JohnD-EE JohnD-EE commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the deferred-item gaps surfaced by a 2026-06-07 audit of development-plan.md against 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 per VERSIONING.md there's deliberately no CHANGELOG entry.

What's in here (one commit per item)

Item Change
0 + 1 Demo-client branding preview — new DemoClientThemePreview (reuses resolveTheme() + the escaped --app-logo-url background from BrandThemeProvider) 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 stale development-plan.md header refresh (P6/P7 were merged but read "not started") + the audit decisions-log entry.
2 Live refinementHistory append — the live turn loop now takes the full F4.4 path (loadAnswerSlotapplyRefinementpersistRefinement), so real sessions keep the provenance trail (and confidence — see gate fixes) instead of dropping it. P8 analytics/exports read this.
3 Invite claim-via-existing-login — an invited email that already has an account now claims the invitation by signing in (verified via signInEmail, 401 INVALID_CREDENTIALS on a wrong password, bound only after a verified sign-in) instead of hitting a 409 ACCOUNT_EXISTS dead-end. metadata reports accountExists to drive the form.
4 Clone-for-client (DEMO-ONLY) — 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.
5 Attachment input — the answer-extractor capability accepts content parts (with an assertModelSupportsAttachments gate), threaded through /messages + the orchestrator; the chat composer gains a flag-gated attachment affordance built on the platform useAttachments + <AttachmentPickerButton>. New dark-launch sub-flag APP_QUESTIONNAIRES_ATTACHMENT_INPUT_ENABLED (seed 024, off).
6 every_n_turns contradiction cadence — additive contradictionEveryNTurns config column + migration; shouldRunDetection gains a cadence arg the orchestrator drives off the turn index; config-editor field.

Migration

20260607120000_add_config_contradiction_cadence — a plain ADD COLUMN contradictionEveryNTurns INTEGER NOT NULL DEFAULT 1 (no pgvector DDL). Applied via db: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

  • Refinement updates confidenceapplyRefinement/persistRefinement now carry decision.confidence, so refining can improve a low-confidence capture rather than freezing the original score.
  • Chat attachments rewired to the platform useAttachments hook — fixes a mismatched per-file cap and a missing combined-size guard, and removes duplicated handlers/types.
  • Mechanical: restored a displaced JSDoc; added invite-form submit-path tests, the attachment-gate error arm, error-envelope assertions, and page-stub propagation tests.

Test status

Full suite green: 1144 files, 21,926 passed, 0 failed.

🤖 Generated with Claude Code

JohnD-EE and others added 8 commits June 7, 2026 17:13
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>
@JohnD-EE JohnD-EE merged commit bb6ee4c into main Jun 7, 2026
14 checks passed
@JohnD-EE JohnD-EE deleted the feat/close-deferred-gaps branch June 7, 2026 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant