Merge remote-tracking branch 'origin/main' into worktree-iso-phase-4
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m54s

# Conflicts:
#	sx/sx/essays.sx
#	sx/sx/nav-data.sx
#	sx/sxc/pages/docs.sx
This commit is contained in:
2026-03-07 11:28:48 +00:00
7 changed files with 421 additions and 9 deletions

View File

@@ -463,11 +463,12 @@ def components_for_request(source: str = "",
Reads the ``SX-Components`` header (comma-separated component names
like ``~card,~nav-item``) and returns only the definitions the client
is missing. If *source* is provided, only sends components needed
for that source (plus transitive deps).
for that source (plus transitive deps). If the header is absent,
returns all needed defs.
*extra_names* — additional component names to include beyond what
*source* references. Used by defpage to send components the page's
content expression needs for client-side routing.
*extra_names* — additional component names (``~foo``) to include
beyond what *source* references. Used by ``execute_page`` to send
components the page's content expression needs for client-side routing.
"""
from quart import request
from .jinja_bridge import _COMPONENT_ENV

View File

@@ -282,7 +282,7 @@ async def execute_page(
# Compute content expression deps so the server sends component
# definitions the client needs for future client-side routing
extra_deps: set[str] | None = None
if page_def.content_expr is not None:
if page_def.content_expr is not None and page_def.data_expr is None:
from .deps import components_needed
from .parser import serialize
try:

View File

@@ -8,12 +8,22 @@ from __future__ import annotations
import re
def _escape(text: str) -> str:
"""Escape a token for embedding in an SX string literal."""
return (text
.replace("\\", "\\\\")
.replace('"', '\\"')
.replace("\n", "\\n")
.replace("\t", "\\t")
.replace("\r", "\\r"))
def highlight_sx(code: str) -> str:
"""Highlight s-expression source code as sx with Tailwind spans."""
tokens = _tokenize_sx(code)
parts = []
for kind, text in tokens:
escaped = text.replace("\\", "\\\\").replace('"', '\\"')
escaped = _escape(text)
if kind == "comment":
parts.append(f'(span :class "text-stone-400 italic" "{escaped}")')
elif kind == "string":
@@ -94,7 +104,7 @@ def highlight_python(code: str) -> str:
tokens = _tokenize_python(code)
parts = []
for kind, text in tokens:
escaped = text.replace("\\", "\\\\").replace('"', '\\"')
escaped = _escape(text)
if kind == "comment":
parts.append(f'(span :class "text-stone-400 italic" "{escaped}")')
elif kind == "string":
@@ -176,7 +186,7 @@ def highlight_bash(code: str) -> str:
tokens = _tokenize_bash(code)
parts = []
for kind, text in tokens:
escaped = text.replace("\\", "\\\\").replace('"', '\\"')
escaped = _escape(text)
if kind == "comment":
parts.append(f'(span :class "text-stone-400 italic" "{escaped}")')
elif kind == "string":

View File

@@ -368,3 +368,113 @@
"Separation of concerns is a domain-specific design decision. It cannot be imposed by a framework. The web platform's HTML/CSS/JS split is an implementation detail of the browser, not an architectural principle for applications. Treating it as one has cost the industry decades of unnecessary complexity, tooling, and convention.")
(p :class "text-stone-600"
"Separate the things that change for different reasons. Co-locate the things that change together. That is the entire principle. It says nothing about file extensions."))))
(defcomp ~essay-sx-and-ai ()
(~doc-page :title "SX and AI"
(p :class "text-stone-500 text-sm italic mb-8"
"Why s-expressions are the most AI-friendly representation for web interfaces — and what that means for how software gets built.")
(~doc-section :title "The syntax tax" :id "syntax-tax"
(p :class "text-stone-600"
"Every programming language imposes a syntax tax on AI code generation. The model must produce output that satisfies a grammar — matching braces, semicolons in the right places, operator precedence, indentation rules, closing tags that match opening tags. The more complex the grammar, the more tokens the model wastes on syntactic bookkeeping instead of semantic intent.")
(p :class "text-stone-600"
"Consider what an AI must get right to produce a valid React component: JSX tags that open and close correctly, curly braces for JavaScript expressions inside markup, import statements with correct paths, semicolons or ASI rules, TypeScript type annotations, CSS-in-JS string literals with different quoting rules than the surrounding code. Each syntactic concern is a potential failure point. Each failure produces something that does not parse, let alone run.")
(p :class "text-stone-600"
"S-expressions have one syntactic form: " (code "(head args...)") ". Parentheses open and close. Strings are quoted. That is the entire grammar. There is no operator precedence because there are no operators. There is no indentation sensitivity because whitespace is not significant. There are no closing tags because there are no tags — just lists.")
(p :class "text-stone-600"
"The syntax tax for SX is essentially zero. An AI that can count parentheses can produce syntactically valid SX. This is not a small advantage — it is a categorical one. The model spends its capacity on " (em "what") " to generate, not " (em "how") " to format it."))
(~doc-section :title "One representation for everything" :id "one-representation"
(p :class "text-stone-600"
"A typical web project requires the AI to context-switch between HTML (angle brackets, void elements, boolean attributes), CSS (selectors, properties, at-rules, a completely different syntax from HTML), JavaScript (statements, expressions, classes, closures, async/await), and whatever templating language glues them together (Jinja delimiters, ERB tags, JSX interpolation). Each is a separate grammar. Each has edge cases. Each interacts with the others in ways that are hard to predict.")
(p :class "text-stone-600"
"In SX, structure, style, logic, and data are all s-expressions:")
(~doc-code :code (highlight ";; Structure\n(div :class \"card\" (h2 title) (p body))\n\n;; Style\n(cssx card-style\n :bg white :rounded-lg :shadow-md :p 6)\n\n;; Logic\n(if (> (length items) 0)\n (map render-item items)\n (p \"No items found.\"))\n\n;; Data\n{:name \"Alice\" :role \"admin\" :active true}\n\n;; Component definition\n(defcomp ~user-card (&key user)\n (div :class \"card\"\n (h2 (get user \"name\"))\n (span :class \"badge\" (get user \"role\"))))" "lisp"))
(p :class "text-stone-600"
"The AI learns one syntax and applies it everywhere. The mental model does not fragment across subsystems. A " (code "div") " and an " (code "if") " and a " (code "defcomp") " are all lists. The model that generates one can generate all three, because they are the same thing."))
(~doc-section :title "The spec fits in a context window" :id "spec-fits"
(p :class "text-stone-600"
"The complete SX language specification — evaluator, parser, renderer, primitives — lives in four files totalling roughly 3,000 lines. An AI model with a 200k token context window can hold the " (em "entire language definition") " alongside the user's codebase and still have room to work. Compare this to JavaScript (the " (a :href "https://ecma-international.org/publications-and-standards/standards/ecma-262/" :class "text-violet-600 hover:underline" "ECMAScript specification") " is 900+ pages), or the combined specifications for HTML, CSS, and the DOM.")
(p :class "text-stone-600"
"This is not just a convenience — it changes what kind of code the AI produces. When the model has the full spec in context, it does not hallucinate nonexistent features. It does not confuse one version's semantics with another's. It knows exactly which primitives exist, which special forms are available, and how evaluation works — because it is reading the authoritative definition, not interpolating from training data.")
(p :class "text-stone-600"
"The spec is also written in SX. " (code "eval.sx") " defines the evaluator as s-expressions. " (code "parser.sx") " defines the parser as s-expressions. The language the AI is generating is the same language the spec is written in. There is no translation gap between \"understanding the language\" and \"using the language\" — they are the same act of reading s-expressions."))
(~doc-section :title "Structural validation is trivial" :id "structural-validation"
(p :class "text-stone-600"
"Validating AI output before executing it is a critical safety concern. With conventional languages, validation means running a full parser, type checker, and linter — each with their own error recovery modes and edge cases. With SX, structural validation is: " (em "do the parentheses balance?") " That is it. If they balance, the expression parses. If it parses, it can be evaluated.")
(p :class "text-stone-600"
"This makes it trivial to build AI pipelines that generate SX. Parse the output. If it parses, evaluate it in a sandbox. If it does not parse, the error is always the same kind — unmatched parentheses — and the fix is always mechanical. There is no \"your JSX is invalid because you used " (code "class") " instead of " (code "className") "\" or \"you forgot the semicolon after the type annotation but before the generic constraint.\"")
(p :class "text-stone-600"
"Beyond parsing, the SX " (a :href "/specs/primitives" :class "text-violet-600 hover:underline" "boundary system") " provides semantic validation. A pure component cannot call IO primitives — not by convention, but by the evaluator refusing to resolve them. An AI generating a component can produce whatever expressions it wants; the sandbox ensures only permitted operations execute. Validation is not a separate step bolted onto the pipeline. It is the language."))
(~doc-section :title "Components are self-documenting" :id "self-documenting"
(p :class "text-stone-600"
"A React component's interface is spread across prop types (or TypeScript interfaces), JSDoc comments, Storybook stories, and whatever documentation someone wrote. An AI reading a component must synthesize information from multiple sources to understand what it accepts and what it produces.")
(p :class "text-stone-600"
"An SX component declares everything in one expression:")
(~doc-code :code (highlight "(defcomp ~product-card (&key title price image &rest children)\n (div :class \"rounded border p-4\"\n (img :src image :alt title)\n (h3 :class \"font-bold\" title)\n (span :class \"text-lg\" (format-price price))\n children))" "lisp"))
(p :class "text-stone-600"
"The AI reads this and knows: it takes " (code "title") ", " (code "price") ", and " (code "image") " as keyword arguments, and " (code "children") " as rest arguments. It knows the output structure — a " (code "div") " with an image, heading, price, and slot for children. It knows this because the definition " (em "is") " the documentation. There is no separate spec to consult, no type file to find, no ambiguity about which props are required.")
(p :class "text-stone-600"
"This self-describing property scales across the entire component environment. An AI can " (code "(map ...)") " over every component in the registry, extract all parameter signatures, build a complete map of the UI vocabulary — and generate compositions that use it correctly, because the interface is declared in the same language the AI is generating."))
(~doc-section :title "Token efficiency" :id "token-efficiency"
(p :class "text-stone-600"
"LLMs operate on tokens. Every token costs compute, latency, and money. The information density of a representation — how much semantics per token — directly affects how much an AI can see, generate, and reason about within its context window and output budget.")
(p :class "text-stone-600"
"Compare equivalent UI definitions:")
(~doc-code :code (highlight ";; SX: 42 tokens\n(div :class \"card p-4\"\n (h2 :class \"font-bold\" title)\n (p body)\n (when footer\n (div :class \"mt-4 border-t pt-2\" footer)))" "lisp"))
(~doc-code :code (highlight "// React/JSX: ~75 tokens\n<div className=\"card p-4\">\n <h2 className=\"font-bold\">{title}</h2>\n <p>{body}</p>\n {footer && (\n <div className=\"mt-4 border-t pt-2\">{footer}</div>\n )}\n</div>" "python"))
(p :class "text-stone-600"
"The SX version is roughly 40% fewer tokens for equivalent semantics. No closing tags. No curly-brace interpolation. No " (code "className") " vs " (code "class") " distinction. Every token carries meaning. Over an entire application — dozens of components, hundreds of expressions — this compounds into significantly more code visible per context window and significantly less output the model must generate."))
(~doc-section :title "Composability is free" :id "composability"
(p :class "text-stone-600"
"The hardest thing for AI to get right in conventional frameworks is composition — how pieces fit together. React has rules about hooks. Vue has template vs script vs style sections. Angular has modules, declarations, and dependency injection. Each framework's composition model is a set of conventions the AI must learn and apply correctly.")
(p :class "text-stone-600"
"S-expressions compose by nesting. A list inside a list is a composition. There are no rules beyond this:")
(~doc-code :code (highlight ";; Compose components by nesting — that's it\n(~page-layout :title \"Dashboard\"\n (~sidebar\n (~nav-menu :items menu-items))\n (~main-content\n (map ~user-card users)\n (~pagination :page current-page :total total-pages)))" "lisp"))
(p :class "text-stone-600"
"No imports to manage. No registration steps. No render props, higher-order components, or composition APIs. The AI generates a nested structure and it works, because nesting is the only composition mechanism. This eliminates an entire class of errors that plague AI-generated code in conventional frameworks — the kind where each piece works in isolation but the assembly is wrong."))
(~doc-section :title "The feedback loop" :id "feedback-loop"
(p :class "text-stone-600"
"SX has no build step. Generated s-expressions can be evaluated immediately — in the browser, on the server, in a test harness. The AI generates an expression, the system evaluates it, the result is visible. If it is wrong, the AI reads the result (also an s-expression), adjusts, and regenerates. The loop is:")
(ol :class "list-decimal pl-5 text-stone-600 space-y-1 mt-2"
(li "AI generates SX expression")
(li "System parses (parentheses balance? done)")
(li "Evaluator runs in sandbox (boundary-enforced)")
(li "Result rendered or error returned (as s-expression)")
(li "AI reads result, iterates"))
(p :class "text-stone-600"
"Compare this to the conventional loop: AI generates code → linter runs → TypeScript compiles → bundler builds → browser loads → error appears in DevTools console → human copies error back to AI → AI regenerates. Each step is a different tool with different output formats. Each introduces latency and potential information loss.")
(p :class "text-stone-600"
"The SX loop is also " (em "uniform") ". The input is s-expressions. The output is s-expressions. The error messages are s-expressions. The AI never needs to parse a stack trace format or extract meaning from a webpack error. Everything is the same data structure, all the way down."))
(~doc-section :title "This site is the proof" :id "proof"
(p :class "text-stone-600"
"This is not theoretical. Everything you are looking at — every page, every component, every line of this essay — was produced by agentic AI. Not \"AI-assisted\" in the polite sense of autocomplete suggestions. " (em "Produced.") " The SX language specification. The parser. The evaluator. The renderer. The bootstrappers that transpile the spec to JavaScript and Python. The boundary enforcement system. The dependency analyser. The on-demand CSS engine. The client-side router. The component bundler. The syntax highlighter. This documentation site. The Docker deployment. All of it.")
(p :class "text-stone-600"
"The human driving this has never written a line of Lisp. Not Common Lisp. Not Scheme. Not Clojure. Not Emacs Lisp. Has never opened the codebase in VS Code, vi, or any other editor. Every file was created and modified through " (a :href "https://claude.ai/" :class "text-violet-600 hover:underline" "Claude") " running in a terminal — reading files, writing files, running commands, iterating on errors. The development environment is a conversation.")
(p :class "text-stone-600"
"That this works at all is a testament to s-expressions. The AI generates " (code "(defcomp ~card (&key title) (div :class \"p-4\" (h2 title)))") " and it is correct on the first attempt, because there is almost nothing to get wrong. The AI generates a 300-line spec file defining evaluator semantics and every parenthesis balances, because balancing parentheses is the " (em "only") " syntactic constraint. The AI writes a bootstrapper that reads " (code "eval.sx") " and emits JavaScript, and the output runs in the browser, because the source and target are both trees.")
(p :class "text-stone-600"
"Try this with React. Try generating a complete component framework — parser, evaluator, renderer, type system, macro expander, CSS engine, client router — through pure conversation with an AI, never touching an editor. The syntax tax alone would be fatal. JSX irregularities, hook ordering rules, import resolution, TypeScript generics, webpack configuration, CSS module scoping — each is a class of errors that burns tokens and breaks the flow. S-expressions eliminate all of them."))
(~doc-section :title "The development loop" :id "dev-loop"
(p :class "text-stone-600"
"The workflow looks like this: describe what you want. The AI reads the existing code — because it can, because s-expressions are transparent to any reader. It generates new expressions. It writes them to disk. It runs the server. It checks the output. If something breaks, it reads the error, adjusts, and regenerates. The human steers with intent; the AI handles the syntax, the structure, and the mechanical correctness.")
(p :class "text-stone-600"
"This is only possible because the representation is uniform. The AI does not need to switch between \"writing HTML mode\" and \"writing CSS mode\" and \"writing JavaScript mode\" and \"writing deployment config mode.\" It is always writing s-expressions. The cognitive load is constant. The error rate is constant. The speed is constant — regardless of whether it is generating a page layout, a macro expander, or a Docker healthcheck.")
(p :class "text-stone-600"
"The " (a :href "/essays/sx-sucks" :class "text-violet-600 hover:underline" "sx sucks") " essay copped to the AI authorship and framed it as a weakness — microwave dinner on a nice plate. But the framing was wrong. If a language is so well-suited to machine generation that one person with no Lisp experience can build a self-hosting language, a multi-target bootstrapper, a reactive component framework, and a full documentation site through pure agentic AI — that is not a weakness of the language. That is the language working exactly as it should."))
(~doc-section :title "What this changes" :id "what-changes"
(p :class "text-stone-600"
"The question is not whether AI will generate user interfaces. It already does. The question is what representation makes that generation most reliable, most efficient, and most safe. S-expressions — with their zero-syntax-tax grammar, uniform structure, self-describing components, structural validation, and sandboxed evaluation — are a strong answer.")
(p :class "text-stone-600"
"Not because they were designed for AI. " (a :href "https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)" :class "text-violet-600 hover:underline" "McCarthy") " invented them in 1958, decades before anyone imagined language models. But the properties that make s-expressions elegant for humans — minimalism, uniformity, composability, homoiconicity — turn out to be exactly the properties that make them tractable for machines. The simplest possible syntax is also the most machine-friendly syntax. This is not a coincidence. It is a consequence of what simplicity means.")
(p :class "text-stone-600"
"The era of AI-generated software is not coming. It is here. The question is which representations survive contact with it. The ones with the lowest syntax tax, the most uniform structure, and the tightest feedback loops will win — not because they are trendy, but because they are what the machines can actually produce reliably. S-expressions have been waiting sixty-seven years for a generation mechanism worthy of their simplicity. They finally have one."))))

View File

@@ -80,6 +80,8 @@
:summary "How SX enforces the boundary between host and embedded language, and what it looks like across targets.")
(dict :label "Separation of Concerns" :href "/essays/separation-of-concerns"
:summary "The web's HTML/CSS/JS split separates the framework's concerns, not your application's. Real separation is domain-specific.")
(dict :label "SX and AI" :href "/essays/sx-and-ai"
:summary "Why s-expressions are the most AI-friendly representation for web interfaces.")
(dict :label "sx sucks" :href "/essays/sx-sucks"
:summary "An honest accounting of everything wrong with SX and why you probably shouldn't use it.")))
@@ -114,6 +116,8 @@
(dict :label "Async IO" :href "/isomorphism/async-io")))
(define plans-nav-items (list
(dict :label "Status" :href "/plans/status"
:summary "Audit of all plans — what's done, what's in progress, and what remains.")
(dict :label "Reader Macros" :href "/plans/reader-macros"
:summary "Extensible parse-time transformations via # dispatch — datum comments, raw strings, and quote shorthand.")
(dict :label "SX-Activity" :href "/plans/sx-activity"
@@ -121,7 +125,13 @@
(dict :label "Predictive Prefetching" :href "/plans/predictive-prefetch"
:summary "Prefetch missing component definitions before the user clicks — hover a link, fetch its deps, navigate client-side.")
(dict :label "Content-Addressed Components" :href "/plans/content-addressed-components"
:summary "Components identified by CID, stored on IPFS, fetched from anywhere. Canonical serialization, content verification, federated sharing.")))
:summary "Components identified by CID, stored on IPFS, fetched from anywhere. Canonical serialization, content verification, federated sharing.")
(dict :label "Fragment Protocol" :href "/plans/fragment-protocol"
:summary "Structured sexp request/response for cross-service component transfer.")
(dict :label "Glue Decoupling" :href "/plans/glue-decoupling"
:summary "Eliminate all cross-app model imports via glue service layer.")
(dict :label "Social Sharing" :href "/plans/social-sharing"
:summary "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon.")))
(define bootstrappers-nav-items (list
(dict :label "Overview" :href "/bootstrappers/")

View File

@@ -1264,6 +1264,282 @@
". It extends Phase 3 by making more navigations go client-side without needing any IO bridge — purely by ensuring component definitions are available before they're needed.")
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 3 (client-side routing with deps checking). No dependency on Phase 4.")))))
;; ---------------------------------------------------------------------------
;; Plan Status Overview
;; ---------------------------------------------------------------------------
(defcomp ~plan-status-content ()
(~doc-page :title "Plan Status"
(p :class "text-lg text-stone-600 mb-6"
"Audit of all plans across the SX language and Rose Ash platform. Last updated March 2026.")
;; -----------------------------------------------------------------------
;; Completed
;; -----------------------------------------------------------------------
(~doc-section :title "Completed" :id "completed"
(div :class "space-y-4"
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(span :class "font-semibold text-stone-800" "Split Cart into Microservices"))
(p :class "text-sm text-stone-600" "Cart decomposed into 4 services: relations (internal, owns ContainerRelation), likes (internal, unified generic likes), orders (public, owns Order/OrderItem + SumUp checkout), and cart (thin CartItem CRUD). All three new services deployed with dedicated databases."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(span :class "font-semibold text-stone-800" "Ticket Purchase Through Cart"))
(p :class "text-sm text-stone-600" "Tickets flow through the cart like products: state=pending in cart, reserved at checkout, confirmed on payment. TicketDTO, CartSummaryDTO with ticket_count/ticket_total, CalendarService protocol methods all implemented."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(span :class "font-semibold text-stone-800" "Ticket UX Improvements"))
(p :class "text-sm text-stone-600" "+/- quantity buttons on entry pages and cart. Tickets grouped by event in cart display. Adjust quantity route, sold/basket counts, matching product card UX pattern."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 1: Dependency Analysis"))
(p :class "text-sm text-stone-600" "Per-page component bundles via deps.sx. Transitive closure, scan-refs, components-needed, page-css-classes. 15 tests, bootstrapped to both hosts."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 2: IO Detection"))
(p :class "text-sm text-stone-600" "Automatic IO classification. scan-io-refs, transitive-io-refs, compute-all-io-refs. Server expands IO components, serializes pure ones for client."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 3: Client-Side Routing"))
(p :class "text-sm text-stone-600" "router.sx spec, page registry via <script type=\"text/sx-pages\">, client route matching, try-first/fallback to server. Pure pages render without server roundtrips."))))
;; -----------------------------------------------------------------------
;; In Progress / Partial
;; -----------------------------------------------------------------------
(~doc-section :title "In Progress" :id "in-progress"
(div :class "space-y-4"
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase" "Partial")
(a :href "/plans/fragment-protocol" :class "font-semibold text-amber-900 underline" "Fragment Protocol"))
(p :class "text-sm text-stone-600" "Fragment GET infrastructure works. The planned POST/sexp structured protocol for transferring component definitions between services is not yet implemented. Fragment endpoints still use legacy GET + X-Fragment-Request headers."))
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase" "Partial")
(a :href "/isomorphism/" :class "font-semibold text-amber-900 underline" "Isomorphic Phase 4: Client Async & IO Bridge"))
(p :class "text-sm text-stone-600" "Some async evaluation infrastructure exists (helpers.py, async_eval.py). The io-bridge.sx spec file for client-side IO primitives (query -> REST, frag -> fetch) does not exist yet."))))
;; -----------------------------------------------------------------------
;; Not Started
;; -----------------------------------------------------------------------
(~doc-section :title "Not Started" :id "not-started"
(div :class "space-y-4"
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/plans/reader-macros" :class "font-semibold text-stone-800 underline" "Reader Macros"))
(p :class "text-sm text-stone-600" "# dispatch character for datum comments (#;), raw strings (#|...|), and quote shorthand (#'). Fully designed but no implementation in parser.sx or parser.py.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: spec in parser.sx, Python in parser.py, rebootstrap both targets."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/plans/sx-activity" :class "font-semibold text-stone-800 underline" "SX-Activity"))
(p :class "text-sm text-stone-600" "Federated SX over ActivityPub — 6 phases from SX wire format for activities to the evaluable web on IPFS. Existing AP infrastructure provides the foundation but no SX-specific federation code exists.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: shared/sx/activity.py (SX<->JSON-LD), shared/sx/ipfs.py, shared/sx/ref/ipfs-resolve.sx, shared/sx/registry.py, shared/sx/anchor.py."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/plans/glue-decoupling" :class "font-semibold text-stone-800 underline" "Cross-App Decoupling via Glue"))
(p :class "text-sm text-stone-600" "Eliminate all cross-app model imports by routing through a glue service layer. No glue/ directory exists. Apps are currently decoupled via HTTP interfaces and DTOs instead.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: glue/services/ for pages, page_config, calendars, marketplaces, cart_items, products, post_associations. 25+ cross-app imports to eliminate."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/plans/social-sharing" :class "font-semibold text-stone-800 underline" "Social Network Sharing"))
(p :class "text-sm text-stone-600" "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon via the account service. No models, blueprints, or platform clients created.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: SocialConnection model, social_crypto.py, platform OAuth clients (6), account/bp/social/ blueprint, share button fragment."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 5: Streaming & Suspense"))
(p :class "text-sm text-stone-600" "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately, fills in suspended parts. No suspense.sx spec or chunked transfer implementation.")
(p :class "text-sm text-stone-500 mt-1" "Depends on: Phase 4 (client async)."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 6: Full Isomorphism"))
(p :class "text-sm text-stone-600" "Runtime boundary optimizer, affinity annotations, offline data layer via Service Worker + IndexedDB, isomorphic testing harness.")
(p :class "text-sm text-stone-500 mt-1" "Depends on: all previous phases."))))))
;; ---------------------------------------------------------------------------
;; Fragment Protocol
;; ---------------------------------------------------------------------------
(defcomp ~plan-fragment-protocol-content ()
(~doc-page :title "Fragment Protocol"
(~doc-section :title "Context" :id "context"
(p "Fragment endpoints return raw sexp source (e.g., " (code "(~blog-nav-wrapper :items ...)") "). The consuming service embeds this in its page sexp, which the client evaluates. But service-specific components like " (code "~blog-nav-wrapper") " are only in that service's component env — not in the consumer's. So the consumer's " (code "client_components_tag()") " never sends them to the client, causing \"Unknown component\" errors.")
(p "The fix: transfer component definitions alongside fragments. Services tell the provider what they already have; the provider sends only what's missing."))
(~doc-section :title "What exists" :id "exists"
(div :class "rounded border border-green-200 bg-green-50 p-4"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Fragment GET infrastructure works (" (code "shared/infrastructure/fragments.py") ")")
(li (code "X-Fragment-Request") " header protocol for internal service calls")
(li "Content type negotiation for text/html and text/sx responses")
(li "Fragment caching and composition in page rendering"))))
(~doc-section :title "What remains" :id "remains"
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (strong "POST sexp protocol: ") "Switch from GET to POST with structured sexp body containing " (code ":components") " list of what consumer already has")
(li (strong "Structured response: ") (code "(fragment-response :defs (...) :content (...))") " — provider sends only missing component defs")
(li (strong (code "fragment_response()") " builder: ") "New function in helpers.py that diffs provider's component env against consumer's list")
(li (strong "Register received defs: ") "Consumer parses " (code ":defs") " from response and registers into its " (code "_COMPONENT_ENV"))
(li (strong "Shared blueprint factory: ") (code "create_fragment_blueprint(handlers)") " to deduplicate the identical fragment endpoint pattern across 8 services"))))
(~doc-section :title "Files to modify" :id "files"
(div :class "overflow-x-auto rounded border border-stone-200"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "File")
(th :class "px-3 py-2 font-medium text-stone-600" "Change")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/fragments.py")
(td :class "px-3 py-2 text-stone-700" "POST sexp body, parse response, register defs"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/helpers.py")
(td :class "px-3 py-2 text-stone-700" "fragment_response() builder"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/fragment_endpoint.py")
(td :class "px-3 py-2 text-stone-700" "NEW — shared blueprint factory"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "*/bp/fragments/routes.py")
(td :class "px-3 py-2 text-stone-700" "All 8 services: use create_fragment_blueprint"))))))))
;; ---------------------------------------------------------------------------
;; Glue Decoupling
;; ---------------------------------------------------------------------------
(defcomp ~plan-glue-decoupling-content ()
(~doc-page :title "Cross-App Decoupling via Glue Services"
(~doc-section :title "Context" :id "context"
(p "All cross-domain FK constraints have been dropped (with pragmatic exceptions for OrderItem.product_id and CartItem). Cross-domain writes go through internal HTTP and activity bus. However, " (strong "25+ cross-app model imports remain") " — apps still import from each other's models/ directories. This means every app needs every other app's code on disk to start.")
(p "The goal: eliminate all cross-app model imports. Every app only imports from its own models/, from shared/, and from a new glue/ service layer."))
(~doc-section :title "Current state" :id "current"
(p "Apps are partially decoupled via HTTP interfaces (fetch_data, call_action, send_internal_activity) and DTOs. The Cart microservice split (relations, likes, orders) is complete. But direct model imports persist in:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (strong "Cart") " — 9 files importing from market, events, blog")
(li (strong "Blog") " — 8 files importing from cart, events, market")
(li (strong "Events") " — 5 files importing from blog, market, cart")
(li (strong "Market") " — 1 file importing from blog")))
(~doc-section :title "What remains" :id "remains"
(div :class "space-y-3"
(div :class "rounded border border-stone-200 p-3"
(h4 :class "font-semibold text-stone-700" "1. glue/services/pages.py")
(p :class "text-sm text-stone-600" "Dict-based Post access for non-blog apps: get_page_by_slug, get_page_by_id, get_pages_by_ids, page_exists, search_posts."))
(div :class "rounded border border-stone-200 p-3"
(h4 :class "font-semibold text-stone-700" "2. glue/services/page_config.py")
(p :class "text-sm text-stone-600" "PageConfig CRUD: get_page_config, get_or_create_page_config, get_page_configs_by_ids."))
(div :class "rounded border border-stone-200 p-3"
(h4 :class "font-semibold text-stone-700" "3. glue/services/calendars.py")
(p :class "text-sm text-stone-600" "Calendar queries + entry associations (from blog): get_calendars_for_page, toggle_entry_association, get_associated_entries."))
(div :class "rounded border border-stone-200 p-3"
(h4 :class "font-semibold text-stone-700" "4. glue/services/marketplaces.py")
(p :class "text-sm text-stone-600" "MarketPlace CRUD (from blog+events): get_marketplaces_for_page, create_marketplace, soft_delete_marketplace."))
(div :class "rounded border border-stone-200 p-3"
(h4 :class "font-semibold text-stone-700" "5. glue/services/cart_items.py")
(p :class "text-sm text-stone-600" "CartItem/CalendarEntry queries for cart: get_cart_items, find_or_create_cart_item, clear_cart_for_order."))
(div :class "rounded border border-stone-200 p-3"
(h4 :class "font-semibold text-stone-700" "6. glue/services/products.py")
(p :class "text-sm text-stone-600" "Minimal Product access for cart orders: get_product."))
(div :class "rounded border border-stone-200 p-3"
(h4 :class "font-semibold text-stone-700" "7. Model registration + cleanup")
(p :class "text-sm text-stone-600" "register_models() in glue/setup.py, update all app.py files, delete moved service files."))))
(~doc-section :title "Docker consideration" :id "docker"
(p :class "text-stone-600" "For glue services to work in Docker (single app per container), model files from other apps must be importable. Recommended: try/except at import time — glue services that can't import a model raise ImportError at call time, which only happens if called from the wrong app."))))
;; ---------------------------------------------------------------------------
;; Social Sharing
;; ---------------------------------------------------------------------------
(defcomp ~plan-social-sharing-content ()
(~doc-page :title "Social Network Sharing"
(~doc-section :title "Context" :id "context"
(p "Rose Ash already has ActivityPub for federated social sharing. This plan adds OAuth-based sharing to mainstream social networks — Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon.")
(p "All social logic lives in the " (strong "account") " microservice. Content apps get a share button that opens the account share page."))
(~doc-section :title "What remains" :id "remains"
(~doc-note "Nothing has been implemented. This is the full scope of work.")
(div :class "space-y-4"
(div :class "rounded border border-stone-200 p-4"
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 1: Data Model + Encryption")
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
(li (code "shared/models/social_connection.py") " — SocialConnection model (user_id, platform, tokens, scopes, extra_data)")
(li (code "shared/infrastructure/social_crypto.py") " — Fernet encrypt/decrypt for tokens")
(li "Alembic migration for social_connections table")
(li "Environment variables for per-platform OAuth credentials")))
(div :class "rounded border border-stone-200 p-4"
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 2: Platform OAuth Clients")
(p :class "text-sm text-stone-600 mb-2" "All in " (code "account/services/social_platforms/") ":")
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
(li (code "base.py") " — SocialPlatform ABC, OAuthResult, ShareResult")
(li (code "meta.py") " — Facebook + Instagram + Threads (Graph API)")
(li (code "twitter.py") " — OAuth 2.0 with PKCE")
(li (code "linkedin.py") " — LinkedIn Posts API")
(li (code "mastodon.py") " — Dynamic app registration per instance")))
(div :class "rounded border border-stone-200 p-4"
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 3: Account Blueprint")
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
(li (code "account/bp/social/routes.py") " — /social/ list, /social/connect/<platform>/, /social/callback/<platform>/, /social/share/")
(li "Register before account blueprint (account has catch-all /<slug>/ route)")))
(div :class "rounded border border-stone-200 p-4"
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 4: Templates")
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
(li "Social panel — platform cards, connect/disconnect")
(li "Share panel — content preview, account checkboxes, share button")
(li "Share result — per-platform success/failure with links")))
(div :class "rounded border border-stone-200 p-4"
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 5: Share Button in Content Apps")
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
(li "share-button fragment from account service")
(li "Blog, events, market detail pages fetch and render the fragment")))
(div :class "rounded border border-stone-200 p-4"
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 6: Token Refresh + Share History")
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
(li "Automatic token refresh before posting")
(li "Optional social_shares table for history and duplicate prevention")))))))
;; ---------------------------------------------------------------------------
;; Isomorphic Architecture Roadmap

View File

@@ -282,6 +282,7 @@
"reflexive-web" (~essay-reflexive-web)
"server-architecture" (~essay-server-architecture)
"separation-of-concerns" (~essay-separation-of-concerns)
"sx-and-ai" (~essay-sx-and-ai)
:else (~essays-index-content)))
;; ---------------------------------------------------------------------------
@@ -505,8 +506,12 @@
:current (find-current plans-nav-items slug))
:selected (or (find-current plans-nav-items slug) ""))
:content (case slug
"status" (~plan-status-content)
"reader-macros" (~plan-reader-macros-content)
"sx-activity" (~plan-sx-activity-content)
"predictive-prefetch" (~plan-predictive-prefetch-content)
"content-addressed-components" (~plan-content-addressed-components-content)
"fragment-protocol" (~plan-fragment-protocol-content)
"glue-decoupling" (~plan-glue-decoupling-content)
"social-sharing" (~plan-social-sharing-content)
:else (~plans-index-content)))