diff --git a/app/(app)/create/CreateFlowLayoutClient.tsx b/app/(app)/create/CreateFlowLayoutClient.tsx index 9230b57..0499c88 100644 --- a/app/(app)/create/CreateFlowLayoutClient.tsx +++ b/app/(app)/create/CreateFlowLayoutClient.tsx @@ -593,7 +593,7 @@ function CreateFlowLayoutContent({ key: "publish", status: "danger" as const, title: - messages.create.reviewAndComplete.publish.finalizeBannerTitle, + messages.create.reviewAndComplete.publish.publishBannerTitle, description: publishBannerMessage, onClose: () => setPublishBannerMessage(null), } @@ -956,8 +956,8 @@ function CreateFlowLayoutContent({ {isFinalReviewLike ? isPublishing ? messages.create.reviewAndComplete.publish - .finalizeButtonPublishing - : footer.finalizeCommunityRule + .publishButtonPublishing + : footer.publishCommunityRule : getDefaultFooterLabel(currentStep, footer)} ) : null diff --git a/app/(app)/create/hooks/useCreateFlowFinalize.ts b/app/(app)/create/hooks/useCreateFlowFinalize.ts index 849d4a0..52a007c 100644 --- a/app/(app)/create/hooks/useCreateFlowFinalize.ts +++ b/app/(app)/create/hooks/useCreateFlowFinalize.ts @@ -47,7 +47,7 @@ export function useCreateFlowFinalize({ /** Session gate return path (`?syncDraft=1`) — differs for `/create/edit-rule` vs `/create/final-review`. */ loginReturnPath: string; /** - * `undefined` while `/api/auth/session` is in flight — finalize is a no-op + * `undefined` while `/api/auth/session` is in flight — publish is a no-op * until it resolves. `null` is a guest (anonymous publish). */ sessionUser: SessionUser | null | undefined; diff --git a/app/(app)/create/types.ts b/app/(app)/create/types.ts index 56f4798..8ac929e 100644 --- a/app/(app)/create/types.ts +++ b/app/(app)/create/types.ts @@ -222,7 +222,7 @@ export interface CreateFlowState { */ templateReviewEntryFromCreateFlow?: boolean; /** - * When set, **Finalize** and signed-in **Save & Exit** update this published + * When set, **Publish** and signed-in **Save & Exit** update this published * rule (PATCH) instead of POSTing a new rule or only saving a draft. */ editingPublishedRuleId?: string; diff --git a/app/(app)/create/utils/createFlowFooterLabels.ts b/app/(app)/create/utils/createFlowFooterLabels.ts index be3a82a..2d045b7 100644 --- a/app/(app)/create/utils/createFlowFooterLabels.ts +++ b/app/(app)/create/utils/createFlowFooterLabels.ts @@ -10,8 +10,8 @@ type FooterMessages = typeof footerMessages; * from this map fall back to `footer.next`. * * `final-review` is handled separately by the caller because its label - * also depends on the in-flight publish flag (`finalizeButtonPublishing` - * vs `finalizeCommunityRule`). + * also depends on the in-flight publish flag (`publishButtonPublishing` + * vs `publishCommunityRule`). */ const DEFAULT_FOOTER_LABEL_BY_STEP: ReadonlyMap< CreateFlowStep, diff --git a/app/(app)/create/utils/flowSteps.ts b/app/(app)/create/utils/flowSteps.ts index 2251e1d..ac476d0 100644 --- a/app/(app)/create/utils/flowSteps.ts +++ b/app/(app)/create/utils/flowSteps.ts @@ -202,7 +202,7 @@ export const TEMPLATE_REVIEW_FROM_CREATE_FLOW_VALUE = "1" as const; export const TEMPLATES_FACET_RECOMMEND_QUERY = "recommendTemplates" as const; export const TEMPLATES_FACET_RECOMMEND_VALUE = "1" as const; -/** `/create/completed?celebrate=1` — post-finalize toast; set only after **initial** POST publish, not PATCH updates. */ +/** `/create/completed?celebrate=1` — post-publish toast; set only after **initial** POST publish, not PATCH updates. */ export const CREATE_FLOW_COMPLETED_CELEBRATE_QUERY = "celebrate" as const; export const CREATE_FLOW_COMPLETED_CELEBRATE_VALUE = "1" as const; diff --git a/docs/create-flow.md b/docs/create-flow.md index a5498bf..31e2880 100644 --- a/docs/create-flow.md +++ b/docs/create-flow.md @@ -105,8 +105,8 @@ Only one `?fromFlow=1` marker exists, on one hop (`/create/review` → `/templat | Mode | Where progress lives | Save & Exit / publish | | --- | --- | --- | -| **Anonymous** | `localStorage` key **`create-flow-anonymous`** | **Save & Exit** (from `community-structure` onward, plus `edit-rule`) opens the save-progress magic-link modal; after verify, optional **PUT** `/api/drafts/me` when `NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true` (see Tickets 4–5 in [guides/backend-linear-tickets.md](guides/backend-linear-tickets.md)). **Finalize** `POST`s `/api/rules` without a session (`userId` null) and sets httpOnly **`cr_rule_claim`**. Landing on **`/create/completed`** opens a **keep-this-rule** magic-link modal (no draft transfer); the top nav stays **Save & Exit** so they can reopen it (**Continue without saving** from that control leaves the flow). After Finalize, skip on the auto-opened modal stays on completed. **Edit** shows when a last-published rule is in session storage. The public URL works; the row is omitted from `GET /api/rules` until claimed. Signing in on the same browser attaches `userId` (profile, edit, invites). | -| **Signed-in** | In-memory React state in **`CreateFlowContext`** | **Save & Exit** from the **`community-structure`** step onward (and `edit-rule`) may **PUT** `/api/drafts/me` when sync is on. **Completed** is **Exit**. **Finalize** stores the rule with **`userId`**. **Sign out** is on profile, not in the create top nav. | +| **Anonymous** | `localStorage` key **`create-flow-anonymous`** | **Save & Exit** (from `community-structure` onward, plus `edit-rule`) opens the save-progress magic-link modal; after verify, optional **PUT** `/api/drafts/me` when `NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true` (see Tickets 4–5 in [guides/backend-linear-tickets.md](guides/backend-linear-tickets.md)). **Publish** `POST`s `/api/rules` without a session (`userId` null) and sets httpOnly **`cr_rule_claim`**. Landing on **`/create/completed`** opens a **keep-this-rule** magic-link modal (no draft transfer); the top nav stays **Save & Exit** so they can reopen it (**Continue without saving** from that control leaves the flow). After Publish, skip on the auto-opened modal stays on completed. **Edit** shows when a last-published rule is in session storage. The public URL works; the row is omitted from `GET /api/rules` until claimed. Signing in on the same browser attaches `userId` (profile, edit, invites). | +| **Signed-in** | In-memory React state in **`CreateFlowContext`** | **Save & Exit** from the **`community-structure`** step onward (and `edit-rule`) may **PUT** `/api/drafts/me` when sync is on. **Completed** is **Exit**. **Publish** stores the rule with **`userId`**. **Sign out** is on profile, not in the create top nav. | Details and edge cases (conflict confirm, banners, `?syncDraft=1`) match **Ticket 4**, **Ticket 5**, and [`docs/guides/backend-roadmap.md`](guides/backend-roadmap.md) §12. diff --git a/docs/guides/backend-linear-tickets.md b/docs/guides/backend-linear-tickets.md index 61440c4..c982d18 100644 --- a/docs/guides/backend-linear-tickets.md +++ b/docs/guides/backend-linear-tickets.md @@ -218,19 +218,19 @@ Optional: **Docker image deploy** using the repo [Dockerfile](Dockerfile)—admi **Goal:** Completing the flow persists a **PublishedRule** via existing [publishRule](lib/create/api.ts). -**Context:** [lib/create/api.ts](lib/create/api.ts) wraps `POST /api/rules` with Zod-validated body (Ticket 2). Finalize flows through [useCreateFlowFinalize](app/(app)/create/hooks/useCreateFlowFinalize.ts) from [CreateFlowLayoutClient](app/(app)/create/CreateFlowLayoutClient.tsx) (`final-review` → `publishRule` → `/create/completed`). +**Context:** [lib/create/api.ts](lib/create/api.ts) wraps `POST /api/rules` with Zod-validated body (Ticket 2). Publish flows through [useCreateFlowFinalize](app/(app)/create/hooks/useCreateFlowFinalize.ts) from [CreateFlowLayoutClient](app/(app)/create/CreateFlowLayoutClient.tsx) (`final-review` → `publishRule` → `/create/completed`). **Implementation (shipped):** 1. Map `CreateFlowState` → `title` / `summary` / `document` via [buildPublishPayload](lib/create/buildPublishPayload.ts) (and related builders). -2. Call `publishRule` on explicit **Finalize** from `final-review` ([useCreateFlowFinalize](app/(app)/create/hooks/useCreateFlowFinalize.ts)). +2. Call `publishRule` on explicit **Publish** from `final-review` ([useCreateFlowFinalize](app/(app)/create/hooks/useCreateFlowFinalize.ts)). 3. **401** → `openLogin` with return path (Ticket 3 / `AuthModalProvider`). 4. Success: navigate to `completed` with rule id in query string. **Acceptance criteria:** - [x] Published row appears in Postgres (`PublishedRule`) and `GET /api/rules` lists it. -- [x] User sees clear success/failure (banner / flow state; see finalize hook). +- [x] User sees clear success/failure (banner / flow state; see publish hook). **Files:** [app/(app)/create/hooks/useCreateFlowFinalize.ts](app/(app)/create/hooks/useCreateFlowFinalize.ts), [CreateFlowLayoutClient.tsx](app/(app)/create/CreateFlowLayoutClient.tsx), [app/api/rules/route.ts](app/api/rules/route.ts), [lib/create/api.ts](lib/create/api.ts), [lib/create/buildPublishPayload.ts](lib/create/buildPublishPayload.ts). diff --git a/lib/create/pendingKeepRuleLogin.ts b/lib/create/pendingKeepRuleLogin.ts index 145461d..9ca652c 100644 --- a/lib/create/pendingKeepRuleLogin.ts +++ b/lib/create/pendingKeepRuleLogin.ts @@ -1,7 +1,7 @@ /** - * Guest finalize → `/create/completed` keep-this-rule login. Stored in + * Guest publish → `/create/completed` keep-this-rule login. Stored in * sessionStorage so the prompt survives create-layout remounts and is not - * tied to the Finalize click (that click would otherwise dismiss the overlay). + * tied to the Publish click (that click would otherwise dismiss the overlay). */ export const CREATE_FLOW_PENDING_KEEP_RULE_LOGIN_KEY = "createFlow.pendingKeepRuleLogin"; diff --git a/messages/en/create/footer.json b/messages/en/create/footer.json index bb9573d..2f228e0 100644 --- a/messages/en/create/footer.json +++ b/messages/en/create/footer.json @@ -9,7 +9,7 @@ "confirmDetails": "Confirm details", "confirmDescription": "Confirm description", "confirmMembers": "Confirm members", - "finalizeCommunityRule": "Finalize CommunityRule", + "publishCommunityRule": "Publish CommunityRule", "confirmStakeholders": "Confirm Stakeholders", "confirmCoreValues": "Confirm values", "confirmCommunication": "Confirm", diff --git a/messages/en/create/reviewAndComplete/finalReview.json b/messages/en/create/reviewAndComplete/finalReview.json index 460378c..6a9deae 100644 --- a/messages/en/create/reviewAndComplete/finalReview.json +++ b/messages/en/create/reviewAndComplete/finalReview.json @@ -1,8 +1,8 @@ { "title": "Review your CommunityRule", - "description": "Here's what other people will see. Make sure everything looks good before you finalize everything. Once the rule is finalized, you must use one of your decision-making mechanisms to edit it again.", + "description": "Here's what other people will see. Make sure everything looks good before you publish. Once the rule is published, you must use one of your decision-making mechanisms to edit it again.", "editPublishedTitle": "Edit your CommunityRule", - "editPublishedDescription": "Update what others see on your public rule. Save & Exit or Finalize applies changes to your published CommunityRule.", + "editPublishedDescription": "Update what others see on your public rule. Save & Exit or Publish applies changes to your published CommunityRule.", "ruleCardTitleFallback": "Your community", "chipEditModal": { "saveButton": "Save", diff --git a/messages/en/create/reviewAndComplete/publish.json b/messages/en/create/reviewAndComplete/publish.json index f428743..34362e9 100644 --- a/messages/en/create/reviewAndComplete/publish.json +++ b/messages/en/create/reviewAndComplete/publish.json @@ -1,6 +1,6 @@ { - "finalizeBannerTitle": "Couldn't publish", - "missingCommunityName": "Add a community name before finalizing.", - "finalizeButtonPublishing": "Publishing…", + "publishBannerTitle": "Couldn't publish", + "missingCommunityName": "Add a community name before publishing.", + "publishButtonPublishing": "Publishing…", "genericPublishFailed": "Something went wrong. Try again." } diff --git a/stories/pages/FinalReviewPage.stories.js b/stories/pages/FinalReviewPage.stories.js index 03a0e03..2019c2b 100644 --- a/stories/pages/FinalReviewPage.stories.js +++ b/stories/pages/FinalReviewPage.stories.js @@ -8,7 +8,7 @@ export default { docs: { description: { component: - "Pre-finalize review: HeaderLockup + expanded Rule sections.", + "Pre-publish review: HeaderLockup + expanded Rule sections.", }, }, }, diff --git a/tests/components/CompletedPage.test.tsx b/tests/components/CompletedPage.test.tsx index 710ef06..aefdff6 100644 --- a/tests/components/CompletedPage.test.tsx +++ b/tests/components/CompletedPage.test.tsx @@ -84,7 +84,7 @@ describe("CompletedScreen", () => { expect(screen.getByText("Fixture value title")).toBeInTheDocument(); }); - it("does not show post-finalize toast without celebrate query", () => { + it("does not show post-publish toast without celebrate query", () => { render(); expect( screen.queryByText( @@ -98,7 +98,7 @@ describe("CompletedScreen", () => { ).not.toBeInTheDocument(); }); - it("shows post-finalize toast in status region when celebrate query is set", () => { + it("shows post-publish toast in status region when celebrate query is set", () => { mockSearchParams({ [CREATE_FLOW_COMPLETED_CELEBRATE_QUERY]: CREATE_FLOW_COMPLETED_CELEBRATE_VALUE, diff --git a/tests/components/FinalReviewPage.test.tsx b/tests/components/FinalReviewPage.test.tsx index 178e3f8..1b64789 100644 --- a/tests/components/FinalReviewPage.test.tsx +++ b/tests/components/FinalReviewPage.test.tsx @@ -79,7 +79,7 @@ describe("FinalReviewScreen", () => { render(); expect( screen.getByText( - /Here's what other people will see. Make sure everything looks good before you finalize everything. Once the rule is finalized, you must use one of your decision-making mechanisms to edit it again./i, + /Here's what other people will see. Make sure everything looks good before you publish. Once the rule is published, you must use one of your decision-making mechanisms to edit it again./i, ), ).toBeInTheDocument(); }); @@ -1004,6 +1004,18 @@ function FinalReviewEditPublishedWithStateProbe({ } describe("FinalReviewScreen — edit published title and description", () => { + it("renders edit-published lockup copy", () => { + render(); + expect( + screen.getByRole("heading", { name: "Edit your CommunityRule" }), + ).toBeInTheDocument(); + expect( + screen.getByText( + /Update what others see on your public rule. Save & Exit or Publish applies changes to your published CommunityRule./i, + ), + ).toBeInTheDocument(); + }); + it("does not expose click-to-edit title or description on default final review", () => { render( { it("exposes confirmMembers for the community-size footer CTA", () => { expect(messages.create.footer.confirmMembers).toBe("Confirm members"); }); + + it("exposes publishCommunityRule for the final-review footer CTA", () => { + expect(messages.create.footer.publishCommunityRule).toBe( + "Publish CommunityRule", + ); + }); }); diff --git a/tests/unit/hooks/useCreateFlowFinalize.test.tsx b/tests/unit/hooks/useCreateFlowFinalize.test.tsx index 965d505..7a18b7f 100644 --- a/tests/unit/hooks/useCreateFlowFinalize.test.tsx +++ b/tests/unit/hooks/useCreateFlowFinalize.test.tsx @@ -258,7 +258,7 @@ describe("useCreateFlowFinalize", () => { }); }); - it("does not PATCH when a guest finalizes an already-published rule", async () => { + it("does not PATCH when a guest publishes an already-published rule", async () => { const { result } = renderHook(() => useCreateFlowFinalize({ state: {