Skip to content

feat(publish): guided publish flow in the case editor (ADR 0003) - #887

Merged
chrisdburr merged 3 commits into
stagingfrom
feat/publish-flow
Aug 11, 2026
Merged

chrisdburr merged 3 commits into
stagingfrom
feat/publish-flow

Conversation

@chrisdburr

Copy link
Copy Markdown
Collaborator

Summary

Wires the complete publish flow into the case editor (ADR 0003 items 2–3):

  • Publish: single guided action — validates case-information completeness (required fields: description, authors, sector), surfaces exactly the missing fields and opens the case-information pane focused on them, single confirm, snapshot via publishAssuranceCase.
  • Republish: divergence indicator using existing change-detection; "Update Published" re-snapshots atomically; completeness re-validated on republish (and on the raw PATCH /status first-publish path).
  • Unpublish: plain-consequences confirm; removes the public record.
  • Performance: the status dialog opens immediately — first paint no longer gated on the synchronous export + change-detection round trip.
  • Removed: orphaned publish-modal.tsx / use-publish-modal.ts and the READY_TO_PUBLISH placeholder content.

Review chain

  • Implementation + amendment rounds with full suites green (unit 1242, integration 902 effective; the known publishing-schema-migration afterAll-timeout flake passes in isolation).
  • QA pass: coverage mapped criterion-by-criterion; findings addressed (raw-API completeness gap closed with regression test; divergence indicator directly asserted).
  • Code review: approved; security verified (EDIT enforced at the service layer on all mutations, anti-enumeration responses intact); duplicated validation and missing-fields UI extracted (requireCaseInformationComplete, MissingFieldsGate). Fallow audit clean against baselines.
  • Post-review delta independently re-verified against the review prescriptions.

… 0003)

Builds the complete publish/republish/unpublish flow on top of the
previously orphaned publish-service backend:

- Publish: validates case-information completeness (currently just a
  required description) before a single-confirm publish via the existing
  publishAssuranceCase/POST /api/cases/[id]/publish. Incomplete cases get
  the exact missing fields surfaced in place, with a shortcut into the
  existing case-information pane focused on the gap.
- Republish/unpublish: reuses the existing PATCH /api/cases/[id]/status
  transitions; unpublish now has a plain-consequences confirm step.
- Fixes the case editor's status button to open the dialog immediately
  from already-known state, instead of awaiting the synchronous
  export+change-detection GET /api/cases/[id]/status before opening.
- Adds a case-information completeness gate (lib/schemas/case-information,
  case-information-service, the publish route) as defence in depth behind
  the client-side check.
- Deletes the dead publish-modal.tsx/use-publish-modal.ts pair this
  replaces and the placeholder Draft status-modal content.

Unit and integration tests cover the completeness gate, the publish/
unpublish confirm flows, and the header's no-longer-synchronous open.
…republish (ADR 0003)

Chris's ruling: the publish-readiness gate now requires description, authors
AND sector (previously description-only), widening
REQUIRED_CASE_INFORMATION_FIELDS and the associated labels/UI copy.

Lead adjudication: republish (PATCH /api/cases/[id]/status PUBLISHED ->
PUBLISHED, the "Update Published" action) now re-runs the same completeness
gate as first publish, so a published record can't regress to incomplete via
an edit that clears a required field followed by a republish. The status
modal's Published view surfaces the same missing-fields gate as the Draft
flow rather than a raw error.
Post-review fixes (QA + code review, 2026-08-11):

- PATCH /api/cases/[id]/status now runs the case-information completeness
  gate for first publish (DRAFT -> PUBLISHED), not just republish — a direct
  PATCH used to bypass it entirely, since the check only ran
  `if (isRepublish)`.
- Extract the duplicated completeness-check/fieldErrors/validation-error
  block from publish/route.ts and status/route.ts into a shared
  `requireCaseInformationComplete` helper in case-information-service.ts.
- Extract the duplicated missing-fields gate JSX (PublishContent /
  PublishedContent) into a shared `<MissingFieldsGate>` component.
- Add a direct assertion for the "changes since last published" divergence
  Alert (previously only the button was asserted).
@github-actions

Copy link
Copy Markdown

Fallow combined report

GitHub PR summary, scope: project

Important

Quality gates need attention. Found 44 findings.

Checks

