Conversation
Picasite (and any other MCP client) gathers source URLs during
article planning + drafting — Tavily web searches, fetch_page
calls, manual additions. Persisting them so they survive publish
and can be rendered as a "Sources" section on the live article.
Schema:
- Article: new `references Json?` column. Additive + nullable.
- Migration: 20260526220000_add_article_references — pure ADD COLUMN.
Contract (v2):
- ArticleReferenceSchema = { url, title?, snippet?, accessedAt, source? }
where source ∈ { 'web_search', 'fetch_page', 'manual' }.
- ArticleCanonicalV2Schema extended with
`references: z.array(ArticleReferenceSchema).default([])`.
- DELIBERATELY EXCLUDED from CANONICAL_ARTICLE_FIELDS_V2 — references
don't participate in content hashing or webhook changedFields.
Same pattern as socialImages / readTime: persisted but not part
of the optimistic-concurrency contract. Lets clients update
references without invalidating other clients' baseHash.
MCP article service plumbing:
- ArticleRowLike + ARTICLE_ROW_SELECT include references.
- mapArticleToCanonical parses + emits.
- canonicalToArticleData / buildExtraUpdateData write through the
"extra" path (same as socialImages).
- parseReferences safeParses the loosely-typed Json column.
Theme rendering on the live article page is a separate change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 14 minutes and 1 second. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (16)
WalkthroughThis PR adds persistent storage for article references (external citations). It defines a reference schema with URL and access timestamp, creates a database column, and integrates reference loading, parsing, and persistence throughout the article service layer. ChangesArticle References Storage
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…tter Two test/CI fixes on the references PR. 1. plan-service.ts:draftToCanonical was missing the new `references` field in the canonical article it builds, so strict TS rejected the assignment to `ArticleCanonicalV2`. Default to `[]` like the schema does (clients that don't track references pass empty). 2. jest.setup.js Request polyfill assigned `this.url = ...` directly, which trips "Cannot set property url of #<NextRequest> which has only a getter" when a test instantiates `NextRequest` (NextRequest extends Request and defines `url` as a read-only accessor on its prototype). Switch to `Object.defineProperty` so we create an OWN property that shadows the prototype getter — works for both plain Request and NextRequest constructions. Pre-existing test failures unrelated to references but breaking CI on this PR. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/mcp/contract.ts`:
- Around line 409-417: ArticleReferenceSchema currently accepts any string for
url and accessedAt; tighten validation by replacing url: z.string() with a URL
validator (e.g., z.string().url()) and replace accessedAt: z.string() with an
ISO datetime check (e.g., z.string().refine(val => !isNaN(Date.parse(val)), {
message: 'invalid ISO datetime' }) or a strict ISO-8601 regex/validator). Update
the ArticleReferenceSchema definition (the url and accessedAt properties) to use
these validators so malformed URLs or non-ISO timestamps are rejected.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7f8f21a3-b414-4dcf-90e7-db1e766d16c9
📒 Files selected for processing (4)
prisma/migrations/20260526220000_add_article_references/migration.sqlprisma/schema.prismasrc/lib/mcp/articles-service.tssrc/lib/mcp/contract.ts
| export const ArticleReferenceSchema = z.object({ | ||
| url: z.string(), | ||
| title: z.string().optional(), | ||
| /** Short excerpt from the source (search hit content or page summary). */ | ||
| snippet: z.string().optional(), | ||
| /** ISO timestamp of when the source was looked up. */ | ||
| accessedAt: z.string(), | ||
| /** Where this reference was captured in the client's pipeline. */ | ||
| source: z.enum(['web_search', 'fetch_page', 'manual']).optional(), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant section of the file
sed -n '360,480p' src/lib/mcp/contract.ts | nl -ba | sed -n '1,160p'
# Find all usages of ArticleReferenceSchema and accessedAt/url fields in contract.ts
rg -n "ArticleReferenceSchema|accessedAt|source:\s*z\.enum|url:\s*z\.string" src/lib/mcp/contract.ts
# Check for existing zod datetime/url validators elsewhere in the codebase
rg -n "z\.string\(\)\.url\(|z\.string\(\)\.datetime\(" -S src | head
rg -n "accessedAt" -S src | head -n 50
rg -n "ArticleCanonicalV2Schema" -S src/lib/mcp/contract.ts
# Print package.json or lock snippet to confirm zod version if available
if [ -f package.json ]; then rg -n "\"zod\"" package.json || true; fi
if [ -f pnpm-lock.yaml ]; then rg -n "zod:" pnpm-lock.yaml | head -n 20 || true; fi
if [ -f yarn.lock ]; then rg -n "zod@" yarn.lock | head -n 20 || true; fi
if [ -f package-lock.json ]; then rg -n "\"zod\"" package-lock.json | head -n 20 || true; fiRepository: mmogomedia/music-next
Length of output: 108
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant section of the file
sed -n '360,480p' src/lib/mcp/contract.ts | nl -ba | sed -n '1,160p'
# Find all usages of ArticleReferenceSchema and accessedAt/url fields in contract.ts
rg -n "ArticleReferenceSchema|accessedAt|source:\s*z\.enum|url:\s*z\.string" src/lib/mcp/contract.ts
# Check for existing zod datetime/url validators elsewhere in the codebase
rg -n "z\.string\(\)\.url\(|z\.string\(\)\.datetime\(" -S src | head
rg -n "accessedAt" -S src | head -n 50
rg -n "ArticleCanonicalV2Schema" -S src/lib/mcp/contract.ts
# Print package.json or lock snippet to confirm zod version if available
if [ -f package.json ]; then rg -n "\"zod\"" package.json || true; fi
if [ -f pnpm-lock.yaml ]; then rg -n "zod:" pnpm-lock.yaml | head -n 20 || true; fi
if [ -f yarn.lock ]; then rg -n "zod@" yarn.lock | head -n 20 || true; fi
if [ -f package-lock.json ]; then rg -n "\"zod\"" package-lock.json | head -n 20 || true; fiRepository: mmogomedia/music-next
Length of output: 108
🏁 Script executed:
# (Results needed from the executed shell script)Repository: mmogomedia/music-next
Length of output: 47
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the exact current schema lines (no nl dependency)
sed -n '395,430p' src/lib/mcp/contract.ts
# Show wider context including where ArticleReferenceSchema is defined/used
sed -n '430,470p' src/lib/mcp/contract.ts
# Find usages of ArticleReferenceSchema and accessedAt/url within contract.ts
rg -n "ArticleReferenceSchema" src/lib/mcp/contract.ts
rg -n "accessedAt" src/lib/mcp/contract.ts
rg -n "url:\s*z\.string" src/lib/mcp/contract.ts
# Check for existing stronger validators elsewhere in src
rg -n "z\.string\(\)\.url\(" src | head -n 50
rg -n "z\.string\(\)\.datetime\(" src | head -n 50
# Confirm zod version from lockfiles/package.json (best-effort)
for f in package.json pnpm-lock.yaml yarn.lock package-lock.json; do
if [ -f "$f" ]; then
echo "---- $f ----"
rg -n "\"zod\"|zod:" "$f" | head -n 30 || true
fi
doneRepository: mmogomedia/music-next
Length of output: 7085
🏁 Script executed:
sed -n '280,340p' src/lib/mcp/system-manifest.ts
rg -n "accessedAt" srcRepository: mmogomedia/music-next
Length of output: 2242
Harden citation contract validation for URL/timestamp fields.
In src/lib/mcp/contract.ts, ArticleReferenceSchema currently uses url: z.string() and accessedAt: z.string(), so it doesn’t enforce the documented “URL” / ISO datetime semantics and can allow malformed citation payloads to be persisted.
Suggested diff
export const ArticleReferenceSchema = z.object({
- url: z.string(),
+ url: z.string().url(),
title: z.string().optional(),
/** Short excerpt from the source (search hit content or page summary). */
snippet: z.string().optional(),
/** ISO timestamp of when the source was looked up. */
- accessedAt: z.string(),
+ accessedAt: z.string().datetime(),
/** Where this reference was captured in the client's pipeline. */
source: z.enum(['web_search', 'fetch_page', 'manual']).optional(),
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const ArticleReferenceSchema = z.object({ | |
| url: z.string(), | |
| title: z.string().optional(), | |
| /** Short excerpt from the source (search hit content or page summary). */ | |
| snippet: z.string().optional(), | |
| /** ISO timestamp of when the source was looked up. */ | |
| accessedAt: z.string(), | |
| /** Where this reference was captured in the client's pipeline. */ | |
| source: z.enum(['web_search', 'fetch_page', 'manual']).optional(), | |
| export const ArticleReferenceSchema = z.object({ | |
| url: z.string().url(), | |
| title: z.string().optional(), | |
| /** Short excerpt from the source (search hit content or page summary). */ | |
| snippet: z.string().optional(), | |
| /** ISO timestamp of when the source was looked up. */ | |
| accessedAt: z.string().datetime(), | |
| /** Where this reference was captured in the client's pipeline. */ | |
| source: z.enum(['web_search', 'fetch_page', 'manual']).optional(), |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/mcp/contract.ts` around lines 409 - 417, ArticleReferenceSchema
currently accepts any string for url and accessedAt; tighten validation by
replacing url: z.string() with a URL validator (e.g., z.string().url()) and
replace accessedAt: z.string() with an ISO datetime check (e.g.,
z.string().refine(val => !isNaN(Date.parse(val)), { message: 'invalid ISO
datetime' }) or a strict ISO-8601 regex/validator). Update the
ArticleReferenceSchema definition (the url and accessedAt properties) to use
these validators so malformed URLs or non-ISO timestamps are rejected.
…yfill Two unblockers for PR #48 CI. 1. Vercel preview builds were failing at `prisma migrate deploy` with P1001: "Can't reach database server" because the preview env's Neon endpoint (ep-orange-union-ad1mxrdt) is currently unreachable. Preview deploys shouldn't be running migrations anyway — those belong to prod main. Switch the build to call a small wrapper `scripts/migrate-deploy.mjs` that skips when VERCEL_ENV != "production". Mirrors Picasite's existing pattern. 2. jest.setup.js Response polyfill was missing the static `Response.json(body, init)` factory method, which NextResponse.json calls internally. Every API-route test that returns NextResponse.json(...) threw "Response.json is not a function". Added the static — pre-existing test failure that's been around for a while, but now PR #48 surfaces it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nt shape
Both AbuseGuardAgent and IndustryInfoAgent now return AgentResponse with
`type` nested under `data` (`{ message, data: { type, message, timestamp } }`),
not at the top level. Update the two test suites to assert against
`data: { type: 'text' }` so they match the actual response shape.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…les) - response-types: rewire mocks to @/lib/ai/memory/bootstrap (current module path) and add semanticMemoryManager + memoryOrchestrator mocks to match route.ts dependencies. Drops obsolete conversation-store and preference-tracker mock paths. - discovery-tools: align expectations with current source — drop minStrength: 70 arg from searchTracks calls, drop third options arg from getTracksByGenre, drop trendingScore field, drop 'Track/Artist not found' error key (validated by zod schema that strips it), and update getPlaylist "limit to 10" test to assert all tracks returned (no slice in source). - article-tools: update URL prefix /articles/ → /learn/.
…d OOM The provider has two effects on volume/isMuted: an init effect that reads persisted values from localStorage and calls setState, and a persist effect that writes state back to localStorage on change. Under jsdom these two oscillate (init reads stale value, persists writes new value, init re-runs, ...) producing "Maximum update depth exceeded" and crashing the worker via heap exhaustion (~167s before crash on this machine). Stub localStorage.getItem to return null so the init effect never calls setState, and add cleanup() between tests so listeners + audio refs from the previous render don't accumulate. Suite now passes in ~1.2s at 70MB.
The init route was rewritten to take `fileType` (not `mimeType`), allow files up to 100MB (not 50MB), and return error messages like "File too large. Maximum size: 100MB" and "Invalid file type. Allowed: MP3, WAV, FLAC, M4A, AAC". The test was still asserting the old shape and so 3 of 4 cases were tripping the missing-required-fields guard. Update payloads and expected error strings to match the route, and add a mock for `prisma.uploadJob.update` (the route now calls update after create to attach the presigned URL).
…-tests test: align abuse-guard + industry-info expectations with current agent shape
test: fix AI chat / tool test drift (response-types, discovery, articles)
test: fix MusicPlayerContext OOM + verify upload-init polyfill
FileUpload:
- Component button is "Choose File" (singular) and only opens the hidden
<input type="file" />, so target the input directly with user.upload
instead of getByRole('button', /choose files/i).
- "Upload to Cloud Storage" was renamed to "Upload Track".
- Heading is "Drag & drop your music" with subtext "or click below to
browse your files".
- The component performs no JS-level type/size validation in
handleFileSelect; type filtering is just the input's `accept`
attribute and size limits are enforced server-side. Replaced the two
validation cases with one assertion on `accept` and one that confirms
large files are accepted as-is.
- Switched fetch mockClear -> mockReset so leftover
mockResolvedValueOnce queue entries don't bleed across tests.
- Mock @heroui/ripple + @heroui/dom-animation locally so HeroUI Button
clicks don't trigger framer-motion's dynamic import (jsdom can't
resolve it without --experimental-vm-modules).
ClaimProfileStep:
- Mock @heroui/ripple + @heroui/dom-animation so the Claim button click
doesn't hit the framer-motion dynamic-import error.
- Description text is now "Search for your existing artist profile...",
not "Already have music on Flemoji".
- Switched fetch mockClear -> mockReset to clear leftover
mockResolvedValueOnce queue entries between tests (was causing the
"search failed" and "track artwork" cases to flake when the suite ran
end-to-end).
ArtistProfileWizard:
- HeroUI Button destructures useRipple() as { onPress, onClear, ripples }.
The old mock returned pointer handlers (onMouseDown/Up/etc.), so
onRipplePressHandler was undefined and every click threw
"onRipplePressHandler is not a function". Updated mock shape.
pulse-league-service: getLatestEligibilityScores was refactored from groupBy + N×findFirst into a single $queryRaw (DISTINCT ON). Tests still mocked the old shape. Add $queryRaw to the prisma mock and a mockEligibilityScores helper, replacing all groupBy/findFirst setup across the 25 affected cases. article-service: getArticleBySlug now reads from prisma.contentLink to resolve linked tool slugs. Mock contentLink.findMany so the slug lookup test stops throwing 'Cannot read properties of undefined (reading findMany)'. tracks/route: already passing — no change needed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
test: realign onboarding + upload component tests with current UI
test: fix Prisma mock drift for pulse-league + article + tracks
Summary
Promote develop to main so prod picks up the new
referencescolumnon Article + the canonical schema extension that lets MCP clients
(Picasite, etc.) attach source citations to articles.
Key change:
3eb3175— feat(mcp): persist external source citations on Article.referencesPicasite gathers source URLs during MCP-driven planning + drafting
(Tavily web searches, fetch_page calls, manual additions). Until now
those URLs were captured at plan time but stripped on apply because
the Article model had no field for them. This change adds the field
end-to-end.
Schema:
references Json?onArticle(additive, nullable).20260526220000_add_article_references— pureADD COLUMN.Contract:
ArticleReferenceSchema = { url, title?, snippet?, accessedAt, source? }ArticleCanonicalV2Schemaextended withreferencesarray.CANONICAL_ARTICLE_FIELDS_V2so referencechanges don't invalidate other clients' baseHash. Same pattern as
socialImages/readTime.MCP plumbing:
"extra" path (same as socialImages).
Theme rendering on the live article page is a separate change.
Test plan
created articles (visible via get_article in canonical shape)
research → apply → confirm references survive on the Article row
🤖 Generated with Claude Code