Skip to content

feat: shared pseudoscript-lsp-core — stdio + LSP-over-wasm, one API#12

Merged
JonathanTurnock merged 65 commits into
mainfrom
feat/ide-lsp-completion
Jun 2, 2026
Merged

feat: shared pseudoscript-lsp-core — stdio + LSP-over-wasm, one API#12
JonathanTurnock merged 65 commits into
mainfrom
feat/ide-lsp-completion

Conversation

@shockwave-bot
Copy link
Copy Markdown
Contributor

@shockwave-bot shockwave-bot Bot commented Jun 1, 2026

Closes #10. Closes #13. Single MR.

The web IDE now sources all language intelligence from the shared compiler core (via wasm), instead of reimplementing pieces in JavaScript. Plus an automated test suite.

1. Completion (was: context-free JS provider)

  • Scope fix (governing_trigger): a partial identifier under the caret no longer hijacks the trigger, so ./::/:/#[ contexts stay scoped.
  • Engine extracted to pseudoscript-model::complete (neutral types, byte offsets); pseudoscript-lsp and pseudoscript-wasm are thin adapters. IDE calls the wasm completion export.

2. Semantic highlighting (was: hand-written StreamLanguage tokenizer)

  • Extracted the AST-aware token engine into pseudoscript-model::semantic (SemKind/SemToken, byte offsets). pseudoscript-lsp delta-encodes to lsp_types; pseudoscript-wasm exports semantic_tokens.
  • The IDE decorates these tokens (data-sem marks). The JS keyword lists, regex token rules, and HighlightStyle are deleted — no more drift from the compiler's lexer. StreamLanguage is kept only for comment-toggle.

3. Folding (was: blocks.js brace-scan)

  • Extracted to pseudoscript-model::fold; pseudoscript-wasm exports folding_ranges. The IDE folds AST-accurate ranges; blocks.js removed.