Area Status Result Threshold Details
Dead code warn 8 issues configured rules
Duplication warn 7 clone groups n/a 0.3% duplicated lines
Health fail 2 findings configured complexity gates
Top fixes
Severity Fix Location Why
minor Extract the repeated block or centralize shared logic. app/api/cases/%5Bid%5D/permissions/%5BpermissionId%5D/route.ts:31 Code clone group 1 (17 lines, 10 instances)
minor Extract the repeated block or centralize shared logic. app/api/cases/%5Bid%5D/permissions/%5BpermissionId%5D/route.ts:31 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/cases/%5Bid%5D/publish/route.ts:72 Code clone group 3 (15 lines, 3 instances)
minor Extract the repeated block or centralize shared logic. app/api/cases/%5Bid%5D/publish/route.ts:97 Code clone group 4 (14 lines, 2 instances)
minor Extract the repeated block or centralize shared logic. app/api/cases/%5Bid%5D/publish/route.ts:138 Code clone group 4 (14 lines, 2 instances)
major Fix the import path or install the missing package. app/api/cases/%5Bid%5D/status/route.ts:16 Import '@/src/generated/prisma' could not be resolved
minor Extract the repeated block or centralize shared logic. app/api/cases/%5Bid%5D/status/route.ts:39 Code clone group 6 (43 lines, 3 instances)
minor Extract the repeated block or centralize shared logic. app/api/cases/%5Bid%5D/status/route.ts:39 Code clone group 5 (44 lines, 2 instances)
minor Extract the repeated block or centralize shared logic. app/api/cases/%5Bid%5D/status/route.ts:84 Code clone group 1 (17 lines, 10 instances)
minor Extract the repeated block or centralize shared logic. app/api/cases/%5Bid%5D/status/route.ts:84 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/comments/%5Bid%5D/route.ts:58 Code clone group 6 (43 lines, 3 instances)
minor Extract the repeated block or centralize shared logic. app/api/elements/%5Bid%5D/route.ts:87 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/elements/%5Bid%5D/route.ts:87 Code clone group 1 (17 lines, 10 instances)
minor Extract the repeated block or centralize shared logic. app/api/integrations/%5Bid%5D/case-grants/route.ts:108 Code clone group 3 (15 lines, 3 instances)
minor Extract the repeated block or centralize shared logic. app/api/integrations/%5Bid%5D/route.ts:48 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/integrations/%5Bid%5D/route.ts:48 Code clone group 1 (17 lines, 10 instances)
minor Extract the repeated block or centralize shared logic. app/api/integrations/%5Bid%5D/tokens/route.ts:49 Code clone group 3 (15 lines, 3 instances)
minor Extract the repeated block or centralize shared logic. app/api/integrations/route.ts:70 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/integrations/route.ts:70 Code clone group 1 (17 lines, 10 instances)
minor Extract the repeated block or centralize shared logic. app/api/machine/health/elements/%5Bid%5D/evidence/route.ts:118 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/teams/%5Bid%5D/members/route.ts:51 Code clone group 1 (17 lines, 10 instances)
minor Extract the repeated block or centralize shared logic. app/api/teams/%5Bid%5D/members/route.ts:51 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/teams/%5Bid%5D/route.ts:26 Code clone group 5 (44 lines, 2 instances)
minor Extract the repeated block or centralize shared logic. app/api/teams/%5Bid%5D/route.ts:26 Code clone group 6 (43 lines, 3 instances)
minor Extract the repeated block or centralize shared logic. app/api/teams/%5Bid%5D/route.ts:48 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/teams/%5Bid%5D/route.ts:48 Code clone group 1 (17 lines, 10 instances)
minor Extract the repeated block or centralize shared logic. app/api/teams/route.ts:40 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/teams/route.ts:40 Code clone group 1 (17 lines, 10 instances)
minor Extract the repeated block or centralize shared logic. app/api/users/me/route.ts:44 Code clone group 1 (17 lines, 10 instances)
minor Extract the repeated block or centralize shared logic. app/api/users/me/route.ts:44 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/users/register/route.ts:23 Code clone group 2 (10 lines, 11 instances)
minor Extract the repeated block or centralize shared logic. app/api/users/register/route.ts:23 Code clone group 1 (17 lines, 10 instances)
minor Split branches or add focused tests around the risky path. components/cases/header.tsx:22 'Header' has CRAP score 37.1 (threshold: 30.0, cyclomatic 11)
major Split branches or add focused tests around the risky path. components/publishing/status-modal.tsx:311 'PublishedContent' has CRAP score 79.4 (threshold: 30.0, cyclomatic 17)
major Remove the export or mark it public if it is part of the API. lib/schemas/case-information.ts:12 Export 'caseInformationSchema' is never imported by other modules
major fallow/duplicate-export lib/schemas/case-information.ts:45 Export 'CaseInformationInput' appears in multiple modules
major fallow/unused-type lib/schemas/publish.ts:15 Type export 'PublishCaseBodyInput' is never imported by other modules
major fallow/unused-type lib/schemas/publish.ts:16 Type export 'PublishCaseBodyOutput' is never imported by other modules
major Fix the import path or install the missing package. lib/services/case-information-service.ts:8 Import '@/src/generated/prisma' could not be resolved
major fallow/duplicate-export lib/services/case-information-service.ts:33 Export 'CaseInformationInput' appears in multiple modules
minor fallow/dev-dependency-in-production package.json:134 devDependency 'tailwindcss' is imported by production code at runtime (consider moving to dependencies)
major fallow/unused-type store/store.ts:99 Type export 'NodeData' is never imported by other modules
minor Extract the repeated block or centralize shared logic. store/store.ts:214 Code clone group 7 (8 lines, 2 instances)
minor Extract the repeated block or centralize shared logic. store/store.ts:227 Code clone group 7 (8 lines, 2 instances)

Generated by fallow.

@chrisdburr
chrisdburr merged commit 0ff7167 into staging Aug 11, 2026
5 checks passed
@chrisdburr
chrisdburr deleted the feat/publish-flow branch August 11, 2026 12:36
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