Skip to content

fix(frontend): preserve structured footnote roles - #388

Merged
seonghobae merged 7 commits into
feat/event-lineage-channel-evidencefrom
feat/post-body-footnote-display
Aug 21, 2026
Merged

fix(frontend): preserve structured footnote roles#388
seonghobae merged 7 commits into
feat/event-lineage-channel-evidencefrom
feat/post-body-footnote-display

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • preserve footnote semantics in the browser fallback renderer for HTML footnote lists, Word MsoFootnoteText, and OOXML footnote/endnote containers
  • keep anchors from creating false leading indentation in rendered footnote text
  • add synthetic regression coverage for all three source encodings

Delivery

This is intentionally stacked on PR #387 (4faf9a31371195c5ec63fca42a5afbb93a95369b) so the existing exact-head frontend build fixes remain in the base. Merge the base PR first, then rebase this change onto the resulting main head.

Verification

  • corepack pnpm run lint
  • corepack pnpm run test — 144 passed
  • corepack pnpm run build
  • git diff --check

No real records or identifiers are included.


Open in Devin Review

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c61893f0-560a-4fcc-a7a8-4371d04501d9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

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

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Please review exact current head 146cc56e07db9479ab8dba93aedb0ffd06d2e795. The stack now closes HTML footnote scopes against matching open tags (including nested/non-footnote markup) and the fallback renderer strips control markers for an empty footnote container. Exact-head validation: frontend lint passed, all frontend tests passed (146), production build passed, and diff check passed. This requests review and is not an approval.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Fixed at 81cd12f.

Root cause: closing HTML footnote tags do not repeat the class that identifies the container, so the old depth counter leaked the footnote role into later ordinary paragraphs. The parser now tracks matching open tags, including nested lists and OOXML containers, and decrements only when the corresponding container closes.

Evidence: frontend test suite 145 passed, TypeScript/Vite build passed, oxlint passed, and the focused post-body parser test passed (14 tests).

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Please review exact current head ee5882a after the normal stack update. PR #388 now includes current parent head 4bf0613 through a regular merge commit, while retaining the footnote-container and empty-marker fixes. Review the parent-child boundary, frontend behavior, and any merge-conflict or regression risk at this exact head; report actionable findings with file and line evidence.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review Please re-review exact head 1b680a27e6eaca544f1d99512e31220278c43110. Current-head fixes bound anchor recognition to the actual a tag and footnote recognition to semantic class/role attributes. Frontend: 148 passed; lint, build, and Storybook build passed. Do not approve superseded heads.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Please review exact current head 1b680a2. A concurrent repair now bounds footnote detection to class/role attributes and fixes anchor-tag name matching (so abbr is not stripped), with two regression tests. The current branch retains the parent-stack merge and prior footnote fixes; frontend lint, 148 tests, and build pass. Review exact-head parser safety and buyer-visible text boundaries; report actionable findings.

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Exact-head frontend evidence

Current head: 1b680a27e6eaca544f1d99512e31220278c43110.

  • pnpm run test: 14 files, 148 tests passed.
  • pnpm run lint: passed.
  • pnpm run build: TypeScript and Vite production build passed.
  • git diff --check: passed.
  • Footnote recognition covers HTML, Word, OOXML, empty containers, and avoids unrelated attribute false positives; anchor-tag matching is bounded to actual anchor tags.

Hosted Checks and independent approval remain required. No force push or bypass.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Please review the exact current head 1b680a2 after the parent stack update. The child #389 includes this parent and passed the combined frontend test/build path; verify this PR independently and leave a formal review verdict after hosted Checks.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Fixed on exact head caea21be. Footnote containers now include a labeled div wrapper around an unlabeled ol/ul, matching common <div class="footnotes"><ol>...</ol></div> markup without labeling ordinary content after the wrapper. Added a regression for body text, wrapped footnote text, and post-footnote text. Exact-head local verification: 149 frontend tests passed, lint, production build, and diff check.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review 재검토 요청: exact head 86ac1d4. 동시 wrapper-footnote 수정과 최신 PR #387 부모를 보존해 fast-forward stack으로 재적층했고 frontend lint, 149 tests, production build, Storybook build가 통과했습니다.

@seonghobae
seonghobae merged commit 068ed6a into feat/event-lineage-channel-evidence Aug 21, 2026
3 of 4 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in naruon Platform Roadmap Aug 21, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

Open in Devin Review

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Marker hides footnote-line indentation from unit inference