Formatting, diagnostics, hover, definition, references, and outline were already wasm-backed (audit in #13). Highlight colours stay client-side (presentation). Rename/inlay/doc-highlight are absent features, not reimplementations — out of scope.

4. Test suite (data-testid throughout, no brittle selectors)

  • Vitest + Testing Library: offset-conversion unit tests; FileTree component test asserting the data-testid contract.
  • Playwright e2e (headless): highlighting roles, completion scoping + prefix-narrowing scope fix, compiler-driven folding — driven via data-testid/data-sem.
  • Storybook (@storybook/sveltekit): FileTree stories.
  • data-testid added to file rows, editor host, sample cards.
  • CI: new test workflow runs Rust (fmt/clippy/test) + web-ide (vitest, build, e2e, storybook) on every PR.

Verification

  • cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace — all green (19 suites).
  • web-ide: vitest run 9/9, playwright test 4/4, vite build + storybook build succeed.
  • Browser-verified earlier: shared:: → only module shared's symbols; shared::User → 1, no keyword leak.

shockwave-bot Bot and others added 3 commits June 1, 2026 02:46
A partial identifier under the caret ends at the offset, so the
`<= offset` trigger scan selected it instead of the `.`/`::`/`:`/`#[`
before it, falling through to general items. Skip the boundary
identifier; its predecessor governs context. Adds with-prefix tests
per context.

refs #10

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Native LSP (complete.rs) and the web IDE's context-free JS provider are
unsynchronised; wasm exports no completion. Notes the surface to edit
per request and the unify-via-wasm direction.

refs #10

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The web IDE completed from a context-free JS provider (every keyword +
every symbol, always), while the LSP's context-aware engine was
unreachable from wasm (tower-lsp/tokio won't build to wasm32).

Extract the completion engine into pseudoscript-model::complete with a
neutral CompletionItem/CompletionKind over byte offsets, carrying the
governing_trigger scope fix. pseudoscript-lsp and pseudoscript-wasm
become thin adapters: the LSP maps to lsp_types, wasm serialises to JSON
via a new completion(modules, fqn, offset) export. The web IDE calls it
and replaces only the trailing identifier segment, so members complete
after '.', a module's symbols after '::', macros after '#[', types in
type position — identical to native editors. Regenerated pds-wasm bundle.

refs #10

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Jun 1, 2026

Deploying pseudoscript-landing with  Cloudflare Pages  Cloudflare Pages

Latest commit: bb85516
Status: ✅  Deploy successful!
Preview URL: https://d7709d93.pseudoscript-landing.pages.dev
Branch Preview URL: https://feat-ide-lsp-completion.pseudoscript-landing.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Jun 1, 2026

Deploying pseudoscript-ide with  Cloudflare Pages  Cloudflare Pages

Latest commit: bb85516
Status: ✅  Deploy successful!
Preview URL: https://77b430c1.pseudoscript-ide.pages.dev
Branch Preview URL: https://feat-ide-lsp-completion.pseudoscript-ide.pages.dev

View logs

@shockwave-bot
Copy link
Copy Markdown
Contributor Author

shockwave-bot Bot commented Jun 1, 2026

Browser acceptance — verified in the live dev IDE (ACME Tickets sample, orders module):

  • Typed shared:: → completion popup shows 8 items, exactly module shared's types (EventId, HoldId, Money, OrderId, Quantity, TicketId, TierId, UserId), each with its shared::… fqn detail. No keywords, no other-module symbols. The previous context-free provider would have listed every keyword + all 381 workspace symbols here.
  • Typed prefix shared::User → narrows to 1 item (UserId), no general-keyword leak — the governing_trigger scope fix (fix(lsp): keep completion scoped once a prefix is typed #11) confirmed end-to-end through the wasm path.

Engine + wasm + IDE wiring all exercised. The "not yet verified" caveat in the description is now closed.

@shockwave-bot shockwave-bot Bot changed the base branch from fix/completion-scope-leak to main June 1, 2026 04:04
@shockwave-bot shockwave-bot Bot changed the title feat(ide): source completion from the shared LSP engine feat: context-aware completion shared by the LSP and web IDE Jun 1, 2026
Broader LSP alignment (the web IDE should source language intelligence
from the compiler, not reimplement it in JS):

- Semantic highlighting: extract the AST-aware token engine into
  pseudoscript-model::semantic (neutral SemKind/SemToken over byte
  offsets); pseudoscript-lsp delta-encodes to lsp_types, pseudoscript-wasm
  exports semantic_tokens(). The IDE now decorates these tokens (data-sem
  marks) and the hand-written StreamLanguage tokenizer + JS keyword lists
  are gone — no more drift from the lexer.
- Folding: extract fold ranges into pseudoscript-model::fold; wasm
  folding_ranges(); the IDE folds AST-accurate ranges and blocks.js
  (brace-scan) is removed.

Testing suite (data-testid throughout, no brittle selectors):
- Vitest + Testing Library: unit tests for offset conversion, component
  test for FileTree's data-testid contract.
- Playwright e2e: headless tests for highlighting roles, completion
  scoping + the prefix-narrowing scope fix, and compiler-driven folding.
- Storybook (@storybook/sveltekit) with FileTree stories.
- data-testid added to file rows, editor host, and sample cards.

refs #10

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@shockwave-bot shockwave-bot Bot changed the title feat: context-aware completion shared by the LSP and web IDE feat: web IDE sources all language intelligence from the LSP + test suite Jun 1, 2026
shockwave-bot Bot and others added 2 commits June 1, 2026 04:52
refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
One LSP API, two transports. The handler logic (analysis, complete,
convert, infer, refs, semantic, symbols) moves into a new
pseudoscript-lsp-core crate that depends on pseudoscript-model +
standalone lsp-types =0.94.1 (the version tower-lsp 0.20 re-exports,
WASM-safe), NOT tower-lsp.

- pseudoscript-lsp keeps only the stdio transport (server.rs + the
  disk-walking workspace.rs) over lsp-core.
- pseudoscript-wasm's language exports (completion, hover, semantic_tokens,
  folding_ranges) now call the same lsp-core handlers and serialise their
  lsp_types results to JSON, so the wasm API is byte-for-byte the LSP API.
  Hover drops the SVG (now a plain lsp_types::Hover); the diagram stays on
  symbol_scene/symbol_svg.
- The web IDE is now an LSP client: decodes delta-encoded SemanticTokens,
  integer CompletionItemKind, line-based FoldingRange, Markdown Hover.
  definition/references stay fqn-ergonomic (LSP Location is URL-centric;
  the browser has no file URLs) but share model::resolve, so no fork.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@shockwave-bot shockwave-bot Bot changed the title feat: web IDE sources all language intelligence from the LSP + test suite feat: shared pseudoscript-lsp-core — stdio + LSP-over-wasm, one API Jun 1, 2026
@shockwave-bot
Copy link
Copy Markdown
Contributor Author

shockwave-bot Bot commented Jun 1, 2026

Architecture follow-up: one LSP API, two transports

Per review, the wasm API now mirrors the LSP API exactly via a shared core, instead of parallel adapters that could drift.

New crate pseudoscript-lsp-core — transport-neutral text + position -> lsp_types value handlers (analysis, complete, convert, infer, refs, semantic, symbols). Depends on pseudoscript-model + standalone lsp-types =0.94.1 (the version tower-lsp 0.20 re-exports, WASM-safe), not tower-lsp.

pseudoscript-lsp-core   (model + lsp-types, no tower-lsp)
   ├── pseudoscript-lsp   = + tower-lsp   (stdio server)
   └── pseudoscript-wasm  = + wasm-bindgen (JSON of the same lsp_types)
  • pseudoscript-lsp keeps only server.rs + the disk-walking workspace.rs.
  • pseudoscript-wasm language exports (completion, hover, semantic_tokens, folding_ranges) call the same lsp-core handlers and serialise the result → the wasm API is byte-for-byte the LSP API.
  • Hover drops the SVG (now a plain lsp_types::Hover); the diagram stays on symbol_scene/symbol_svg, re-resolved by the tooltip's Canvas/go-to-def actions.
  • The web IDE is now an LSP client: decodes delta-encoded SemanticTokens, integer CompletionItemKind, line-based FoldingRange, Markdown Hover.
  • definition/references stay fqn-ergonomic (LSP Location is URL-centric; the browser has no file URLs) but share model::resolve — no logic fork. Diagram/doc exports are wasm-only (no LSP equivalent).

Verified: lsp-types 0.94 compiles to wasm32; cargo fmt/clippy/test workspace (21 suites), vitest 9/9, Playwright e2e 4/4, Storybook build, CI all green. File moves are tracked as renames (history preserved). Repo CLAUDE.md updated.

shockwave-bot Bot and others added 13 commits June 1, 2026 10:13
decl.span reaches back over leading `///` doc comments and blank lines,
so folding it swallowed the gaps between declarations — the rendered
code lost its newlines (`{…}…}…}`). Fold from the declaration's name
through its end instead, and fold only brace-delimited records (a
brace-less `data = | …` union is not a block fold). Regression tests
in model and e2e.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The fold close landed at the start of the closing line, so an indented
`}` left its leading whitespace between the `…` placeholder and the
brace. Close at the brace itself (past the indent) so the indentation
folds away too.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Topbar and footer brand showed both a plain accent dot and the
nested-squares Mark. Keep the Mark; remove the dot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`identity::sessions.` only tried the bare name `sessions` in the current
module, so cross-module member completion never resolved. Resolve the
qualifier path to the owning module (public, per §8.2).

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ndo-on-switch

- Nav history records the pre-jump caret as the origin, so Back works
  from the first go-to-definition (was: only the destination recorded).
- Editor exposes location(); the shell records it before each jump.
- Theme selector in the toolbar cycles system/light/dark.
- Syntax highlight colours move to theme-aware --hl-* tokens (dark +
  darker, higher-contrast light variants), so light mode is legible.
- A file switch resets the editor undo history, so Ctrl+Z no longer
  undoes across the boundary into the previous file (Format stays
  undoable on the same file).

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Only `#[` + the name were decorator; `#[onevent(catalog::EventCancelled)]`
left its args as namespace/type and the brackets uncoloured, reading as
fragments. Colour the whole `#[…]` span as a decorator (subsuming name
and args), matching the prior tokenizer.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…als clicks

- Doc preview embeds the whole built site in a host page and swaps pages
  in an iframe on internal-link clicks (assets inlined), so navigation
  stays in the preview instead of breaking on relative .html links.
- The hover card's transparent bridge is pointer-transparent, so clicks
  and double-clicks land on the editor text, not the tooltip; the card
  itself stays interactive.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Open-folder, import and recents were three mismatched affordances stacked
together. Recast them as one row language under Start (open folder /
import) and Recent, with a consistent glyph + meta + chevron, matching
the example cards' rigor. data-testid on the action rows.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nter byte mapping

- An opened example persists nowhere, so it is no longer recorded in
  recents (legacy sample entries are filtered out); it re-opens from the
  examples catalogue.
- Seed the session baseline from the example so edits register; the save
  indicator now reads 'session · N unsaved' once a session is edited.
- Restore the byteToChar import in pseudoscript-language.js — the linter
  needs it to map diagnostic byte spans; its removal during the semantic
  highlighting change broke all diagnostics. Add an e2e guard.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The doc site drew sequences with a dagre node-graph; the web IDE draws a
lifeline/message UML diagram positioned by pseudoscript-layout. Unify
them: embed the positioned Layout in the Sequence figure props (Rust),
vendor the IDE's FlowTimeline + Sequence{Lifeline,Messages,Fragment}
components into the doc bundle (dropping the IDE-only theme store), and
pass the layout through. Regenerated client.js/ssr.js/style.css and the
wasm bundle.

Verified: a module page embeds the layout and the client bundle renders
the lifelines.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert both Svelte apps from JS to TypeScript: every lib .js → .ts and
every component to <script lang="ts">, with typed $props/$state and
typed function signatures. Configs/tests to .ts too (svelte.config.js
stays .js per SvelteKit). tsconfig strict; @types/node + vite-env shims.

svelte-check passes with 0 errors on both apps; wired `npm run check`
into CI (web job + a new landing job). Build, vitest (9/9) and the
Playwright e2e (5/5) are green — types only, no runtime change.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
General/reference-position completion only offered keywords + this
module's symbols, so a cross-module reference (e.g. context::AcmeTickets)
couldn't be started from autocomplete. Add the workspace's other modules
(the names before ::) to the general set; pick one, then :: drills in.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audit of the completion engine surfaced several gaps:
- Members of a local binding (a = Repo.fetch(id); a.) gave nothing — the
  receiver's inferred type was unused. Move infer into the model (it
  depends only on model) and resolve the binding's type to its node.
- Type position offered Result but not Option (both built-in generics).
- A macro argument (#[onevent(Event)]) got the general keyword set
  instead of types; route it to type completion.

infer.rs moves from pseudoscript-lsp-core to pseudoscript-model; lsp-core
re-points to pseudoscript_model::infer. Tests for each gap.

refs #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JonathanTurnock and others added 21 commits June 1, 2026 19:51
Apply rustfmt and satisfy clippy::semicolon_if_nothing_returned in the
call-member check added in the previous commit — the CI `cargo fmt --all
--check` and `clippy --all-targets -D warnings` gates. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two IDE correctness bugs:

1. The new-project (`starterModule`) and new-file (`pdsSkeleton`)
   templates emitted generic brace-nested pseudocode — `#` comments,
   `system X { container Y { fn z() } }`, none of which is PseudoScript.
   Replace with a minimal valid model: `//!` header, `///` docs, a
   `public system`, a `public container … for …`, and a void callable.
   Verified to check clean and project a diagram.

2. Go-to-definition reported PDS-GOTO-003 (resolved symbol not in the
   navigator) for every cross-module target. `nodeIndex` was keyed by
   single-file `outline`, which derives the module FQN from the `//!`
   header, while `definition`/`hover`/`references` qualify by the file's
   path FQN. When the two differ (`messaging` header vs `messagingcore`
   path) the index missed. Key `nodeIndex` off the workspace outline
   (`outlineModules`, path FQNs, cross-file parents resolved), grouping
   each node to its file by longest module-FQN prefix.

Pin the resolver contract with a wasm test: `definition` qualifies by the
workspace module FQN, not the `//!` header. JS-only IDE change otherwise —
no wasm rebuild needed for the fix itself.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The empty-template e2e asserted the old starter's hardcoded `Platform`
system. The starter now names the system after the project, so assert the
stable `public container Api` the template always emits, and check the
external-reload test against that token. Full e2e suite (10) + storybook
build pass locally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e 0)

Begin the Phase-1 logic lift: add src/lib/core/types.ts holding the shapes
the view and the (coming) pure core modules both need — StructureNode,
Symbol, Problem, OpenFile, WorkspaceModel, Loc, Canvas, doc/dialog/note
types — plus the WasmApi injection interface (the subset of pds.ts the core
will take as a dependency rather than import, keeping core unit-testable).

+page.svelte now imports these (WorkspaceModel aliased to its historical
PageWorkspace name); only the view-specific MountInput stays local. Pure
type re-home, no behaviour change. check + vitest green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…core (Wave 1a)

Lift three independent concerns out of +page.svelte into pure, unit-tested
core modules; the page now delegates via thin wrappers (behaviour identical):

- core/navigation.ts — history reducer (record/collapse/cap/step), originLoc,
  samePosition. The impure applyLocation/editorApi stay in the page.
- core/diagnostics.ts — computeDiagnostics: checkModules → problems/errorCount/
  errorPaths, with the throw-degrades-to-empty behaviour preserved (WASM injected).
- core/workspace-ops.ts — pdsSkeleton/pascalName/normalizePdsPath/slugify/withBase/
  docPathSet and the new-file/new-doc/rename validators + danglingImporters, now
  taking the file set / base / doc paths as args instead of reading globals.

20 new vitest cases pin these (incl. the valid-starter-template and GOTO-adjacent
invariants). Full suite: 32 vitest + 10 e2e green, svelte-check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Complete Wave 1 — the remaining independent pure concerns lift out of
+page.svelte; the page delegates via thin wrappers (behaviour identical):

- core/dirty.ts — keyOf, computeDirty (live buffers vs baseline), dirtyPaths,
  seedBaseline (pure, returns a new map), and classifyReload (skip/reload/
  conflict). The debounce/FS/timer half stays in the page (→ save store later).
- core/share.ts — snapshotWorkspace, mountedSources, parseHashPayload around the
  already-pure codec; the impure mountWorkspace/encode/decode stay in the page.
- core/docs.ts — manifestHasDeps, resolveDocPath (./.. normalise), findDocByPath,
  buildDocConfig, sampleDocPages. The async readDocPages/load-race guard stays.

15 more vitest cases. Full suite: 47 vitest + 10 e2e green, build clean,
svelte-check 0 errors. Wave 1 done: 6 core modules, page steadily shrinking.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lift the structural heart out of +page.svelte:

- core/model.ts — buildModelIndex(nodes, moduleFqns) computes the node index,
  per-file grouping (longest-prefix, the cross-module GOTO fix), flows, type map,
  and collapse info in one pass; plus pure resolvers resolveNodeFqn (collapsed
  member → real node), ownerNodeOf (field → owner, PDS-GOTO-002), ancestry
  (cycle-guarded), nodeTitle, singleLifelineScene, nodeByteOffset.
- core/canvas.ts — projectCanvas: symbolScene vs context, collapseSequence, the
  empty/throw → lifeline fallback, layout; errors reported via an injected callback
  (wired to reportError) so it stays side-effect-free. canvasHint.

The page now derives a single `index` and exposes its fields via aliases, and the
canvas is one projectCanvas call. Dropped the now-dead charToByte/collapseSequence/
Info/PdsScene imports. 15 new vitest cases (incl. the GOTO + fallback invariants).
Full suite: 62 vitest + 10 e2e green, svelte-check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Begin moving reactive $state off +page.svelte into thin Svelte-5 rune stores
(state ownership only — all logic already lives in core/*):

- stores/notifications.svelte.ts — owns the toast stack, flash message, and
  their timers; notify/dismiss/flash.
- stores/wasm.svelte.ts — owns ready/error/version + the initWasm lifecycle.

The page reads via aliases (`const ready = $derived(wasm.ready)`, etc.) and thin
delegating wrappers, so every call site is unchanged; it no longer declares those
state vars or imports initWasm/version. Behaviour identical: 62 vitest + 10 e2e
green, svelte-check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the history trail off +page.svelte into stores/navigation.svelte.ts — it
owns history/index over the pure core/navigation reducer and exposes canBack/
canForward/record/recordIfMoved/back/forward/reset. The view keeps the impure
application (applyLocation: open file, jump editor) and calls back()/forward(),
applying the returned Loc. Call sites unchanged via thin aliases.

Behaviour identical — e2e exercises go-to-definition + Back/Forward: 62 vitest
+ 10 e2e green, svelte-check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…drop corner ticks

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the canonical workspace and selection state off +page.svelte:

- stores/workspace.svelte.ts (wsStore) — workspace, openFile, moduleSources,
  manifestSource/Error, docGroups/Sources/Meta.
- stores/selection.svelte.ts (selection) — view, seqDepth, selected, pendingGoto.

Technique (svelte-check-guarded, zero behaviour change): replace each `let X =
$state(...)` with a read-only `const X = $derived(store.X)` so every read site is
untouched; the checker then flags each write (assign-to-derived) and those route
to `store.X = …`. The derived/aria-selected reads stay intact (guarded the
substitution so `aria-selected` wasn't mangled). The page keeps all orchestration
(selectNode, mutations, the pendingGoto effect).

62 vitest + 10 e2e green, svelte-check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- stores/save.svelte.ts (saveStore) — owns persisted baseline + saveState cue;
  the debounce/FS methods stay in the view. Writes routed via the checker-guided
  derived-alias technique.
- stores/diagnostics.svelte.ts (diagnostics) — a derived-only store: re-runs the
  static check over wsStore + WASM checkModules via core/diagnostics, exposing
  problems/errorCount/errorPaths. Added wsStore.allModules getter so the page and
  this store share one module list.

Dropped the now-dead computeDiagnostics/checkModules page imports. 62 vitest +
10 e2e green, svelte-check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The last of the canonical state moves off +page.svelte:

- stores/ui.svelte.ts (ui) — panel/modal flags, the persisted doc reading width,
  recents, canvas popovers, and the prompt/confirm dialogs (handlers stay in the
  view).
- stores/share.svelte.ts (shareStore) — the share-encode busy flag.

Same checker-guided derived-alias + write-reroute technique; no attribute names
collided. With this, all ~30 canonical $state vars now live in 8 focused rune
stores (wasm, notifications, navigation, workspace, selection, save, diagnostics,
ui, share); +page.svelte holds orchestration + template, all logic in core/*.

Full gate: 62 vitest + 10 e2e green, build + storybook ok, svelte-check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…kens

Phase 2 foundation: bring shadcn-svelte in and map its theme onto the existing
design tokens — keep the colours and fonts, let shapes follow shadcn.

- Tailwind v4 via @tailwindcss/vite (vite.config); deps: tailwindcss,
  tw-animate-css, clsx, tailwind-merge, tailwind-variants, bits-ui.
- app.css: `@import "tailwindcss"` + a `@theme inline` block mapping shadcn's
  semantic tokens to our vars — brand `--accent` → `primary`, surfaces/lines →
  card/secondary/muted/border, `--err` → destructive, `--radius` → the radius
  scale. Fonts already share Tailwind's `--font-sans`/`--font-mono` names, so
  they map for free. `dark:` keys off the existing `data-theme`. Tailwind's
  layers sit below the unlayered hand-written CSS, so the current UI is untouched
  — only shadcn components draw from Tailwind (verified: compiled
  `.bg-primary{background-color:var(--accent)}`).
- components.json + src/lib/utils.ts (cn) so `shadcn-svelte add` works.
- Button component (shadcn) + a Storybook showcase; themed Storybook preview
  (app.css + data-theme) so components render on the IDE palette.

No regression: 62 vitest + 10 e2e green, build + storybook ok, svelte-check 0
errors. Existing components unchanged — migration to shadcn comes next, per panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pull the genuine registry components (via `npx shadcn-svelte@latest add`) the
panel redesign will use: tooltip, separator, dropdown-menu, scroll-area, tabs —
joining button. Each is the official component built on Bits UI headless
primitives (DropdownMenuPrimitive, TooltipPrimitive, …) + tailwind-variants, not
hand-rolled, and inherits the brand theme via the tokens mapped in app.css.

Unused until the migration, so the build tree-shakes them; verified they compile:
62 vitest + 10 e2e green, build clean, svelte-check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restructure +page.svelte into a Fleet-style island shell built from shadcn-svelte
primitives, on the brand theme (compact "mira" density):

- ActivityBar (left, VS-Code rail): Explorer / Canvas activities + Settings
  anchored bottom-left; shadcn Tooltip labels.
- FileMenu (top-left, shadcn DropdownMenu): Open project, New file/doc, Save,
  Save all, Share/Export/Import, Build docs. TopBar adds theme/Format/save cue +
  the download-skill link (e2e testid kept).
- StructurePanel (right, collapsible): the workspace symbol/outline tree, lifted
  out of FileTree into SymbolTree.svelte. FileTree drops its Symbols section.
- ProblemsBadge (top-right, shadcn Popover): error/warning count → ProblemsPane,
  replacing the Problems view tab (Fleet floating icon).
- Canvas is its own activity that replaces the editor in the centre; Explorer
  restores it. StatusBar extracted, compact.

View model: selection.view "problems" dropped (→ code|canvas); ui.structureOpen
(default on) + ui.problemsOpen added; added saveAll. New shell grid CSS; old
.app/.workspace/.tree-pane/.view-toggle/.statusbar styles + Toolbar.svelte removed.

Behaviour preserved — verified by the full e2e oracle (10) which exercises the
new shell (create project, editor, file rows, canvas, diagnostics): 62 vitest +
10 e2e green, build + storybook ok, svelte-check 0 errors. Screenshotted dark:
the activity rail, file tree, editor/canvas centre, and structure panel render on
the brand palette.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- shadcn ContextMenu on file-tree rows (Open / Rename… / New file… / Delete) and
  on Structure-panel symbols (Go to definition / Reveal on canvas). Left-click and
  the data-testid="file-*" rows still work (e2e green).
- Island look: a shared `.island` treatment (rounded, hairline-framed) on the
  activity rail, explorer, centre, and structure; the `.body` grid gains a gutter
  + padding on the app backdrop, so the panels read as separated Fleet islands.
  Top/status bars stay flush full-width. Collapsing structure now drops its column
  (no trailing gap).
- vitest.config: add the `$lib` alias so deep shadcn-svelte imports resolve in
  component tests as they do in the app (FileTree.test was failing to load).

62 vitest + 10 e2e green, build clean, svelte-check 0 errors. Screenshotted: the
rounded islands float on the dark backdrop; right-click opens the themed menu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a shadcn Command palette (CommandPalette.svelte) opened with Cmd/Ctrl-K: a
centred dialog that searches the workspace's files and symbols (kind-badged,
fqn-qualified) and jumps to the pick — selectFile for a file, go-to-definition
for a symbol. Wired via ui.commandOpen + a window keydown.

62 vitest + 10 e2e green, build clean, svelte-check 0 errors. Screenshotted:
typing "order" filters to the orders module + its Order/OrderStatus/… symbols.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- TopBar gains a VS-Code-style centre search pill (workspace name + ⌘/Ctrl-K
  badge) that opens the command palette — discoverable + a tidy focal point.
- FileMenu: "Go to file or symbol…" item (⌘K) and a Save shortcut hint (⌘S),
  platform-correct modifier glyph; "Open project…" relabelled.

62 vitest + 10 e2e green, build clean, svelte-check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…skill→settings

Per product direction, rebalance the shell chrome:
- StatusBar (footer) now hosts the save indicator and the Problems badge/popover
  (moved off the header).
- TopBar (header) gains Back/Forward; drops save + problems.
- Settings becomes a proper panel: an Appearance theme switcher (system/light/dark)
  and the authoring-skill download moved out of the header. e2e opens Settings via
  the activity-bar gear to reach the skill download.
- StructurePanel gains a filter input (search symbols by name/fqn, ancestors kept
  so the tree still nests).
- Command palette widened (≈56rem).

62 vitest + 10 e2e green, build clean, svelte-check 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Squash of the day's IDE work into one commit. Highlights:

- Shell: Fleet/VS-Code island layout, activity bar, tool-window panels with
  drag-resizable + persisted widths, relocated chrome, unified bottom-right
  toast notifications.
- Files & editor: FS-backed file manager, unified file tree (symbol/text
  search), multi-file tabs as standalone pills with per-file state preserved,
  non-PDS files surfaced and editable with highlighting, TOML highlighting.
- Launcher: IntelliJ-style Search Everywhere command palette, separate
  New-project dialog (mandatory name, target-folder-before-template).
- Canvas: right-click context menus, Customise modal (layout algorithms +
  floating edge styles), layout presets, and PNG/SVG diagram export.
- Language intelligence: LSP symbol rename across the project with a
  selectable preview.
- CLI: `pds lang` and `pds skill` for offline language discovery.
- Polish: PWA/address-bar colour + header sizing, opaque editor gutter,
  themed active-tab border.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JonathanTurnock JonathanTurnock force-pushed the feat/ide-lsp-completion branch from e9a567b to 4802ae5 Compare June 2, 2026 17:03
JonathanTurnock and others added 6 commits June 2, 2026 18:09
The header's go-to/search button sat ~4px closer to the right edge than the
right-rail nav button under it. Make the topbar's right zone a rail-width
(--right-rail-w) box ending the same --island-gap from the edge as the rail
column, with the icon centred — so both centre at the same distance from the
edge. Apply the same gap in the PWA window-controls-overlay inset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The header back/forward/search buttons were 1.7rem with 15px glyphs, smaller
than the 2rem/18px act-btns in the left activity bar and right rail. Bump them
to the same 2rem box and 18px (1.75 stroke) glyph so the search button matches
the rail button it sits above; centre alignment is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The header was 38px (--bar-h, shared with the content/tab/dock bars) while the
left activity bar and right rail are 42px wide. Give the header its own height
by reviving the unused --topbar-h (now 42px) and wiring the .ide grid row and
TopBar to it — so the header matches the rail width and the rails start flush
with its bottom edge. The compact --bar-h chrome is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the C4 graph: lifelines, message labels, and signature type tokens
open a right-click menu (Go to definition / Find usages) instead of the
hold-hover info card and Cmd-click. Extract the shared menu chrome into
CanvasMenu, driven by a declarative section model both diagrams build;
hoist the menu/target types into core/types. Remove the now-dead
hover-info popover path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
web-landing: collapse onto one theme (model-driven development + pseudo-programming).
- Hero: 'Model the intent. Implement the detail.', single primary CTA (Web IDE), benefit-led copy, Wikipedia links to MDE/pseudocode.
- Reframe Workflow/agent copy; cut Convergence, Packages, IdeShowcase, Proof.
- Scroll-link the Refine/Generate animations (scrub with scroll position).
- Refine example now uses verbatim 'pds eval' diagnostics.
- Match light theme to the web-ide; de-card the install band; align hero gutter.

pds: add 'eval' subcommand — read a model from stdin, report diagnostics, exit
non-zero on error (lets an agent check a snippet without a file). Factor the
shared 'report_source' helper out of 'check'; add integration tests.

docs: refresh CLAUDE.md (toolchain is implemented; list crates + commands incl.
eval); add a root README with a hero + repository signposting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
web-landing SEO pass:
- index.html: aligned description, canonical, robots, theme-color, keywords;
  Open Graph + Twitter summary_large_image cards; JSON-LD (WebSite +
  SoftwareApplication); svg icon + apple-touch-icon; a <noscript> fallback with
  the h1, pitch, and primary links (the app is client-rendered).
- public/: robots.txt + sitemap.xml (https://pdscript.dev/), and a real
  1200x630 og.png social preview.
- Hero: mark the decorative C4 diagram aria-hidden so the a11y tree reflects
  only meaningful content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JonathanTurnock JonathanTurnock merged commit 647e047 into main Jun 2, 2026
5 checks passed
@JonathanTurnock JonathanTurnock deleted the feat/ide-lsp-completion branch June 2, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants