Skip to content

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

Merged
seonghobae merged 8 commits into
feat/post-body-footnote-displayfrom
feat/markdown-table-display
Aug 21, 2026
Merged

fix(frontend): render markdown tables in post bodies#389
seonghobae merged 8 commits into
feat/post-body-footnote-displayfrom
feat/markdown-table-display

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • render pipe-delimited Markdown tables in ordinary post bodies
  • preserve empty cells and existing image OCR table rendering
  • keep the source table as a semantic unit before rendering

Verification

  • pnpm run test -- --no-file-parallelism --maxWorkers=1 (147 passed)
  • pnpm run lint
  • pnpm run build
  • git diff --check

This PR is stacked on #388 and must be revalidated after its parent changes.


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: 0d684b0e-e68e-4056-8f6e-7045861ba972

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 0d8b6f4 after the current-head repair. It now requires a Markdown separator row before pipe tables, applies table rendering in the persisted-structure path, and gives rendered tables stable React keys. Frontend lint, 149 tests, build, and diff-check pass. Review only this exact head for parsing false positives, buyer rendering, accessibility, and stack boundaries; report actionable findings.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Re-review exact current head 7b551bf after the normal stack update. PR #389 now includes current PR #388 head ee5882a through a regular merge commit and retains the Markdown table hardening. Verify parent-child boundaries and frontend behavior at this exact head; report actionable findings only.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review Please re-review exact head 388037648ca125ccd569035b520ed8d2eea87fbc. The three current-head review findings are covered: stable table key, persisted-unit Markdown rendering, and separator-required prose detection while retaining separator-free OCR tables. Do not approve superseded heads.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Please review exact current head 3880376. A concurrent remote commit now preserves separator-free pipe rows only for persisted OCR image evidence while Markdown source text still requires an explicit separator row; stable React keys and persisted-structure rendering remain fixed. Frontend lint, 150 tests, build, and diff-check pass. Review parser boundaries, buyer display, and accessibility at this exact head; report actionable findings.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Exact-head verification

Current head: 388037648ca125ccd569035b520ed8d2eea87fbc.

  • pnpm run test: 14 files, 150 tests passed.
  • pnpm run lint: passed.
  • pnpm run build: TypeScript and Vite production build passed.
  • pnpm run build-storybook: Storybook build passed.
  • OCR image evidence retains separator-free table parsing; source Markdown and persisted semantic text require a separator row, preventing pipe-delimited prose false positives.
  • No force push or protection bypass was used.

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review Please review restacked exact head 0c2f706008f9888f3ed24ee981029c2b83f3b796. It includes parent PR #388 exact head 1b680a27e6eaca544f1d99512e31220278c43110 plus the Markdown/OCR table fixes. Frontend exact tree: 152 passed; lint, build, and Storybook build passed.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Please review exact current head 0c2f706. The branch now includes current #388 head 1b680a2 via a normal merge, retaining Markdown-table hardening and separator-free OCR support. Frontend lint, 152 tests, build, and diff-check pass. Review exact parent-child boundaries and buyer-visible parser behavior; report actionable findings only.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review Please re-review exact head 5118500c8e1adf05e37da498558ef516ac9f6959. Candidate pipe rows are now buffered until a header separator proves a Markdown table; stray and consecutive pipe prose retain prior paragraph semantics. Frontend exact tree: 154 passed; lint, build, and Storybook build passed.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Exact-head verification

Current head: 5118500c8e1adf05e37da498558ef516ac9f6959.

  • pnpm run test: 14 files, 154 tests passed.
  • pnpm run lint: passed.
  • pnpm run build: TypeScript and Vite production build passed.
  • pnpm run build-storybook: Storybook build passed.
  • git diff --check: passed.
  • Candidate pipe rows now remain in the surrounding paragraph unless a valid Markdown separator follows the header; separator-free OCR remains limited to image evidence.

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

@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 1 new potential issue.

Open in Devin Review

Comment on lines +197 to +204
if (trimmed.includes("|")) {
const cells = trimmed.replace(/^\|/, "").replace(/\|$/, "").split("|");
if (cells.length >= 2 && cells.some((cell) => cell.trim())) {
pipeTableRows.push(line);
continue;
}
}
if (pipeTableRows.length > 0) flushPipeTableRows();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Pipe-bearing list items merge into one paragraph

A line containing | with two or more non-empty cells is accumulated as a table-candidate row and continues, bypassing the LIST_ITEM_START flush at postBodyDisplay.ts. When these lines do not form a table (no separator row), flushPipeTableRows appends them back to lines (postBodyDisplay.ts), joining them into one paragraph. Consecutive plain-text list items whose text contains a pipe (e.g. - Owner | Alice / - Reviewer | Bob on single-newline lines) previously stayed separate via the list-item flush; they now collapse into a single run-on paragraph. No test covers this.

Open in Devin Review

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

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Please review the exact current head 5118500. Local verification passed 154 frontend tests, lint, production build, and diff-check. Verify the current diff and hosted Checks, then leave a formal review verdict.

@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review 재검토 요청: exact head 788bacd. 최신 PR #388 부모와 동시 wrapper-footnote 수정까지 보존해 재적층했고 frontend lint, 155 tests, production build, Storybook build가 통과했습니다.

@seonghobae
seonghobae merged commit 778c5df into feat/post-body-footnote-display 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 1 new potential issue.

Open in Devin Review

Comment on lines +181 to +193
const flushPipeTableRows = () => {
const hasSeparator = pipeTableRows.some((row) => {
const cells = row.trim().replace(/^\|/, "").replace(/\|$/, "").split("|");
return cells.length >= 2 && cells.every((cell) => /^\s*:?-{3,}:?\s*$/.test(cell));
});
if (pipeTableRows.length >= 2 && hasSeparator) {
flush();
paragraphs.push(pipeTableRows.map((row) => row.trim()).join("\n"));
} else {
lines.push(...pipeTableRows);
}
pipeTableRows = [];
};

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: Separator accepted anywhere when grouping, required at row 1 when rendering

flushPipeTableRows groups pipe lines into a table paragraph when any row is a separator, but parsePipeDelimitedTable (frontend/src/PostBody.tsx:18) only renders when the separator is at row index 1. A table whose separator sits elsewhere is grouped, fails to parse, then renders as one <p> with newlines flattened to spaces. Affects only malformed Markdown tables.

Open in Devin Review

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

@seonghobae

Copy link
Copy Markdown
Contributor Author

@claude Please review the exact PR head 788bacd (the PR ref is distinct from the moving branch tip; verify the PR ref directly). Exact-head local verification passed 21 focused backend tests, 155 frontend tests, lint, production build, and diff-check. Verify current hosted Checks and stack base before a formal review verdict.

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