From aac0c603c2a1fb71116d9c8cd39aaa75ef3592c7 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sun, 15 Feb 2026 21:18:21 +0800 Subject: [PATCH 1/3] chore: pr comments --- .../-components/content-display.tsx | 30 +++---- .../manage-content-metadata-dialog.tsx | 7 +- packages/api-seo/src/routes/content.ts | 1 + .../src/workflows/onboarding-workflow.ts | 78 +++++++++---------- .../api-seo/src/workflows/planner-workflow.ts | 2 +- 5 files changed, 52 insertions(+), 66 deletions(-) diff --git a/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/-components/content-display.tsx b/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/-components/content-display.tsx index d54aee8e..ed338fc5 100644 --- a/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/-components/content-display.tsx +++ b/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/-components/content-display.tsx @@ -36,7 +36,7 @@ import { TabsTrigger, } from "@rectangular-labs/ui/components/ui/tabs"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { type ReactNode, useEffect, useRef, useState } from "react"; +import { type ReactNode, useEffect, useState } from "react"; import { getApiClientRq } from "~/lib/api"; import { LoadingError } from "~/routes/_authed/-components/loading-error"; import { ManageContentMetadataDialog } from "./manage-content-metadata-dialog"; @@ -147,8 +147,6 @@ export function useContentDisplayController({ { status: "idle" } | { status: "saving" } | { status: "saved"; at: string } >({ status: "idle" }); const [lastSavedContentMarkdown, setLastSavedContentMarkdown] = useState(""); - const latestSaveVersionRef = useRef(0); - const latestAppliedSaveVersionRef = useRef(0); const [draftDetails, setDraftDetails] = useState({ contentMarkdown: "", @@ -163,11 +161,8 @@ export function useContentDisplayController({ await Promise.all([ contentDetailsQuery.refetch(), queryClient.invalidateQueries({ - queryKey: api.content.list.queryKey({ - input: { - organizationIdentifier, - projectId, - }, + queryKey: api.content.list.key({ + type: "query", }), }), ]); @@ -197,19 +192,16 @@ export function useContentDisplayController({ status: "saved", at: new Date(draft.updatedAt).toISOString(), }); - latestSaveVersionRef.current = 0; - latestAppliedSaveVersionRef.current = 0; }, [draft]); useEffect(() => { if (!draft || !canEdit) return; - if (draftDetails.contentMarkdown === lastSavedContentMarkdown) return; + const markdownToSave = draftDetails.contentMarkdown; + if (markdownToSave === lastSavedContentMarkdown) return; + let cancelled = false; setContentSaveIndicator({ status: "saving" }); const timeoutId = window.setTimeout(() => { - const saveVersion = ++latestSaveVersionRef.current; - const markdownToSave = draftDetails.contentMarkdown; - void saveContentMarkdownAsync({ organizationIdentifier, projectId, @@ -217,8 +209,7 @@ export function useContentDisplayController({ contentMarkdown: markdownToSave, }) .then(() => { - if (saveVersion < latestAppliedSaveVersionRef.current) return; - latestAppliedSaveVersionRef.current = saveVersion; + if (cancelled) return; setLastSavedContentMarkdown(markdownToSave); setContentSaveIndicator({ status: "saved", @@ -226,12 +217,15 @@ export function useContentDisplayController({ }); }) .catch(() => { - if (saveVersion < latestAppliedSaveVersionRef.current) return; + if (cancelled) return; setContentSaveIndicator({ status: "idle" }); }); }, 800); - return () => window.clearTimeout(timeoutId); + return () => { + cancelled = true; + window.clearTimeout(timeoutId); + }; }, [ canEdit, draft, diff --git a/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/-components/manage-content-metadata-dialog.tsx b/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/-components/manage-content-metadata-dialog.tsx index 67722983..42dd5b51 100644 --- a/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/-components/manage-content-metadata-dialog.tsx +++ b/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/-components/manage-content-metadata-dialog.tsx @@ -119,11 +119,8 @@ export function ManageContentMetadataDialog({ onOpenChange(false); await Promise.all([ queryClient.invalidateQueries({ - queryKey: api.content.list.queryKey({ - input: { - organizationIdentifier, - projectId, - }, + queryKey: api.content.list.key({ + type: "query", }), }), draftId diff --git a/packages/api-seo/src/routes/content.ts b/packages/api-seo/src/routes/content.ts index 9a48f163..d11e0dbe 100644 --- a/packages/api-seo/src/routes/content.ts +++ b/packages/api-seo/src/routes/content.ts @@ -74,6 +74,7 @@ const list = withOrganizationIdBase db: context.db, organizationId: context.organization.id, projectId: input.projectId, + strategyId: input.strategyId, }); if (!rowsResult.ok) { throw new ORPCError("INTERNAL_SERVER_ERROR", { diff --git a/packages/api-seo/src/workflows/onboarding-workflow.ts b/packages/api-seo/src/workflows/onboarding-workflow.ts index 464885bc..6312080c 100644 --- a/packages/api-seo/src/workflows/onboarding-workflow.ts +++ b/packages/api-seo/src/workflows/onboarding-workflow.ts @@ -91,17 +91,14 @@ export class SeoOnboardingWorkflow extends WorkflowEntrypoint< } logInfo("homepage title", { homepageTitle }); - const system = `You extract a company or product name from a homepage title. + const system = `You extract a company or product name from a homepage title/website url. ## Task - Analyze the provided homepage title and URL to derive the entity name. ## Guidelines -- Return the homepage title exactly as provided in the input. -- Extract the brand or product name from the title; if unclear, return an empty string. - -## Expectations -- Provide concise, exact values without extra commentary.`; +- use the homepage title and URL to derive the entity name. +- If unclear, return an empty string.`; const { experimental_output } = await generateText({ model: google("gemini-3-flash-preview"), @@ -211,43 +208,40 @@ Extract the name from the above context.`, }, ); - await step.do("trigger strategy suggestions workflow", async () => { - const db = createDb(); - const taskResult = await createTask({ - db, - userId: undefined, - input: { - type: "seo-generate-strategy-suggestions", - projectId: project.id, - instructions: ONBOARDING_STRATEGY_SUGGESTION_INSTRUCTIONS, - }, - workflowInstanceId: `strategy_${event.instanceId}_${crypto.randomUUID().slice(0, 5)}`, - }); - if (!taskResult.ok) { - logError("failed to trigger strategy suggestions workflow", { - projectId: project.id, - error: taskResult.error, + const [, brandVoiceResult] = await Promise.all([ + step.do("trigger strategy suggestions workflow", async () => { + const db = createDb(); + const taskResult = await createTask({ + db, + userId: undefined, + input: { + type: "seo-generate-strategy-suggestions", + projectId: project.id, + instructions: ONBOARDING_STRATEGY_SUGGESTION_INSTRUCTIONS, + }, + workflowInstanceId: `strategy_${event.instanceId}_${crypto.randomUUID().slice(0, 5)}`, }); - return; - } + if (!taskResult.ok) { + logError("failed to trigger strategy suggestions workflow", { + projectId: project.id, + error: taskResult.error, + }); + return; + } - const updateResult = await updateSeoProject(db, { - id: project.id, - organizationId: project.organizationId, - strategySuggestionsWorkflowId: taskResult.value.id, - }); - if (!updateResult.ok) { - logError("failed to save strategy suggestions workflow id", { - projectId: project.id, - error: updateResult.error, + const updateResult = await updateSeoProject(db, { + id: project.id, + organizationId: project.organizationId, + strategySuggestionsWorkflowId: taskResult.value.id, }); - } - }); - - const brandVoiceResult = await step.do( - "generate brand voice", - { timeout: "10 minutes" }, - async () => { + if (!updateResult.ok) { + logError("failed to save strategy suggestions workflow id", { + projectId: project.id, + error: updateResult.error, + }); + } + }), + step.do("generate brand voice", { timeout: "10 minutes" }, async () => { const { tools } = createWebToolsWithMetadata(project, this.env.CACHE); const system = `You are an SEO research expert extracting a brand's writing tone. @@ -289,8 +283,8 @@ Extract the name from the above context.`, }); return outputResult.experimental_output; - }, - ); + }), + ]); const { brandVoice } = await step.do( "update project brand voice", diff --git a/packages/api-seo/src/workflows/planner-workflow.ts b/packages/api-seo/src/workflows/planner-workflow.ts index c9abe329..55884019 100644 --- a/packages/api-seo/src/workflows/planner-workflow.ts +++ b/packages/api-seo/src/workflows/planner-workflow.ts @@ -447,7 +447,7 @@ export class SeoPlannerWorkflow extends WorkflowEntrypoint< locationName, languageCode, project, - notes: "(none)", + notes: content.notes ?? undefined, }; }); From 3963b178c4a00b331bac0cb21f892ed081399acf Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sun, 15 Feb 2026 21:21:12 +0800 Subject: [PATCH 2/3] chore: update changeset config --- .changeset/config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/config.json b/.changeset/config.json index 89b8925c..d7dd59d7 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -25,6 +25,7 @@ "@rectangular-labs/ui", "www", "seo", - "seo-www" + "seo-www", + "contact" ] } From 8170bd882a947e280745083ede7b246b3a227ad4 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sun, 15 Feb 2026 21:29:11 +0800 Subject: [PATCH 3/3] chore: pr comments --- packages/api-seo/src/workflows/planner-workflow.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api-seo/src/workflows/planner-workflow.ts b/packages/api-seo/src/workflows/planner-workflow.ts index 55884019..ebbaf6e5 100644 --- a/packages/api-seo/src/workflows/planner-workflow.ts +++ b/packages/api-seo/src/workflows/planner-workflow.ts @@ -399,6 +399,7 @@ export class SeoPlannerWorkflow extends WorkflowEntrypoint< organizationId: input.organizationId, projectId: input.projectId, id: input.draftId, + withContent: true, }); if (!contentResult.ok) { throw new NonRetryableError(contentResult.error.message);