Skip to content

fix(publish) + feat(published): public-page polish + cross-ref/citation strip - #414

Merged
koolamusic merged 7 commits into
mainfrom
pr-5/publish-origin-fix
May 26, 2026
Merged

koolamusic merged 7 commits into
mainfrom
pr-5/publish-origin-fix

Conversation

@builder-25

Copy link
Copy Markdown
Contributor

Summary

Operator-reported bug → triage → root cause → fix → polish, all on the public-reader surface (/p/[nanoid]).

The trigger: published a wiki on a two-host deployment (Wiki at gatsby.up.railway.app, Core at robincore-production-xxx.up.railway.app). The Settings tab toggle flipped fine, but the Open button sent users to https://robincore-production-xxx.up.railway.app/p/<slug> — Core's hostname, which doesn't serve the public page. Investigating uncovered more issues on the reader surface (logo, GitHub star, duplicate title, dead cross-refs, citation markers leaking) so I rolled the polish into the same PR. Each commit is independently squashable.

1. wikis.publishedOrigin captures the wrong URL on multi-host setups

Root cause — core/src/routes/wikis.ts:686 derives origin from new URL(c.req.url).origin. When the frontend POSTs /api/wikis/<id>/publish via Next.js's rewrites() (or any reverse proxy), Core receives the rewritten request and c.req.url resolves to Core's hostname. The stored value then leaks into MCP responses, the Settings modal's Open/Copy buttons, and any consumer that builds /p/<slug> URLs from the field.

Fix — two commits:

  • core/src/routes/wikis.ts — prefer X-Forwarded-Host / X-Forwarded-Proto when present (proxies pass these through). Fall back to existing c.req.url derivation so same-origin setups keep working.
  • wiki/src/components/layout/AddWikiModal.tsx — Open/Copy buttons construct the URL from window.location.origin, ignoring the stored field. The browser always knows its own host, so trust that.

DB rows published before this PR keep the stale value but no longer affect the UI. Republishing rewrites the field correctly.

2. Public-page polish

The published /p/[nanoid] page is the read-only reader surface anyone-with-the-link can open. A few items felt off:

  • Robin logo replaces the abstract infinity glyph that was top-left (matches onboarding + brand surface).
  • Star on GitHub removed — /p/<slug> is a reader page, not a developer landing page.
  • "Powered by Robin Wiki" in the footer is now a link to withrobin.ai/knowledge so the footer line itself routes back to the marketing page.
  • Infobox hidden — the side-panel infobox is an editor affordance, visually loud on the read-only surface.
  • Duplicate title stripped — regen emits a {Type}: {Name} heading at the top of the wiki body. The public page already renders {wiki.name} as the H1, so the body's title appeared twice. Strip rule: first heading- or bold-only line whose plaintext contains the wiki name. Narrow enough that arbitrary prose mentioning the title isn't clipped.
  • ES2017-safe regex — the strip-rule originally used the /s (dotAll) flag for spanning newlines; the wiki workspace's tsconfig targets ES2017, which predates dotAll, so next build rejected it. Swapped .*? for [\s\S]*? and dropped the flag. Same behaviour.

3. Cross-refs and citations on the public reader

The wiki body contains [[wiki:slug]], [[fragment:slug]], [[entry:slug]], [[person:slug]] cross-refs that the authenticated app resolves into clickable anchors to /wiki/<id>, /fragments/<id>, etc. The public page has no such routes — the links 404, and the inline citation markers [1], [2] point to source fragments that the reader can't open either.

Fix — core/src/lib/published-content.ts + core/src/routes/published.ts:

  • For each [[wiki:slug]] token, look up whether the target is itself published. If yes, emit a native markdown link [Label](/p/<their-slug>). If no, drop the link and keep the label as plain text.
  • For [[fragment:slug]], [[entry:slug]], [[person:slug]] — always drop to plain label (no public route exists for these).
  • Strip [N] citation markers entirely.

Content returned to the client is now self-contained markdown — every surviving cross-ref is a real anchor, the refs field is empty, and MarkdownContent renders natively. No client-side change needed beyond the existing renderer.

Test plan

  • On a two-host deploy (Wiki ≠ Core hostname), publish any wiki via Settings tab → click Open → URL points at the Wiki host, not Core.
  • Public /p/<slug> page: logo is Robin (not infinity), no GitHub button, footer line is a link, no infobox.
  • If the wiki body starts with ## Belief: <name> or similar, the published page renders the H1 once (chrome) without a duplicate underneath.
  • Body has no [1], [2] markers.
  • [[wiki:public-target]] renders as a clickable link to /p/<their-slug>.
  • [[wiki:private-target]] renders as plain text (the label, no link).
  • [[fragment:foo]], [[entry:foo]], [[person:foo]] all render as plain text.

