[publish 1-5] code + sheet static renderers#2047
Merged
Merged
Conversation
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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 |
Add packages/lib/src/publish/render-code-page.ts and render-sheet-page.ts: static, script-free HTML renderers for CODE and SHEET pages, sharing the same document shell + CSP as DOCUMENT pages. No syntax highlighting or client-side grid in v1 (Shiki is client-side; sheet is a plain <table> snapshot of evaluated values). - renderCodePage: HTML-escapes the raw code string into <pre><code>. - renderSheetPage: parses via sheets/parseSheetContent + evaluateSheet (reuses the sheet engine's own evaluation, no formula engine reimplemented), emits a static <table>; optional hasHeaders renders the first row as <th>. Malformed/unparseable content and parse/eval failures fall back to an empty-state message, never throw. 1-4 (renderDocumentPage) hasn't landed on pu/publish-any-page-type yet, so this also fills the missing shared pieces it depends on in document-shell.ts: buildDocumentCsp() (script-src 'none', the documents CSP contract) and renderDocumentShell() (doctype/head/CSP/typography assembly) — both renderers, and eventually renderDocumentPage, delegate to these instead of duplicating head assembly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JDNXDo1shhfCzWjUFLV1Ni
Rebased onto origin/pu/publish-any-page-type now that 1-4 (#2046) landed the canonical buildDocumentCsp() (packages/lib/src/canvas/csp.ts) and renderCanvasDocument's cspOverride param. - Deleted the parallel renderDocumentShell + duplicate buildDocumentCsp this PR had added to document-shell.ts (and their tests) — document-shell.ts is back to its 1-3 state (DOCUMENT_TYPOGRAPHY_CSS, documentStartsWithH1, wrapDocumentBody only). - render-code-page.ts / render-sheet-page.ts now mirror render-document-page.ts exactly: build escaped bodyHtml, wrapDocumentBody, prepend DOCUMENT_TYPOGRAPHY_CSS as a <style> block, delegate the whole head assembly to renderCanvasDocument with cspOverride: buildDocumentCsp() (from ../canvas/csp) and injectThemeBridge: false. - Both renderers now accept and pass through the same head-field inputs as renderDocumentPage (pageUrl, ogImageUrl, ogDescription, description, robots, faviconHref, faviconBaseUrl, lang, allowedAssetHosts). Body-building, HTML-escaping, and sheet parsing/evaluation logic is unchanged from the prior revision; shell-related test assertions updated to match, plus new coverage for OG/canonical pass-through. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JDNXDo1shhfCzWjUFLV1Ni
2witstudios
force-pushed
the
pu/pub-1-5-code-sheet
branch
from
July 13, 2026 04:49
3f535e7 to
d977f0b
Compare
2witstudios
added a commit
that referenced
this pull request
Jul 13, 2026
…de, sheets) (#2055) * feat(publish): neutralize leftover dashboard links in published documents (#2033) At publish time, links to published pages get rewritten to public URLs, but anchors pointing at UNPUBLISHED pages keep their in-app /dashboard/{driveId}/{pageId} href — a dead, login-walled link for an anonymous visitor. Add a pure string transform that converts those leftover anchors to inert <span>s, preserving inner HTML and the data-mention-type/data-page-id attributes so mention-chip CSS still applies. Published https:// URLs, fragments, non-dashboard hrefs, and malformed/unclosed anchors are left byte-identical; the transform is idempotent. Claude-Session: https://claude.ai/code/session_01FYot8EYcBEA77oaNW3mZgo Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(publish): respect quoted attribute values when matching dashboard anchors (#2036) A valid anchor may carry a quoted attribute containing '>' after the href (e.g. <a href="/dashboard/d/p" title="2 > 1">x</a>). The opening-tag matcher treated that '>' as the end of the tag, so neutralizing emitted corrupted markup (<span> 1">x</span>). Make the attribute region quote-aware — quoted values may contain '>' — while still rejecting bare '<'/'>' outside quotes so malformed/unclosed anchors stay untouched. Alternatives start with disjoint characters, so backtracking stays linear (verified against 100k-char pathological inputs). Addresses the P2 review finding on #2033. Claude-Session: https://claude.ai/code/session_01FYot8EYcBEA77oaNW3mZgo Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(publish): tokenizer-grammar attribute matching in neutralize-dashboard-links (#2037) The quote-aware fix stopped at the opening-tag matcher; deeper review found the same bug class one level down plus new hazards it introduced: - HREF_RE/MENTION_ATTR_RE scanned the raw attribute region quote-blind, so `href=` or `data-page-id=` text inside another attribute's quoted value was taken as real — destroying live links, letting dead dashboard links survive behind a decoy, and fabricating mention attributes on the emitted span. - The attr region treated ANY quote as opening a value, so an unclosed quote or a quote in name position let one match span multiple tags and delete rendered content. - Each complete `<a …>` with a missing close made the lazy inner group scan to end-of-input: measured 6.2s CPU on 1MB of malformed input. Rework: the attribute region now follows the HTML tokenizer's grammar — a run of name(=value)? tokens where quotes open a value only after `=` and a quoted value may contain `>` but never `<` — so a match can never cross a tag boundary; attributes are extracted by sequentially tokenizing the validated region (one shared value grammar). Inner content refuses to cross another `<a` opening, so an unclosed anchor can't steal a later anchor's close tag and failed scans stop at the next anchor (1MB pathological input: 6.2s -> 5ms). Hrefs are trimmed like browsers trim URL attributes, and a `/dashboard/` substring pre-filter skips the pass for documents with no dashboard links. Grammar violations never match — when corrupt output and a missed anchor are the only options, the module prefers the miss. Claude-Session: https://claude.ai/code/session_01FYot8EYcBEA77oaNW3mZgo Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * [publish 1-2] sanitize-document-html pure module (#2034) * feat(publish): sanitize-document-html pure module Published DOCUMENT pages must never execute author scripts (unlike CANVAS, whose isolation is by origin). CSP (script-src 'none') is the enforcement layer; this is the hygiene layer on TipTap-authored HTML: - <script>/<style>/<iframe>/<object>/<form> removed with content (any casing, junk-padded close tags, unclosed at EOF); <embed>/<base>/<meta>/<link> tags removed - on* attributes removed, element kept - href/src with javascript: or data:text/html schemes removed (case/whitespace/entity tricks normalized for the check only) Quote-aware tag tokenizer with the (?=[\s/>]) delimiter lookahead from canvas/render-document.ts (so <script-template> survives and quoted ">" can't end a tag early), looped to a fixpoint so removals can't reassemble surrounding text into a new executable tag. Pure function, idempotent, benign TipTap output passes through byte-identical. Adds the packages/lib exports + typesVersions entries for the new publish/ subpath. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wrz5eTSEE6z3QxhUgW6iVP * fix(publish): decode / entities before URL scheme checks data:text/html hrefs slipped past hasDangerousScheme because the minimal entity decoder handled :/&tab;/&newline; but not /. ASCII letters have no named entities, so colon + sol + whitespace named entities plus numeric references make the decode complete for the two denied scheme prefixes. Codex review P2 on PR #2034. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wrz5eTSEE6z3QxhUgW6iVP * test(publish): review-pass hardening — sticky tag-name regex, coverage gaps - TAG_NAME now a sticky regex matched in place (no per-tag slice) - Tests: unquoted javascript: href, SVG xlink:href, /-separated on* attrs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wrz5eTSEE6z3QxhUgW6iVP --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat(lib): add required publishable capability to page-type configs (#2040) Publishing is currently CANVAS-only via a hard gate in publish-page.ts. This adds a required `publishable: boolean` to PageTypeCapabilities so future page types must decide publishability at declaration time, plus isPublishablePageType()/getPublishablePageTypes() helpers. Publishable: CANVAS, DOCUMENT, CODE, SHEET (content in pages.content). Not publishable: FOLDER, CHANNEL, AI_CHAT, FILE, TASK_LIST, MACHINE (content lives in chat/tasks tables, object storage, or machine state). Claude-Session: https://claude.ai/code/session_01B3WTcZGWpLBszpdkuqNpan Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat(publish): generalize PublishControls into ViewHeader for all publishable page types (#2045) Move CanvasPublishControls to content-header/PublishControls (behavior and props unchanged) and mount it from ViewHeader, gated by isPublishablePageType(page.type). contentDirty is wired through the same useDocumentManagerStore selector pattern as DocumentSaveStatus, so the Stale/Update flow works for every editor that flows through the document manager, not just canvas. The CanvasPageView mount is removed — single mount point, hard cutover. Claude-Session: https://claude.ai/code/session_019hKLnVwZfHqDxhT6X7vm2L Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * [publish 1-3] document shell + typography CSS (#2035) * [publish 1-3] document shell + typography CSS Add packages/lib/src/publish/document-shell.ts: DOCUMENT_TYPOGRAPHY_CSS (.ps-document-scoped blog-post typography with hardcoded light values and a prefers-color-scheme dark override — published pages have no app CSS vars), documentStartsWithH1 (tolerates leading whitespace/comments), and wrapDocumentBody (article shell with escaped <h1> title header, suppressed when the body already leads with an h1). Mention anchors (a[data-mention-type]) render as subtle inline chips. Creates the publish/ module dir + exports entry (leaf 1-2 not landed yet). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011NfYFfxkMtytdp4zAhPyNZ * fix(publish): dark page canvas + UA margin reset in document shell Two review fixes: published pages load no app stylesheet, so (1) dark mode must style the page canvas, not just the article foreground — :root color-scheme + explicit body backgrounds (#ffffff / #0d1117) are the one deliberate exception to .ps-document scoping; and (2) browser default element margins are zeroed via a low-specificity :where() reset placed before the sibling rhythm rule, so the rhythm isn't doubled by UA margins (and can't be beaten by a higher-specificity reset). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011NfYFfxkMtytdp4zAhPyNZ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * [publish 1-4] document CSP + renderDocumentPage (#2046) * [publish 1-4] document CSP + renderDocumentPage - buildDocumentCsp() in csp.ts: baseline directives with script-src 'none' and form-action 'none' (documents never run author scripts). - renderCanvasDocument gains optional cspOverride, used verbatim instead of buildBaselineCsp(...) when present; unchanged output when absent. - renderDocumentPage() in publish/render-document-page.ts: sanitizes author HTML, wraps it in the document shell, prepends the shared typography CSS, and delegates to renderCanvasDocument with cspOverride: buildDocumentCsp() and injectThemeBridge: false. * [publish 1-4] add exports entry for publish/render-document-page apps/web (leaf 1-7) will import renderDocumentPage; without this entry the subpath import hits TS2307. Mirrors the exports + typesVersions declaration of the sibling ./publish/sanitize-document-html entry exactly. * [publish 1-5] code + sheet static renderers (#2047) * [publish 1-7] per-type render branch: publish any page type (#2052) publishCanvasPage/renderNotFoundPageHtml gate on isPublishablePageType (from getPublishablePageTypes) instead of a hardcoded CANVAS check. Extracts a shared preparePublishContent() that branches per page type: - CANVAS: unchanged (rewriteCanvasAssets -> rewriteInterPageLinksForDrive -> extractAndStripOgMeta -> renderPublishedPage), byte-identical. - DOCUMENT: markdown->HTML via marked (contentMode-gated) -> same asset/ link rewriting -> neutralizeDashboardLinks -> extractAndStripOgMeta -> renderPublishedDocument. - CODE/SHEET: raw content passed straight to renderPublishedCode/ renderPublishedSheet (not HTML, no rewriting needed). Adds renderPublishedDocument/renderPublishedCode/renderPublishedSheet wrappers in render-published.ts with the same allowedAssetHosts plumbing as renderPublishedPage. Claude-Session: https://claude.ai/code/session_015j7689HB9YWSyBL2PpfeS2 Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * fix(publish): OG-image state sync, XSS/CSS/UI review findings - publishCanvasPage now returns the effective (server-resolved) SEO overrides; PublishControls reads settings back from the response instead of caching its own request payload, fixing a bug (flagged by Codex review) where publishing via an uploaded OG image file cached a blank ogImageUrl locally and could re-clear the resolved CDN URL on the next save. - sanitizeDocumentHtml also blocks data:image/svg+xml href/src values (SVG documents can carry <script>, unlike inert raster data: URIs), closing a gap in the published-DOCUMENT script-src 'none' hygiene layer. - HeaderPublishControls is now keyed on pageId so navigating between publishable pages remounts it instead of leaking local UI state (e.g. an open Publish Settings dialog) across pages. - Mention-chip CSS now also matches the neutralized `<span data-mention-type>` markup that unpublished-target mentions render as, not just `<a data-mention-type>` — neutralizeDashboardLinks already preserved the attribute, the selector just never matched it. - CODE pages no longer derive a fallback SEO description from raw source text (deriveDescription's HTML-tag stripping mangled generics/comparisons); left empty like SHEET's already-accepted non-prose fallback. - buildDocumentCsp/buildBaselineCsp now share one asset/font CSP prefix constant instead of two independently maintained literals. Ralph-loop convergence pass on PR #2055: CI was already green with no unresolved review threads; these are proactive findings from an 8-angle review (correctness, removed-behavior, cross-file, reuse, simplification, efficiency, altitude, conventions) plus the one existing Codex bot comment. All 692 apps/web + 402 packages/lib tests pass; typecheck, build, and lint are green. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
packages/lib/src/publish/render-code-page.ts(renderCodePage) andrender-sheet-page.ts(renderSheetPage): static, script-free HTML renderers for CODE and SHEET pages. No syntax highlighting or client-side grid in v1 —renderCodePageHTML-escapes the raw code string into<pre><code>;renderSheetPageparses viasheets/parseSheetContent+evaluateSheet(reusing the sheet engine's own evaluation, no formula engine reimplemented) and emits a static<table>, with an optionalhasHeadersflag to render the first row as<th>. Malformed/unparseable sheet content — and any parse/eval failure — falls back to an empty-state message; both renderers never throw.render-document-page.tsexactly (1-4, [publish 1-4] document CSP + renderDocumentPage #2046): build escapedbodyHtml→wrapDocumentBody→ prependDOCUMENT_TYPOGRAPHY_CSSas a<style>block → delegate the whole head assembly (SEO/OG/Twitter/JSON-LD, favicon, CSP<meta>) torenderCanvasDocumentwithcspOverride: buildDocumentCsp()(from../canvas/csp,script-src 'none') andinjectThemeBridge: false. Both accept and pass through the same head-field inputs asrenderDocumentPage(pageUrl,ogImageUrl,ogDescription,description,robots,faviconHref,faviconBaseUrl,lang,allowedAssetHosts).origin/pu/publish-any-page-typeafter [publish 1-4] document CSP + renderDocumentPage #2046 merged; no parallel shell or duplicate CSP builder —document-shell.tsis untouched from its 1-3 state.Test plan
bun run typecheckcleanbun run build(packages/lib) cleanturbo run test --filter=@pagespace/lib— 8062 passed, 0 failed (24 tests acrossrender-code-page.test.ts/render-sheet-page.test.ts, including OG/canonical pass-through and CSP-override coverage)anytypes<scriptin output; CSP meta always carriesbuildDocumentCsp()'sscript-src 'none'verbatim🤖 Generated with Claude Code
https://claude.ai/code/session_01JDNXDo1shhfCzWjUFLV1Ni