diff --git a/sx/sx/sx-tools.sx b/sx/sx/sx-tools.sx index ffd7e4ed..311a7880 100644 --- a/sx/sx/sx-tools.sx +++ b/sx/sx/sx-tools.sx @@ -17,7 +17,7 @@ (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Dependency analysis") (p (code "sx_deps") - " walks a component or file and collects every free symbol — names that are referenced but not locally bound. For each symbol it reports where the definition lives: same file, another " + " walks a component or file and collects every free symbol -- names that are referenced but not locally bound. For each symbol it reports where the definition lives: same file, another " (code ".sx") " file, or a platform primitive.") (~docs/code @@ -26,7 +26,7 @@ (p "The " (code "???") - " entries are HTML tags or symbols defined at runtime. This tool catches missing imports and build pipeline gaps — it would have instantly revealed that " + " entries are HTML tags or symbols defined at runtime. This tool catches missing imports and build pipeline gaps -- it would have instantly revealed that " (code "resource") " was missing from the browser island environment.") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Build manifest") @@ -44,7 +44,7 @@ :title title (p :class "text-stone-500 text-sm italic mb-8" - "A structural tree editor for s-expression files — because the thing that reads and edits code should understand the code as a tree, not as a sequence of characters.") + "A structural tree editor for s-expression files -- because the thing that reads and edits code should understand the code as a tree, not as a sequence of characters.") (~docs/section :title "Tool catalogue" :id "catalogue" @@ -73,7 +73,7 @@ (td :class "pr-4 py-1 font-mono text-xs" "sx_summarise") (td :class "py-1" - "Folded overview at configurable depth — shape without detail.")) + "Folded overview at configurable depth -- shape without detail.")) (tr (td :class "pr-4 py-1 font-mono text-xs" "sx_read_subtree") (td :class "py-1" "Expand a specific subtree by path.")) @@ -94,7 +94,7 @@ :class "py-1" "Structural integrity: balanced parens, valid paths.")))) (~docs/code - :src ";; Focus mode — expand only matching subtrees\nsx_read_tree file=\"home.sx\" focus=\"defisland\"\n\n;; Paginated — first 30 lines\nsx_read_tree file=\"home.sx\" max_lines=30\n\n;; Context chain for a deep node\nsx_get_context file=\"home.sx\" path=\"(0 2 2 1 12)\"") + :src ";; Focus mode -- expand only matching subtrees\nsx_read_tree file=\"home.sx\" focus=\"defisland\"\n\n;; Paginated -- first 30 lines\nsx_read_tree file=\"home.sx\" max_lines=30\n\n;; Context chain for a deep node\nsx_get_context file=\"home.sx\" path=\"(0 2 2 1 12)\"") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Path-based editing (4 tools)") @@ -118,7 +118,7 @@ (td :class "py-1" "Insert child at index within a list.")) (tr (td :class "pr-4 py-1 font-mono text-xs" "sx_delete_node") - (td :class "py-1" "Remove node — siblings shift to fill gap.")) + (td :class "py-1" "Remove node -- siblings shift to fill gap.")) (tr (td :class "pr-4 py-1 font-mono text-xs" "sx_wrap_node") (td :class "py-1" "Wrap node in template with _ placeholder.")))) @@ -222,7 +222,7 @@ (td :class "pr-4 py-1 font-mono text-xs" "sx_test") (td :class "py-1" - "Run test suite — host=js or ocaml, full=true for extensions.")) + "Run test suite -- host=js or ocaml, full=true for extensions.")) (tr (td :class "pr-4 py-1 font-mono text-xs" "sx_format_check") (td @@ -237,7 +237,7 @@ (td :class "pr-4 py-1 font-mono text-xs" "sx_eval") (td :class "py-1" - "REPL — evaluate SX expressions in the MCP server env.")))) + "REPL -- evaluate SX expressions in the MCP server env.")))) (~docs/code :src ";; Build and test in one go\nsx_build target=\"ocaml\"\nsx_test host=\"ocaml\"\n;; → Results: 1116 passed, 0 failed\n\n;; Quick REPL check\nsx_eval \"(+ (* 3 4) 5)\" ;; → 17") (h4 @@ -332,19 +332,19 @@ :title "The problem" :id "problem" (p - "On 25 March 2026, the SX documentation site went blank. The home page stepper widget — a 310-line " + "On 25 March 2026, the SX documentation site went blank. The home page stepper widget -- a 310-line " (code "defisland") - " — failed to render. The cause was a single extra closing parenthesis on line 222 of " + " -- failed to render. The cause was a single extra closing parenthesis on line 222 of " (code "home-stepper.sx") ".") (p "That parenthesis closed the " (code "letrec") - " bindings list one level too early. Two function definitions — " + " bindings list one level too early. Two function definitions -- " (code "rebuild-preview") " and " (code "do-back") - " — silently became body expressions instead of bindings. They were evaluated and discarded rather than bound in scope. Every subsequent reference to " + " -- silently became body expressions instead of bindings. They were evaluated and discarded rather than bound in scope. Every subsequent reference to " (code "rebuild-preview") " raised " (code "Undefined symbol") @@ -357,14 +357,14 @@ "), and finally writing a paren-depth tracer that walked the file character by character to find where the nesting diverged from expectation.") (p "The fix was removing one character.") (p - "This is a class of bug, not an incident. S-expressions encode tree structure in linear text using matched delimiters. When those delimiters are wrong, the meaning of every subsequent expression changes. The error is silent — the parser succeeds, the evaluator runs, the wrong thing happens. The gap between the intended tree and the actual tree is invisible in the source.")) + "This is a class of bug, not an incident. S-expressions encode tree structure in linear text using matched delimiters. When those delimiters are wrong, the meaning of every subsequent expression changes. The error is silent -- the parser succeeds, the evaluator runs, the wrong thing happens. The gap between the intended tree and the actual tree is invisible in the source.")) (~docs/section :title "Why raw text fails" :id "why-text-fails" (p "Claude Code reads " (code ".sx") - " files as raw text and mentally reconstructs the tree structure by tracking bracket nesting. It does this imperfectly — especially in deep or wide trees where closing parentheses pile up and their correspondence to openers is lost. Consider the end of a complex island:") + " files as raw text and mentally reconstructs the tree structure by tracking bracket nesting. It does this imperfectly -- especially in deep or wide trees where closing parentheses pile up and their correspondence to openers is lost. Consider the end of a complex island:") (~docs/code :src "(set-cookie \"sx-home-stepper\" (freeze-to-sx \"home-stepper\"))))))))") (p @@ -374,9 +374,9 @@ (code "fn") "? Which closes the binding pair? Which closes the letrec bindings list? Answering this requires counting backward through hundreds of lines. Counting is not what language models do well.") (p - "The same problem compounds when writing. Claude generates plausible-looking s-expression fragments that are structurally wrong — a paren added, a paren dropped, a level of nesting off. The " + "The same problem compounds when writing. Claude generates plausible-looking s-expression fragments that are structurally wrong -- a paren added, a paren dropped, a level of nesting off. The " (code "str_replace") - " tool makes this worse: replacing a string inside a deeply nested form can silently unbalance the surrounding structure in ways that are not visible until the file fails to parse — or worse, parses into a different tree.")) + " tool makes this worse: replacing a string inside a deeply nested form can silently unbalance the surrounding structure in ways that are not visible until the file fails to parse -- or worse, parses into a different tree.")) (~docs/section :title "The fix: structural tools" :id "structural-tools" @@ -409,7 +409,7 @@ "For editing, Claude specifies tree operations rather than text replacements:") (~docs/code :src (str - ";; Replace a node by path — the fragment is parsed before\n" + ";; Replace a node by path -- the fragment is parsed before\n" ";; the file is touched. Bracket errors are impossible.\n" "(replace-node \"home-stepper.sx\" [0,2,2,1,12]\n" " \"(rebuild-preview (fn (target) ...))\")\n" @@ -418,7 +418,7 @@ "(insert-child \"home-stepper.sx\" [0,2,2,1] 12\n" " \"(new-function (fn () nil))\")\n" "\n" - ";; Delete a node — siblings adjust automatically\n" + ";; Delete a node -- siblings adjust automatically\n" "(delete-node \"home-stepper.sx\" [0,2,2,3])")) (p "Every write operation parses the new fragment as a complete s-expression " @@ -451,7 +451,7 @@ (strong "parser") " is " (code "sx-parse") - " — the same parser that evaluates SX source. No new parser needed. Round-trip fidelity is inherited from the existing serializer.") + " -- the same parser that evaluates SX source. No new parser needed. Round-trip fidelity is inherited from the existing serializer.") (p "The " (strong "tree logic") @@ -471,14 +471,14 @@ (code "defisland") " at " (code "/sx/(tools.(sx-tools))") - " — the page you are reading. Interactive tree visualization with click-to-navigate, path display, and structural editing. Islands and signals make it reactive. It is both a tool and a demonstration of the SX platform.")) + " -- the page you are reading. Interactive tree visualization with click-to-navigate, path display, and structural editing. Islands and signals make it reactive. It is both a tool and a demonstration of the SX platform.")) (~docs/section :title "Comprehension tools" :id "comprehension" (p "These are the tools Claude uses to " (em "understand") - " structure before touching anything. They are read-only and have no side effects. They are not a convenience layer — they are as important as the editing tools.") + " structure before touching anything. They are read-only and have no side effects. They are not a convenience layer -- they are as important as the editing tools.") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Annotated tree view") @@ -544,7 +544,7 @@ (p "The " (code "focus") - " parameter expands only subtrees that match a pattern, collapsing everything else. This is the most useful mode — it combines search and comprehension in one call.") + " parameter expands only subtrees that match a pattern, collapsing everything else. This is the most useful mode -- it combines search and comprehension in one call.") (~docs/code :src (str ";; Show only the parts of the tree that mention \"defisland\"\nsx_read_tree file=\"home.sx\" focus=\"defisland\"\n\n;; The ~docs/section containing defisland expands fully.\n;; Every other section collapses to one line.")) @@ -554,7 +554,7 @@ (code "max_depth") " parameter works like " (code "sx_summarise") - " — it caps how deep the tree expands.") + " -- it caps how deep the tree expands.") (~docs/code :src (str ";; Just the top-level structure\nsx_read_tree file=\"home.sx\" max_depth=1\n\n;; Two levels deep\nsx_read_tree file=\"home.sx\" max_depth=3")) @@ -602,37 +602,37 @@ (td :class "pr-4 py-1 font-mono text-xs" "delete-node") (td :class "py-1" - "Remove a node — siblings shift to fill the gap")) + "Remove a node -- siblings shift to fill the gap")) (tr (td :class "pr-4 py-1 font-mono text-xs" "wrap-node") (td :class "py-1" - "Wrap a node in a new list — e.g. wrap expression in " + "Wrap a node in a new list -- e.g. wrap expression in " (code "(when cond ...)"))) (tr (td :class "pr-4 py-1 font-mono text-xs" "validate") (td :class "py-1" - "Check structural integrity — balanced parens, valid paths")))) + "Check structural integrity -- balanced parens, valid paths")))) (p (strong "Fragment-first validation.") " Every write operation parses the new source fragment as a complete s-expression before navigating to the target path. If the fragment is malformed, the operation returns an error with the line and column of the parse failure. The source file is never touched in the failure path.") (p (strong "Named paths.") - " Index paths break when sibling nodes are inserted or deleted. Named paths — " + " Index paths break when sibling nodes are inserted or deleted. Named paths -- " (code "[Head \"letrec\", Head \"rebuild-preview\"]") - " — survive structural edits and are more natural for Claude to reason about. Claude should prefer named paths; index paths are for mechanical follow-up after " + " -- survive structural edits and are more natural for Claude to reason about. Claude should prefer named paths; index paths are for mechanical follow-up after " (code "find-node") " has located a target.")) (~docs/section :title "Smart editing" :id "smart-editing" (p - "The path-based edit tools require knowing the exact path to a node. The smart edit tools combine search with editing — find a node by pattern, then edit it in one call.") + "The path-based edit tools require knowing the exact path to a node. The smart edit tools combine search with editing -- find a node by pattern, then edit it in one call.") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Rename symbol") (p (code "sx_rename_symbol") - " renames every occurrence of a symbol throughout a file. Structural — only renames symbol nodes, never touches strings or keywords.") + " renames every occurrence of a symbol throughout a file. Structural -- only renames symbol nodes, never touches strings or keywords.") (~docs/code :src (str ";; Rename a component\nsx_rename_symbol file=\"home.sx\" old_name=\"~card\" new_name=\"~ui/card\"\n;; → Renamed 3 occurrences of '~card' → '~ui/card' in home.sx")) @@ -650,7 +650,7 @@ "Insert near pattern") (p (code "sx_insert_near") - " inserts a new form before or after the top-level definition that contains a pattern match. No path arithmetic needed — just name what you want to insert near.") + " inserts a new form before or after the top-level definition that contains a pattern match. No path arithmetic needed -- just name what you want to insert near.") (~docs/code :src (str ";; Insert a new component before the footer definition\nsx_insert_near file=\"page.sx\" pattern=\"~footer\" position=\"before\"\n new_source=\"(defcomp ~sidebar () (div :class sidebar))\"")) @@ -673,7 +673,7 @@ (p "The " (code "sx_eval") - " tool evaluates SX expressions inside the MCP server's environment. The environment has the parser, tree-tools functions, and all primitives loaded — the same context the tree tools themselves run in.") + " tool evaluates SX expressions inside the MCP server's environment. The environment has the parser, tree-tools functions, and all primitives loaded -- the same context the tree tools themselves run in.") (p "This is useful for testing expressions, checking how primitives behave, or debugging tree-tool functions without a full build cycle.") (~docs/code @@ -724,7 +724,7 @@ (code ".sx") " files. The reverse of " (code "find-all") - " — instead of searching one file, it searches the whole project.") + " -- instead of searching one file, it searches the whole project.") (~docs/code :src (str ";; Who uses ~docs/section?\nsx_comp_usage dir=\"/root/rose-ash/sx\" name=\"~docs/section\"\n\n;; Output:\n;; sx/sx-tools.sx [0,3,4] (~docs/section :title \"The problem\" ...)\n;; sx/sx-tools.sx [0,3,5] (~docs/section :title \"Why raw text fails\" ...)\n;; ..."))) @@ -735,7 +735,7 @@ (code "sx_diff") " compares two " (code ".sx") - " files structurally and reports differences with tree paths. Unlike text diff, it understands nesting — a renamed symbol deep in a tree shows as one " + " files structurally and reports differences with tree paths. Unlike text diff, it understands nesting -- a renamed symbol deep in a tree shows as one " (code "CHANGED") " line, not a confusing hunk of parentheses.") (~docs/code @@ -748,7 +748,7 @@ (code "ADDED") " (present in second file but not first), " (code "REMOVED") - " (present in first but not second). Comparison is positional — nodes are matched by index, not by name.")) + " (present in first but not second). Comparison is positional -- nodes are matched by index, not by name.")) (~docs/section :title "Development tools" :id "dev-tools" @@ -765,7 +765,7 @@ (code "sx_write_file") " creates or overwrites an " (code ".sx") - " file. Source is parsed before writing — malformed SX is rejected and the file is not touched. Output is pretty-printed.") + " file. Source is parsed before writing -- malformed SX is rejected and the file is not touched. Output is pretty-printed.") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Build") (p (code "sx_build") @@ -807,7 +807,7 @@ (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Macro expand") (p (code "sx_macroexpand") - " evaluates an expression with a file's definitions loaded. Use to test macros — the file's " + " evaluates an expression with a file's definitions loaded. Use to test macros -- the file's " (code "defmacro") " forms are available in the evaluation environment.")) (~docs/section @@ -832,7 +832,7 @@ (code "sx_diff") " on every changed " (code ".sx") - " file, comparing the current version against the base ref. Shows structural ADDED/REMOVED/CHANGED per file — like a PR review that understands trees.") + " file, comparing the current version against the base ref. Shows structural ADDED/REMOVED/CHANGED per file -- like a PR review that understands trees.") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Blame") (p (code "sx_blame") @@ -850,7 +850,7 @@ (code "defisland") ", and " (code "defmacro") - " signatures — names, types, keyword parameters, and whether the component accepts children.") + " signatures -- names, types, keyword parameters, and whether the component accepts children.") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Playwright tests") (p (code "sx_playwright") @@ -859,9 +859,9 @@ :title "The protocol" :id "protocol" (p - "The tools only work if they are actually used. The MCP server must be accompanied by a protocol — enforced via " + "The tools only work if they are actually used. The MCP server must be accompanied by a protocol -- enforced via " (code "CLAUDE.md") - " — that prevents fallback to raw text editing.") + " -- that prevents fallback to raw text editing.") (~docs/code :src (str ";; Before doing anything in an .sx file:\n" @@ -880,7 +880,7 @@ ";; Never proceed to an edit without first establishing\n" ";; where you are in the tree using the comprehension tools.")) (p - "The comprehension-first discipline is the key insight. Claude cannot edit reliably what it does not understand reliably. The same parsed tree representation serves both needs — reading and writing are two sides of the same structural problem.")) + "The comprehension-first discipline is the key insight. Claude cannot edit reliably what it does not understand reliably. The same parsed tree representation serves both needs -- reading and writing are two sides of the same structural problem.")) (~docs/section :title "Why SX, not OCaml" :id "why-sx" @@ -893,14 +893,14 @@ (li (strong "Parser: ") (code "sx-parse") - " already parses s-expressions correctly — it is the same parser the evaluator uses. No second parser to maintain, no divergence risk.") + " already parses s-expressions correctly -- it is the same parser the evaluator uses. No second parser to maintain, no divergence risk.") (li (strong "Serializer: ") (code "sx-serialize") " already handles round-tripping. The existing serializer preserves structure.") (li (strong "Tree operations: ") - "Recursive list processing is what SX does best. Annotating a tree, folding to a summary, navigating by path — these are all natural " + "Recursive list processing is what SX does best. Annotating a tree, folding to a summary, navigating by path -- these are all natural " (code "map") "/" (code "reduce") @@ -911,10 +911,10 @@ (strong "Web UI: ") "The interactive tree editor is a " (code "defisland") - " — signals for selection state, reactive DOM for the tree view, lakes for server-morphable content. The home stepper widget is proof this works.") + " -- signals for selection state, reactive DOM for the tree view, lakes for server-morphable content. The home stepper widget is proof this works.") (li (strong "OCaml host: ") - "The SX functions run on the OCaml evaluator. The MCP server is a thin OCaml wrapper around SX function calls. Native performance for the server, WASM for the browser — same codebase.")) + "The SX functions run on the OCaml evaluator. The MCP server is a thin OCaml wrapper around SX function calls. Native performance for the server, WASM for the browser -- same codebase.")) (p "Writing the tree tools in SX means they can run in the browser (via the WASM evaluator) and on the server (via the OCaml kernel). The web editor and the MCP server share identical logic. There is one implementation, not two.")) (~docs/section @@ -922,7 +922,7 @@ :id "build-plan" (h4 :class "font-semibold text-stone-700 mt-6 mb-2" - "Phase 1 — Tree comprehension functions") + "Phase 1 -- Tree comprehension functions") (p "Implement " (code "annotate-tree") @@ -940,10 +940,10 @@ (code "web/lib/tree-tools.sx") ". Test against real project " (code ".sx") - " files. Iterate on output formats until the output is genuinely easy for a language model to read — the format is load-bearing.") + " files. Iterate on output formats until the output is genuinely easy for a language model to read -- the format is load-bearing.") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" - "Phase 2 — Edit operations") + "Phase 2 -- Edit operations") (p "Implement " (code "replace-node") @@ -955,30 +955,30 @@ (code "wrap-node") ", " (code "validate") - " as pure SX functions. Fragment-first validation on all write operations. Test error paths exhaustively — error messages are part of the interface.") + " as pure SX functions. Fragment-first validation on all write operations. Test error paths exhaustively -- error messages are part of the interface.") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" - "Phase 3 — MCP server") + "Phase 3 -- MCP server") (p "Thin OCaml binary: stdio JSON-RPC, calls SX functions via the bridge, reads/writes files. Wire all comprehension and edit tools to MCP handlers. Manual testing with raw JSON.") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" - "Phase 4 — Web editor") + "Phase 4 -- Web editor") (p (code "defisland ~sx-tools/tree-editor") - " — interactive tree visualization on this page. Click a node to see its path, context, and siblings. Edit nodes through a structural interface. Islands and signals for reactivity. A tool and a demonstration.") + " -- interactive tree visualization on this page. Click a node to see its path, context, and siblings. Edit nodes through a structural interface. Islands and signals for reactivity. A tool and a demonstration.") (h4 :class "font-semibold text-stone-700 mt-6 mb-2" - "Phase 5 — Integration and iteration") + "Phase 5 -- Integration and iteration") (p "Write the " (code "CLAUDE.md") - " protocol. Run real tasks with Claude Code — both reading and editing. Observe which comprehension tools Claude actually reaches for. Observe where it still makes structural errors. Iterate on output formats and add any missing tools. The output formats deserve careful design based on observed behaviour, not just on what seems reasonable in advance.")) + " protocol. Run real tasks with Claude Code -- both reading and editing. Observe which comprehension tools Claude actually reaches for. Observe where it still makes structural errors. Iterate on output formats and add any missing tools. The output formats deserve careful design based on observed behaviour, not just on what seems reasonable in advance.")) (~docs/section :title "Try it" :id "try-it" (p - "Paste or edit SX source below. The tree view shows every node with its path — click a node to select it, then switch to context view to see the enclosing chain.") + "Paste or edit SX source below. The tree view shows every node with its path -- click a node to select it, then switch to context view to see the enclosing chain.") (~sx-tools/tree-editor)) (~docs/section :title "What changes" @@ -1001,11 +1001,11 @@ (code "validate home-stepper.sx") " would report the structural anomaly: 14 names in the letrec bindings list but only 12 binding pairs, with 2 bare expressions following.") (li - "The fix — " + "The fix -- " (code "delete-node") " to remove the extra paren, or " (code "wrap-node") - " to restructure — would be a tree operation. No character-counting, no mental stack simulation, no risk of introducing a second paren error while fixing the first.")) + " to restructure -- would be a tree operation. No character-counting, no mental stack simulation, no risk of introducing a second paren error while fixing the first.")) (p "The gap between intended tree and actual tree stops being invisible. Claude sees trees, edits trees, and the brackets take care of themselves."))))