mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 06:19:49 +00:00
c7cd363173
* fix(web): a renamed item's links follow the new title in collab-edited docs (BUG-3315 U2) A tab loads [[TASK-1]] as a link whose TEXT is the title at that moment, and the text lives in the collaborative document from then on. After a rename, the next save compared it with the current title only and wrote [[TASK-1|Old Title]], pinning the old title for good. The load now marks a link that follows the title with the link mark's EXISTING `title` attribute, set to the text as loaded, so no schema change. It is set only for a link with no explicit override whose text is the title. The save treats text still equal to the marker as following the title ([[REF]]). An edited text, or a deliberate [[REF|X]] (which loads without the marker), is kept as an override. The marker never reaches stored content: markdownToWikiLinks consumes it, and cleanBrokenLinks, the last step of every save pipeline, strips any that is left, because those pipelines skip markdownToWikiLinks on an empty index. SafeLink keeps a same-origin link's `title` out of the DOM, so the stale title is never shown as a tooltip. Documents already in an op-log carry no marker and keep today's behaviour until they are next loaded. The characterization test from #1684 is flipped to the fixed answer. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(web): keep the save's link match as wide as before the marker (BUG-3315 U2, codex r1) The marker regex narrowed the href to exclude whitespace, so a link whose last segment had a space stopped converting. The path is matched lazily instead, ahead of the optional title, and the cleanBrokenLinks strip the same way. A guard leg pins it. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(web): the follows-title marker is prefixed, so a user's link title is never read or stripped (BUG-3315 U2, codex r2) cleanBrokenLinks stripped EVERY same-origin link title, so a user-authored [x](/path "tip") lost its tooltip on save. markdownToWikiLinks would also read that title as a marker. The marker is now "pad-follows-title:<text>". Only a title with that prefix is read, stripped, or hidden from the DOM. A titled internal link a user wrote is left exactly as before U2, which never converted it. The prefix lives in its own module (utils/followsTitle.ts), so safeLink, which the materializer bundles, imports nothing DOM-dependent. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(web): a follows-title marker straight from the load still reads as the title (BUG-3315 U2, codex r3) Through the editor the marker comes back as the serializer writes a title (only " escaped), so it is compared raw. Straight from wikiLinksToMarkdown it still carries the load's \\, \" and \& escapes, so it is also compared with those undone. The ')' findings of the same round did not reproduce; A)B, f(x) = y and q"uote) convert and strip correctly on both paths. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * docs(web): the follows-title prefix is reserved (BUG-3315 U2, codex r4) Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(web): the follows-title marker is stripped from every link at save, whatever its href (BUG-3315 U2) The lead's gate before the GO: the marker never reaches stored markdown on any path where a marked link does not convert back to [[...]]. A leg now covers ten paths through real save pipelines: target deleted, empty index, restricted viewer, hard break in the text, partial bold, markdown table, HTML-serialized table, and a link whose URL the user edited to an external address (target present, and empty index). It asserts no 'pad-follows-title' substring, with a guard that each document really carried the marker. The URL-edit path LEAKED: the mark keeps its attributes, and both strips matched only same-origin hrefs. The final strip in cleanBrokenLinks now removes the reserved-prefix title from any link. Red variants: that strip off alone leaks 3 paths, all strips off leaks 9. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(web): the marker strip touches only a real link's title, never code or prose (BUG-3315 U2, codex) The broadened strip removed marker-shaped text wherever it appeared, so a doc that mentions the marker (a code span, a fenced block, escaped-bracket prose) lost text on save. Marker handling in markdownToWikiLinks and the cleanBrokenLinks strip now skip fenced code blocks and inline code spans (codeRanges), and the strip needs an unescaped '](' with a space-free href. Four legs pin it: each goes red when its guard is removed. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(web): marker strip counts backslash parity; code scan is linear (BUG-3315 U2, codex) A link text ending in a backslash serializes as [a\\](...): its ']' follows a backslash, but an even number of them, so it is a real link. The strip's lookbehind now requires even parity, where it used to keep the marker. The inline-span scan rescanned per opening run (267ms for 40,000 alternating runs, 18x for 10x the input). It is now one pass with per-length pointers and binary-searched ranges (9.6ms, 98ms at 400,000). The fence finding of the same round did not reproduce: a closing fence with trailing spaces is recognized. Legs pin both fixes. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(web): an inline code span never pairs across a fenced block (BUG-3315 U2, codex r3) Backtick runs are paired only inside their own gap between fences, which also keeps every code range disjoint, as the binary search requires. A leg pins it; it goes red when the gap is ignored. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * test(web): pin that a run inside a code span opens no second span (BUG-3315 U2, codex r4) Codex r4 read the span pairing as able to overlap spans of different lengths. It cannot, because the scan resumes after the closing run, so the finding did not reproduce. This leg pins it, and goes red when the resume is removed. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC
Pad Web UI
SvelteKit 2 + Svelte 5 frontend for Pad, compiled to static files and embedded into the Go binary.
Development
npm install
npm run dev # Dev server at localhost:5173 (proxies API to localhost:7777)
npm run build # Production build to build/
npm run check # Type checking with svelte-check
When developing, run the Go backend separately with make dev from the project root.
Building for Production
Do not build in isolation. Always use make build from the project root — this builds the web frontend, then compiles the Go binary with the build output embedded via //go:embed.
Stack
- Svelte 5 with runes (
$state,$derived,$effect) - SvelteKit 2 with
adapter-static(SPA mode) - Tiptap block editor with markdown round-trip
- svelte-dnd-action for drag-and-drop in board/list views
- SSE for real-time updates
- TypeScript throughout
Structure
src/
routes/ SvelteKit pages
+layout.svelte App shell (sidebar + main)
+page.svelte Landing/redirect
[workspace]/
+page.svelte Dashboard (collections, phases, activity)
+layout.svelte SSE connection per workspace
[collection]/
+page.svelte Collection view (board/list)
[collection]/[item]/
+page.svelte Item detail + editor
conventions/ Purpose-built conventions page
playbooks/ Purpose-built playbooks page
settings/ Workspace settings
lib/
api/client.ts HTTP API client
components/
layout/ Sidebar, navigation
editor/ Tiptap editor, raw markdown editor
fields/ FieldEditor, relation picker
items/ ItemCard, ItemDetail
collections/ BoardView, ListView
common/ StatusBadge, badges, modals
search/ CommandPalette
stores/ Svelte 5 reactive stores
workspace.svelte.ts Workspace state
collections.svelte.ts Collection + item state
ui.svelte.ts Sidebar, mobile state
types/index.ts TypeScript types and constants
app.css Global styles and design tokens