Skip to content

feat(api): include sourceParagraphId in outbound references - #419

Merged
thewrz merged 1 commit into
mainfrom
feat/issue-373
Jul 8, 2026
Merged

feat(api): include sourceParagraphId in outbound references#419
thewrz merged 1 commit into
mainfrom
feat/issue-373

Conversation

@thewrz

@thewrz thewrz commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Why

GET /projects/:id/specs/:specId/references returned OutboundReference without sourceParagraphId, unlike the inbound and broken-refs endpoints which already carry it. The web_ui_demo builds a per-paragraph reference index from that field (tree.js buildSheetCtx → refsByParagraph), so the index was always empty — silently disarming the removed-citation flow: editing citation text out of a paragraph (map textarea flow, the Editor's WYSIWYG blur/Enter/Tab paths, and the chip × for tracked refs) never detected a removed tracked reference, so the "This edit removes a cross-reference / also remove the section?" dialog was unreachable and edits committed directly.

Discovered while verifying PR #370 pass 3 — the client machinery (occurrence-count diffing, the choice dialog, the cascade) all exists and is tested at the module level; it just never received paragraph-scoped references.

What

Add sourceParagraphId to the outbound reference rows and expose it through the API and MCP surfaces:

  • src/db/queries/refs.tsSELECT sr.source_paragraph_id, plus the field on OutboundReferenceRow, the OutboundReference DTO, and mapOutbound. Mirrors exactly how getInboundReferences / getBrokenRefs already do it.
  • openapi.yamlOutboundReference schema gains sourceParagraphId (string, format: uuid, required), matching InboundReference's casing and nullability. Same PR, per the contract gate.
  • The MCP get_references tool serializes the raw outbound array, so it now carries the field automatically — no tool-shape change; the REST↔MCP parity gate (INV-1/2/3) stays green.

Design decisions

  • No migration. The source_paragraph_id column already exists on spec_references (migration 008, NOT NULL, FK → paragraphs); this was purely a projection gap in the outbound SELECT.
  • Non-nullable uuid, required in the schema — mirrors InboundReference.sourceParagraphId exactly, because the column is NOT NULL. No new null case to handle.
  • No ADR. This is a plumbing gap (one omitted column), not a design decision.

Testing

  • Unit tests pass — pnpm test (1467 passed with DATABASE_URL set; the lone local miss was open-comments.test.ts aborting on an unset DATABASE_URL, pre-existing and unrelated).
  • Integration tests pass — ran locally against a disposable Postgres (5434): contract.integration.test.ts (confirms the response now matches the updated OutboundReference schema), refs.integration.test.ts, mcp/contract.integration.test.ts (parity gate), references.integration.test.ts, mcp/server.integration.test.ts — 65 passed.
  • pnpm lint (eslint + tsc + prettier) and pnpm build green.
  • Regression test pins the symptom: outbound references carry sourceParagraphId (was empty → demo removed-citation flow disarmed).
  • CI green

🤖 Co-authored by Claude Opus 4.8. Closes #373.

Summary by CodeRabbit

  • New Features

    • Outbound references now include paragraph-level location details, making it easier to identify exactly where a reference appears in the source content.
  • Bug Fixes

    • Improved outbound reference results so the new location field is consistently returned across reference lookups.
  • Tests

    • Added and updated coverage to verify the new paragraph location data is present in reference outputs.

GET /projects/:id/specs/:specId/references returned OutboundReference
without the paragraph locator, unlike the inbound and broken-refs
endpoints which already carry it. The web_ui_demo builds a per-paragraph
reference index from that field (tree.js buildSheetCtx → refsByParagraph),
so the index was always empty — silently disarming the removed-citation
flow: editing citation text out of a paragraph never detected a removed
tracked reference, so the "also remove the section?" dialog was unreachable
and edits committed directly.

The source_paragraph_id column already exists on spec_references and is
NOT NULL; the inbound and broken queries already SELECT and expose it. Add
it to the outbound query, its row/DTO types, and mapOutbound, and document
it on the OutboundReference schema in openapi.yaml (same PR, per the
contract gate). No migration — the column exists. The MCP get_references
tool serializes the raw outbound array, so it now carries the field
automatically; the REST↔MCP parity gate stays green.

Closes #373

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fc6a6878-34bb-4e8a-b0b7-f755a1e8e47a

📥 Commits

Reviewing files that changed from the base of the PR and between 95058d9 and f810461.

📒 Files selected for processing (5)
  • openapi.yaml
  • src/db/queries/refs.integration.test.ts
  • src/db/queries/refs.test.ts
  • src/db/queries/refs.ts
  • src/mcp/anchors.test.ts

📝 Walkthrough

Walkthrough

The OutboundReference schema and query implementation now include a required sourceParagraphId field. The openapi.yaml contract, refs.ts row mapping/SQL projection, and related unit, integration, and MCP anchor tests are updated to carry paragraph-level locator data through outbound reference results.

Changes

sourceParagraphId propagation

Layer / File(s) Summary
API contract update
openapi.yaml
Adds sourceParagraphId (UUID) to OutboundReference's required fields and property definitions.
Query row, mapper, and SQL projection
src/db/queries/refs.ts
Extends OutboundReferenceRow and OutboundReference with source_paragraph_id/sourceParagraphId, updates mapOutbound, and adds sr.source_paragraph_id to the getOutboundReferences SELECT.
Test coverage for sourceParagraphId
src/db/queries/refs.test.ts, src/db/queries/refs.integration.test.ts, src/mcp/anchors.test.ts
Adds/updates unit tests for query SQL and mapping, an integration assertion for sourceParagraphId presence/type, and MCP anchor fixture updates including the new field.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • wrzonance/SpecR#330: Adds sourceParagraphId derivation to outbound reference handling and MCP anchor generation, directly related to this PR's field addition.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding sourceParagraphId to outbound references.
Linked Issues check ✅ Passed The PR updates the outbound query, types, mapping, OpenAPI schema, and tests to expose sourceParagraphId as requested in #373.
Out of Scope Changes check ✅ Passed The changes stay focused on outbound references and related schema/test updates, with no obvious unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-373

Comment @coderabbitai help to get the list of available commands.

@thewrz
thewrz marked this pull request as ready for review July 8, 2026 06:34
@thewrz

thewrz commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@thewrz
thewrz merged commit fc6038c into main Jul 8, 2026
5 checks passed
@thewrz
thewrz deleted the feat/issue-373 branch July 8, 2026 14:52
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.

feat(api): include sourceParagraphId in project outbound references

1 participant