inferIndentationUnit measures leading whitespace without stripping the footnote marker, so a marker-prefixed footnote line reports width 0. indentationLevel strips markers first, then measures. The two paths disagree on the same line's indentation. Impact is small since footnotes are rarely indented.

(Refers to this code)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +61 to +74
const opensFootnote = isOoxmlContainer || isContainer;
if (!selfClosing) {
openTags.push({ name, isFootnote: opensFootnote });
}
if (opensFootnote) {
if (!selfClosing) footnoteDepth += 1;
return `${tag}${FOOTNOTE_MARKER}`;
}
if (
isWordParagraph ||
(footnoteDepth > 0 && (name === "li" || name === "p" || name === "w:p"))
) {
return `${tag}${FOOTNOTE_MARKER}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Unclosed footnote container mislabels all later paragraphs

markFootnoteTags raises footnoteDepth on an opening footnote container and only lowers it on a matching close. If a container is never closed in malformed markup, the depth stays positive and every later <li>/<p>/<w:p> is tagged role: "footnote". Balanced generator/Word/OOXML output avoids this; hand-authored or truncated HTML does not.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

seonghobae added a commit that referenced this pull request Aug 25, 2026
* feat: persist and explain Event Lineage channel evidence

Reconstruct already computed per-channel scores, but live Event Lineage
collapsed each edge to a fused score. Persist the active signals beside
each edge, return them on GET /api/lineage, and disclose exact inferred
values in the Buyer DAG.

* test(schema): retain project event migration fixture

* fix(frontend): keep admin panel behind authentication

* test: apply lineage evidence migration to API fixtures

* feat: route rebuild adjudication through orchestrator

* fix: preserve adjudication during PostgreSQL imports

* fix: order persisted lineage weights deterministically

* docs: record orchestrated lineage rebuild policy

* fix(frontend): preserve structured footnote roles

* fix(lineage): budget signal rounding tolerance

* fix(frontend): close HTML footnote containers reliably

* fix: hide empty footnote markers

* fix(lineage): offload synchronous reconstruction

* perf(lineage): bound channel evidence reads

* fix(frontend): bound footnote and anchor tag detection

* fix: close rebuild transaction before reconstruction

* fix(frontend): recognize wrapped footnote lists

* fix: release lineage pool during reconstruction

* fix(frontend): preserve structured footnote roles (#388)

* fix(frontend): preserve structured footnote roles

* fix(frontend): close HTML footnote containers reliably

* fix: hide empty footnote markers

* fix(frontend): bound footnote and anchor tag detection

* fix(frontend): recognize wrapped footnote lists

* fix(frontend): render markdown tables in post bodies (#389)

* fix(frontend): render markdown tables in post bodies

* fix(frontend): harden markdown table rendering

* fix(frontend): preserve separator-free OCR tables

* fix(frontend): confirm markdown tables before splitting prose

* fix: make lineage evidence disclosure interactive

* fix(frontend): preserve nested list indentation (#391)

* fix(frontend): preserve nested list indentation

* fix(frontend): indent block children in nested lists

* fix(db): make tenant settings migration replay-safe

* fix: bound live lineage adjudication work

* chore: nudge CI re-review (opencode-agent's prior REQUEST_CHANGES was against a transient coverage-evidence flake on this same head; later reruns of the same head passed, but opencode-review only posts once per head SHA)

* fix: guard corpus lineage rebuild against orchestrator failures; drop Buyer wording

- Wrap rebuild_lineage_from_pool() in main.py's /api/lineage/rebuild in the
  same except (HttpClientError, OSError) -> HTTPException(503) pattern used
  at this file's other orchestrator call sites, so a transient hiccup during
  a corpus-wide (up to 5,000-call) rebuild degrades cleanly instead of
  discarding the whole reconstruction as a raw 500. Adds a focused endpoint
  test covering the new 503 path.
- Reword the new ADR 0124 prose and CHANGELOG/CHANGELOG.d entries added by
  this PR to say "reader" / "Event Lineage DAG" instead of "buyer"/"Buyer",
  so this PR doesn't reintroduce naming PR #474 (ADR 0119) is retiring.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5M79L945DMyMs3sg5yJ14

* fix: allocate migration 0174 for lineage signals

* fix: close lineage evidence review gaps

* Batch Event Lineage persistence writes

* fix(frontend): restore shared OIDC return-URL helpers on login

The login button had regressed to a raw window.location.pathname +
window.location.search concat for the OIDC state.returnUrl, dropping
the hash fragment and the isSafeReturnUrl validation that
returnUrlFromLocation() already provides, and never called
rememberOidcReturnUrl() to persist a storage fallback for
restoreOidcReturnUrl() in main.tsx's onSigninCallback. Use the shared
helpers again, matching the existing recurring-bug pattern already
fixed elsewhere in this stack (the admin-panel-on-unauthenticated-branch
half of the same class of regression).

* fix: reconcile merged accessibility roles, script normalization, and migration-replay tests

Post-merge fixes surfaced by full test verification after merging
origin/main into this branch:

- LineageDag's SVG now uses role="group" (this PR's own accessibility
  fix for the interactive per-edge evidence buttons), superseding
  main's older role="img". Updated the tests that still queried
  role="img" for the lineage graph, and switched the ambiguous
  getAllByRole("group") assertions to precise `svg[role="group"]`
  queries since <details> (added by this PR's channel-evidence panel)
  also carries an implicit ARIA group role.
- postBodyDisplay.ts: adopted main's normalizeScriptText for <sup>/<sub>
  handling (real Unicode superscripts, later re-rendered by
  splitScriptRuns) instead of this PR's older ad hoc "^N" caret
  regex, and dropped the bare-marker FOOTNOTE_START heuristic main
  had already removed as a false-positive source (a bullet list
  starting with "*" was being misread as a footnote). Restored the
  FOOTNOTE_START constant only where still needed transitively, then
  removed it entirely once isMarkedFootnote (this PR's own
  container-aware footnote detection) proved sufficient on its own.
  Updated the one test still asserting the old "^1" caret text.
- migrate.sh's replay gate: this PR's test asserted the old explicit
  per-file allowlist main had already replaced with ADR 0166's general
  four-digit filename pattern. Updated the assertion to check the new
  mechanism covers 0103/0174 without individual entries.
- scripts/import_postgresql_posts.py's rebuild_lineage stub now
  accepts the llm= keyword this PR's own call site passes.
- lineage_persistence.py: added the missing docstring on the nested
  sort_key helper the AST docstring-coverage audit flagged.

Verified: backend `uv run pytest -q` 931 passed / 16 skipped / 0
failed (0:08:37); frontend `pnpm run lint`, `pnpm run build`, and
`pnpm exec vitest run` (245 passed / 245).

* fix: resolve post-merge test breakage from combined LineageDag changes

- LineageDag.test.tsx declared a module-level `graph` const twice
  (once for the channel-evidence tests, once for the hit-target
  tests), which the line-based merge could not detect since both
  additions landed in non-overlapping hunks. Rename the second to
  `nodeHitTargetGraph`.
- The merged LineageDag now keeps the lineage svg's `role="group"`
  (needed because its edges/nodes are interactive button-role
  descendants), so the mobile-scroll viewport test's `getByRole("img", ...)`
  query no longer matches; update it to `getByRole("group", ...)`.

* fix(lineage): persist estimated fusion weights

* fix(lineage): load weights for the active channel set

* fix: align lineage weights with budgeted channels

* fix: fail closed on adjudication errors

* fix(lineage): wire the adjudication client into corpus-wide rebuild

Issue #289: rebuild_lineage accepted no adjudication client, so the
optional LLM channel never contributed on the corpus-wide path. Add an
optional adjudication_client parameter: an available client adds llm to
the active-channel set (failing closed until a four-channel estimate
exists per ADR 0200) and reaches reconstruct as the reasoning channel;
a missing or unavailable client keeps the three-channel path. The
POST /api/lineage/rebuild endpoint now passes _adjudication_client().
Unavailable clients are normalized to None at this boundary so
reconstruct receives a single canonical unavailable value.

* fix(ask): retain lineage reconstruction profile

* fix(lineage): preserve worker retries and parser edge cases

* fix(ui): stop WorkspaceCalendar's fail-closed placeholder announcing as role=status

Its resolved empty/unavailable state carried role="status" like sibling
panels' transient loading text does, so mounting it inside the Board's
collapsed Advanced Review Tools details collided with every other status
region on the page (4 failing App.test.tsx assertions). RankingsPanel's
own resolved placeholders carry no ARIA role for the same reason -- only
the "Loading..." state announces.

* fix(measurement): reject duplicated lineage channels

* fix(lineage): name connection direction correctly

* fix(lineage): name follows direction correctly

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: seonghobae <seonghobae@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant