Skip to content

Winston/strategy#389

Open
ElasticBottle wants to merge 49 commits intomainfrom
winston/strategy-db
Open

Winston/strategy#389
ElasticBottle wants to merge 49 commits intomainfrom
winston/strategy-db

Conversation

@ElasticBottle
Copy link
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings February 1, 2026 08:49
@gemini-code-assist
Copy link

Summary of Changes

Hello @ElasticBottle, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the platform's SEO capabilities by introducing a robust strategy management system. Users can now interact with AI-generated SEO strategy suggestions directly within the application, choosing to adopt or dismiss them, and modifying their details. This feature is seamlessly integrated into the onboarding process, providing immediate value to new projects. The underlying architecture has been updated with new database schemas and a dedicated AI workflow to support this functionality, alongside refinements to the content publishing pipeline.

Highlights

  • Strategy Management System: Introduced a comprehensive system for managing SEO strategies, including new UI components for displaying, adopting, dismissing, and modifying AI-generated strategy suggestions.
  • AI-Driven Strategy Generation: Implemented a new SeoStrategySuggestionsWorkflow to automatically generate SEO strategy suggestions based on project context, leveraging AI tools for web research and data analysis.
  • Database Schema Updates: Added new database tables (seo_strategy, seo_strategy_phase, seo_strategy_phase_content, seo_strategy_snapshot, seo_strategy_snapshot_content) and modified existing ones to support the new strategy framework, including tracking strategy goals, phases, and performance snapshots.
  • Onboarding Integration: Integrated a new 'Strategy Insights' step into the onboarding flow, allowing new projects to immediately benefit from AI-generated SEO strategies.
  • Content Publishing Refactoring: Refactored content draft handling and publishing logic to align with the new strategy framework, including changes to how drafts are identified as new or existing, and how scheduled publishing times are determined based on strategy cadences.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2fc3bfb091

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 31 to 35
.handler(async ({ context, input }) => {
const strategiesResult = await listStrategiesByProjectId({
db: context.db,
projectId: input.projectId,
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce org ownership when listing strategies

These routes only pass projectId into the DB lookup, and the middleware only verifies membership in the supplied organization, not that the project belongs to it. That means a user in org A can supply org A’s identifier but a projectId from org B and still retrieve that project’s strategies if they know the ID. Add a project/org ownership check (e.g., join through seoProject or validate projectId against context.organization.id) before calling listStrategiesByProjectId (and similarly for get/update).

Useful? React with 👍 / 👎.

ALTER TABLE "seo_content_draft" DROP CONSTRAINT "seo_content_draft_base_content_id_seo_content_id_fk";
--> statement-breakpoint
DROP INDEX "seo_content_draft_org_project_status_base_id_idx";--> statement-breakpoint
ALTER TABLE "seo_content" ADD COLUMN "originating_draft_id" uuid NOT NULL;--> statement-breakpoint

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Backfill before adding NOT NULL column

This migration adds originating_draft_id as NOT NULL on seo_content without a default or a backfill. On any environment where seo_content already has rows, Postgres will error because existing rows get NULL for the new column. You’ll need to add the column nullable, backfill from existing draft/content relations, then enforce NOT NULL.

Useful? React with 👍 / 👎.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant new "Strategy" feature, including UI, API, database schema, and AI workflows. It also includes a major and well-executed refactoring of the content draft lifecycle, making drafts long-lived entities. The changes are extensive but appear consistent and well-structured. My review focuses on a few areas for improvement regarding robustness and user experience in the new components.

Comment on lines 279 to 283
<StrategyModifyDialog
organizationId={organizationId ?? ""}
projectId={projectId ?? ""}
strategy={strategy}
/>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The StrategyModifyDialog is being passed organizationId ?? "" and projectId ?? "". If the original IDs are undefined, this will result in empty strings being passed to a component that expects valid identifiers. This can lead to failed API calls. It's safer to conditionally render the StrategyModifyDialog only when both organizationId and projectId are available.

            {organizationId && projectId && (
              <StrategyModifyDialog
                organizationId={organizationId}
                projectId={projectId}
                strategy={strategy}
              />
            )}

Comment on lines 109 to 114
if (!organizationId || !projectId) {
form.setError("root", {
message: "Missing organization or project details.",
});
return;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The check if (!organizationId || !projectId) is not robust against empty strings. Since the parent component might pass "", this check will evaluate to false, but the subsequent API call will likely fail with an empty organizationIdentifier. You should trim the strings before checking to ensure they are not empty. This also applies to the disabled prop of the save button.

Suggested change
if (!organizationId || !projectId) {
form.setError("root", {
message: "Missing organization or project details.",
});
return;
}
if (!organizationId.trim() || !projectId.trim()) {
form.setError("root", {
message: "Missing organization or project details.",
});
return;
}

Comment on lines 284 to 291
<Button
isLoading={isAdopting}
onClick={() => handleStatusChange("planned")}
size="sm"
type="button"
>
Adopt Strategy
</Button>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency and better user experience, the "Adopt Strategy" button should be disabled when organizationId or projectId are missing, similar to how the "Dismiss" button is handled. While the handleStatusChange function has guards, disabling the button provides clearer feedback to the user.

            <Button
              disabled={isAdopting || !organizationId || !projectId}
              isLoading={isAdopting}
              onClick={() => handleStatusChange("planned")}
              size="sm"
              type="button"
            >
              Adopt Strategy
            </Button>

## Expectations
- Provide concise, exact values without extra commentary.`;

const { experimental_output } = await generateText({

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The system prompt for the AI has potentially conflicting instructions. It asks the model to "Return the homepage title exactly as provided" and also to "Extract the brand or product name from the title". Since the output schema only expects the extracted name, the instruction to return the full title is unnecessary and could confuse the model. It's better to have the prompt focus solely on the extraction task.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a comprehensive strategy management feature to the SEO platform. The changes add database schemas for strategies, phases, snapshots, and associated content, along with corresponding API routes, workflows, and UI components.

Changes:

  • Adds new database tables for strategy management (seo_strategy, seo_strategy_phase, seo_strategy_snapshot, etc.)
  • Replaces baseContentId in content drafts with originatingDraftId in published content
  • Removes content attribution tables (seo_content_chat, seo_content_contributor)
  • Adds strategy suggestions workflow integrated into onboarding
  • Implements strategy-related API routes and operations
  • Updates content publishing flow to mark drafts as "published" instead of deleting them

Reviewed changes

Copilot reviewed 58 out of 61 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/db/migrations/0018_square_black_cat.sql Creates new strategy tables and drops old attribution tables
packages/db/migrations/0019_noisy_black_bird.sql Adds originatingDraftId to seo_content and updates indexes
packages/db/src/schema/seo/*.ts New schemas for strategy, phase, snapshot, and related entities
packages/db/src/operations/seo/strategy-operations.ts CRUD operations for strategies and phases
packages/api-seo/src/workflows/strategy-suggestions-workflow.ts AI workflow to generate strategy suggestions
packages/api-seo/src/routes/strategy.ts REST API for strategy management
packages/api-seo/src/lib/content/*.ts Updated content operations for new model
apps/seo/src/routes/_authed/onboarding/*.tsx UI for strategy insights in onboarding
apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/-components/*.tsx Strategy card and modify dialog UI

ALTER TABLE "seo_content_draft" DROP CONSTRAINT "seo_content_draft_base_content_id_seo_content_id_fk";
--> statement-breakpoint
DROP INDEX "seo_content_draft_org_project_status_base_id_idx";--> statement-breakpoint
ALTER TABLE "seo_content" ADD COLUMN "originating_draft_id" uuid NOT NULL;--> statement-breakpoint
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration 0019 adds a NOT NULL column 'originating_draft_id' to existing 'seo_content' table without providing a default value or a data migration step. This will fail if there are any existing rows in the seo_content table. You need to either:

  1. Add a default value temporarily
  2. Make the column nullable initially and backfill data
  3. Include a data migration step before adding the NOT NULL constraint

Copilot uses AI. Check for mistakes.
Comment on lines +213 to +221
if (!scheduledFor && !hasPublishedSnapshot && updatedDraft.strategyId) {
const currentPhase = await getCurrentStrategyPhase({
db: args.db,
strategyId: updatedDraft.strategyId,
});
if (!currentPhase.ok) {
return currentPhase;
}
const project = projectResult.value;

const cadence = project.publishingSettings?.cadence;
if (cadence) {
const scheduledItemsResult = await getScheduledItems({
db: args.db,
organizationId: draft.organizationId,
projectId: draft.projectId,
const { cadence } = currentPhase.value;
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic assumes that if a draft has a strategyId, there must be a current phase. However, getCurrentStrategyPhase can return an error (line 189 in strategy-operations.ts: "Failed to find strategy phase") if no phase exists. This will prevent scheduling content for strategies that don't yet have phases, which could happen during strategy setup. Consider handling the case where no phase exists yet more gracefully, perhaps by falling back to immediate scheduling or returning a more specific error.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +77
ALTER TABLE "seo_content_chat" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
ALTER TABLE "seo_content_contributor" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
DROP TABLE "seo_content_chat" CASCADE;--> statement-breakpoint
DROP TABLE "seo_content_contributor" CASCADE;--> statement-breakpoint
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration 0018 drops the seo_content_chat and seo_content_contributor tables with CASCADE, which will also drop any foreign key constraints from other tables that reference them. However, the schema changes in content-schema.ts show these relations being removed from the code. Verify that no other production code still references these tables, and ensure the migration order is correct if this runs on a live database with existing data.

Copilot uses AI. Check for mistakes.
chore(api-seo): add more information in logs
fix(api-seo): type errors
refactor: unify bunch of duplicated code
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