🤖 Generated with Claude Code

@builder-25
builder-25 requested a review from koolamusic as a code owner May 26, 2026 14:19
…shedOrigin

wikis.publishedOrigin is stamped by the publish service with whichever origin Core saw on the publish request. When the publish toggle is invoked through a frontend that proxies /api/* to the Core server (Next.js rewrite, reverse proxy, etc), that origin resolves to Core's own hostname, not the user-facing frontend host, so the modal's Open/Copy buttons send users to a URL that does not serve /p/[nanoid].

Symptom (Railway 2-service deploy): publish toggle flips fine, but Open opens https://robincore-production-xxx.up.railway.app/p/<slug> instead of https://gatsby.up.railway.app/p/<slug>. The Core service does not serve the public wiki page; it lives in the frontend workspace.

Repro: deploy core/ and wiki/ on separate hosts so that the wiki frontend proxies /api/* to the core service. Publish any wiki via the Settings tab. Click Open. URL points at the core host.

Root cause: core/src/routes/wikis.ts:686 derives origin from new URL(c.req.url).origin (i.e. whatever URL Core itself sees). The frontend re-uses that field in AddWikiModal.tsx:943,961 to build the Open/Copy targets. The frontend already knows its own origin (window.location) and should use that for URL construction. The wikis.publishedOrigin column remains useful as audit but should not be treated as authoritative for user-facing URLs.
Reverse proxies (Next.js rewrites, Railway edge, nginx) forward the original user-facing host as X-Forwarded-Host. Without honouring it, multi-host Robin deployments store Core's hostname in wikis.publishedOrigin, which then leaks into MCP responses and any consumer that constructs /p/<slug> URLs from the stored origin. Fall back to the existing c.req.url derivation when the header is absent so same-origin setups keep working.
- Swap the abstract infinity glyph for Robin's logo (matches the onboarding screen and the rest of the brand surface).
- Remove the 'Star on GitHub' button; the published page is a reader surface, not a developer landing page.
- Wrap 'Powered by Robin Wiki' footer text in an anchor to www.withrobin.ai/knowledge so the footer line is itself a route back to the marketing page.
The infobox is an editor affordance, useful when working inside the wiki tool but visually loud on a read-only public page. Drop it from the published render path and trim the now-unused imports + type field.
Regen emits a leading {Type}: {Name} heading at the top of the wiki body. The published page already renders {wiki.name} as the page H1, so the body's title rendered a second time underneath. Strip the first heading- or bold-only line if its text contains the wiki name; leave arbitrary paragraphs alone to avoid clipping legitimate prose.
Wiki workspace's tsconfig sets target=ES2017 which predates the regex dotAll (/s) flag. Use [\\s\\S]*? in place of .* so the matcher still spans newlines without depending on a flag that fails the production build.
Public readers can't open /wiki/<id>, /fragments/<id>, /people/<id>, etc; those routes live in the authenticated app. Walk every [[kind:slug]] token in the wiki body and:

- For [[wiki:slug]] whose target is itself published, emit an inline markdown link [Label](/p/<their-slug>). Otherwise drop the link and keep the label as plain text.
- For [[person:slug]], drop the link and keep the label. Person names usually read as natural prose.
- For [[fragment:slug]] and [[entry:slug]], remove the token entirely along with any leading whitespace. Fragment titles often carry a date prefix (260514 - ...) which reads as inline noise mid-sentence when rendered as plain text on the public page.

Inline [N] citation markers are stripped outright since the source fragment isn't reachable from the public surface anyway.

Wiki names are free-form user content (e.g. "Q4 [2026] Plan"), so the markdown link label is escaped to keep ']' / '[' / '\\' from breaking the surrounding link syntax.

Covered by core/src/lib/published-content.test.ts.
@koolamusic
koolamusic force-pushed the pr-5/publish-origin-fix branch from b75fe6f to edc52a4 Compare May 26, 2026 15:45
@koolamusic
koolamusic merged commit 169af9b into main May 26, 2026
3 checks passed
@koolamusic
koolamusic deleted the pr-5/publish-origin-fix branch June 11, 2026 13:07
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.

2 participants