diff --git a/.claude/skills/pseudocode/SKILL.md b/.claude/skills/pseudocode/SKILL.md index a857b08d..fd713e2a 100644 --- a/.claude/skills/pseudocode/SKILL.md +++ b/.claude/skills/pseudocode/SKILL.md @@ -257,7 +257,7 @@ public container Checkout for shop::Shop { if (reserved.isErr) { return Err(reserved.error) } - quote = number from self.Quote(cmd.sku, cmd.qty) + quote = number from Quote(cmd.sku, cmd.qty) order = shop::Order from { cmd, quote } paid = Result from shop::Payments.charge(order) if (paid.isErr) { diff --git a/CONFORMANCE/generation/README.md b/CONFORMANCE/generation/README.md index d303f55d..c295990d 100644 --- a/CONFORMANCE/generation/README.md +++ b/CONFORMANCE/generation/README.md @@ -38,7 +38,7 @@ frame "" # opens a frame; nested lines indent t ``` - **Participants** in order of first appearance in the trace; the entry's owner is first. A real trigger actor (client/scheduler/event) leads as its own lifeline and receives the entry's returns. A callable with no trigger omits the generic `caller` participant — the owner leads, and the entry's returns to that absent caller are suppressed (they reappear once a real incoming trigger exists). -- **Messages** in body evaluation order (§7). A chained expression emits one `message` per call, left-to-right; a `self.` call is `self` kind (from owner to owner). A `return` is a `return` message back to the caller. Every call has a matching return: a call to a disclosed callable expands inline and returns through its body; a call to a bodyless callable emits a synthesised `return ""` from callee to caller (the out-and-back response), its label empty and detail the callee's return type. +- **Messages** in body evaluation order (§7). A chained expression emits one `message` per call, left-to-right; a same-node call `Name(args)` is `self` kind (from owner to owner) and expands its disclosed body inline, a method on a local value stays a leaf `self` message (LANG.md §9.2, ADR-041). A `return` is a `return` message back to the caller. Every call has a matching return: a call to a disclosed callable expands inline and returns through its body; a call to a bodyless callable emits a synthesised `return ""` from callee to caller (the out-and-back response), its label empty and detail the callee's return type. - **Frames**: `if`/`else` → `frame alt`; `for`/`while` → `frame loop`. The frame's body messages indent two spaces under it; a closing line is implicit at the dedent. An `else` arm emits a second `frame alt "else "`. An `if` whose then-arm ends in `return` and has no `else` is a guard clause: the steps following it in the same block run only when the guard is false, so they emit as that second `frame alt "else "`. A branch (then or else) whose traced body is empty — e.g. its only step is a return suppressed for a triggerless entry — emits no frame. ### Data entity view (`data`) diff --git a/CONFORMANCE/lexical/2-3-result-keywords.pds b/CONFORMANCE/lexical/2-3-result-keywords.pds new file mode 100644 index 00000000..444f000e --- /dev/null +++ b/CONFORMANCE/lexical/2-3-result-keywords.pds @@ -0,0 +1,6 @@ +// §2.3 — `Ok`/`Err`/`true`/`false` tokenise as keywords; `Result`, `number`, +// and `self` (no longer reserved, ADR-041) stay IDENT. + +Make(): Result; +self = Ok(true) +return Ok(false) diff --git a/CONFORMANCE/lexical/2-3-result-self-keywords.tokens b/CONFORMANCE/lexical/2-3-result-keywords.tokens similarity index 68% rename from CONFORMANCE/lexical/2-3-result-self-keywords.tokens rename to CONFORMANCE/lexical/2-3-result-keywords.tokens index 57d42498..70a42d08 100644 --- a/CONFORMANCE/lexical/2-3-result-self-keywords.tokens +++ b/CONFORMANCE/lexical/2-3-result-keywords.tokens @@ -9,14 +9,12 @@ COMMA@4:22 "," KW_ERR@4:24 "Err" RANGLE@4:27 ">" SEMI@4:28 ";" -IDENT@5:1 "r" -EQ@5:3 "=" -KW_SELF@5:5 "self" -DOT@5:9 "." -IDENT@5:10 "Run" -LPAREN@5:13 "(" -KW_TRUE@5:14 "true" -RPAREN@5:18 ")" +IDENT@5:1 "self" +EQ@5:6 "=" +KW_OK@5:8 "Ok" +LPAREN@5:10 "(" +KW_TRUE@5:11 "true" +RPAREN@5:15 ")" KW_RETURN@6:1 "return" KW_OK@6:8 "Ok" LPAREN@6:10 "(" diff --git a/CONFORMANCE/lexical/2-3-result-self-keywords.pds b/CONFORMANCE/lexical/2-3-result-self-keywords.pds deleted file mode 100644 index f7f25303..00000000 --- a/CONFORMANCE/lexical/2-3-result-self-keywords.pds +++ /dev/null @@ -1,6 +0,0 @@ -// §2.3 — `self`/`Ok`/`Err`/`true`/`false` tokenise as keywords; `Result` and -// primitive `number` stay IDENT (classified in type position). - -Make(): Result; -r = self.Run(true) -return Ok(false) diff --git a/CONFORMANCE/lexical/README.md b/CONFORMANCE/lexical/README.md index c4238fbd..a4614aa3 100644 --- a/CONFORMANCE/lexical/README.md +++ b/CONFORMANCE/lexical/README.md @@ -90,5 +90,5 @@ marker); a `?` in type position is a parse error, not a lexical one. | `2-1-comments-and-docs` | §2.1 — `//`/`/* */` discarded; `//!` → `INNER_DOC`; `///` → `DOC`. | | `2-2-paths-colon-vs-dot` | §2.2 — `::` (`COLONCOLON`) walks a path; `.` (`DOT`) accesses/invokes. | | `2-3-keywords-vs-idents` | §2.3 — keywords vs. greedily-matched identifiers that merely contain a keyword. | -| `2-3-result-self-keywords` | §2.3 — `self`/`Ok`/`Err`/`true`/`false` as keywords; `Result` and primitives as `IDENT`. | +| `2-3-result-keywords` | §2.3 — `Ok`/`Err`/`true`/`false` as keywords; `Result`, primitives, and `self` (freed by ADR-041) as `IDENT`. | | `2-4-hash-disambiguation` | §2.4 — `#name` tag in a doc, `#[` macro open, and a literal `#` inside a string. | diff --git a/CONFORMANCE/static/3-3-unresolved-type.pds b/CONFORMANCE/static/3-3-unresolved-type.pds index 5330268e..994916aa 100644 --- a/CONFORMANCE/static/3-3-unresolved-type.pds +++ b/CONFORMANCE/static/3-3-unresolved-type.pds @@ -7,6 +7,6 @@ public system S; public container C for S { - ping(): Ghost { return self.pong() } + ping(): Ghost { return pong() } pong(): number { return 1 } } diff --git a/CONFORMANCE/static/5-1-arg-type-mismatch.pds b/CONFORMANCE/static/5-1-arg-type-mismatch.pds index 9a82d5ff..0df505dc 100644 --- a/CONFORMANCE/static/5-1-arg-type-mismatch.pds +++ b/CONFORMANCE/static/5-1-arg-type-mismatch.pds @@ -6,6 +6,6 @@ public system S; public container C for S { - run(): void { self.take("x") } + run(): void { take("x") } take(n: number): void { } } diff --git a/CONFORMANCE/static/5-1-call-arity.pds b/CONFORMANCE/static/5-1-call-arity.pds index e4e4f9b0..57094f72 100644 --- a/CONFORMANCE/static/5-1-call-arity.pds +++ b/CONFORMANCE/static/5-1-call-arity.pds @@ -6,6 +6,6 @@ public system S; public container C for S { - run(): void { self.add(1, 2, 3) } + run(): void { add(1, 2, 3) } add(a: number, b: number): number { return a } } diff --git a/CONFORMANCE/static/5-1-self-call-unresolved.diagnostics b/CONFORMANCE/static/5-1-self-call-unresolved.diagnostics index fd02f2eb..10d3a5d4 100644 --- a/CONFORMANCE/static/5-1-self-call-unresolved.diagnostics +++ b/CONFORMANCE/static/5-1-self-call-unresolved.diagnostics @@ -1 +1 @@ -`self.missing` does not name a callable of `C` +`missing` does not name a callable of `C` diff --git a/CONFORMANCE/static/5-1-self-call-unresolved.pds b/CONFORMANCE/static/5-1-self-call-unresolved.pds index 5b71e06f..9b2925c9 100644 --- a/CONFORMANCE/static/5-1-self-call-unresolved.pds +++ b/CONFORMANCE/static/5-1-self-call-unresolved.pds @@ -1,4 +1,4 @@ -// LANG.md §5.1 (ADR-004): `self.Name(args)` must name a callable of the +// LANG.md §5.1 (ADR-041): `Name(args)` must name a callable of the // enclosing node. `missing` is not declared on `C`. //! example @@ -6,5 +6,5 @@ public system S; public container C for S { - run(): void { self.missing() } + run(): void { missing() } } diff --git a/CONFORMANCE/static/5-missing-return.pds b/CONFORMANCE/static/5-missing-return.pds index 94a952da..e5a3cca4 100644 --- a/CONFORMANCE/static/5-missing-return.pds +++ b/CONFORMANCE/static/5-missing-return.pds @@ -10,7 +10,7 @@ public system Banking; public container Mainframe for Banking { Get(): Result { - r = Result from self.Fetch() + r = Result from Fetch() if (r.isErr) { return Err(r.error) } diff --git a/CONFORMANCE/static/6-option-value-on-none.pds b/CONFORMANCE/static/6-option-value-on-none.pds index 7c76d7c4..cae0208d 100644 --- a/CONFORMANCE/static/6-option-value-on-none.pds +++ b/CONFORMANCE/static/6-option-value-on-none.pds @@ -10,7 +10,7 @@ public system Registry; public container Directory for Registry { public find(id: number): Option { - o = Option from self.lookup(id) + o = Option from lookup(id) if (o.isNone) { return Some(o.value) } diff --git a/CONFORMANCE/static/7-from-expr-conversion-mismatch.pds b/CONFORMANCE/static/7-from-expr-conversion-mismatch.pds index fee29176..5a430720 100644 --- a/CONFORMANCE/static/7-from-expr-conversion-mismatch.pds +++ b/CONFORMANCE/static/7-from-expr-conversion-mismatch.pds @@ -10,6 +10,6 @@ public data Dog; public system Zoo; public container Pen for Zoo { - AdoptCat(): Cat { return Cat from self.MakeDog() } + AdoptCat(): Cat { return Cat from MakeDog() } MakeDog(): Dog; } diff --git a/CONFORMANCE/static/7-from-expr-ok.pds b/CONFORMANCE/static/7-from-expr-ok.pds index c1b89aa1..c2b1433e 100644 --- a/CONFORMANCE/static/7-from-expr-ok.pds +++ b/CONFORMANCE/static/7-from-expr-ok.pds @@ -8,6 +8,6 @@ public data Cat; public system Zoo; public container Pen for Zoo { - AdoptCat(): Cat { return Cat from self.MakeCat() } + AdoptCat(): Cat { return Cat from MakeCat() } MakeCat(): Cat; } diff --git a/CONFORMANCE/static/7-from-result-target.pds b/CONFORMANCE/static/7-from-result-target.pds index 7c8e4654..2a6b38ca 100644 --- a/CONFORMANCE/static/7-from-result-target.pds +++ b/CONFORMANCE/static/7-from-result-target.pds @@ -10,7 +10,7 @@ public system Banking; public container Mainframe for Banking { Get(): Result { - r = Result from self.Fetch() + r = Result from Fetch() return r } Fetch(): Result; diff --git a/CONFORMANCE/static/7-rebind-rejected.pds b/CONFORMANCE/static/7-rebind-rejected.pds index 764860b1..4309bd56 100644 --- a/CONFORMANCE/static/7-rebind-rejected.pds +++ b/CONFORMANCE/static/7-rebind-rejected.pds @@ -9,8 +9,8 @@ public system Banking; public container Mainframe for Banking { Run(): void { - r = Info from self.Make() - r = Info from self.Make() + r = Info from Make() + r = Info from Make() } Make(): Info; } diff --git a/CONFORMANCE/syntax/3-option.pds b/CONFORMANCE/syntax/3-option.pds index 9ae7f496..ae15a327 100644 --- a/CONFORMANCE/syntax/3-option.pds +++ b/CONFORMANCE/syntax/3-option.pds @@ -10,7 +10,7 @@ public system Registry; public container Directory for Registry { /// The optional person on file for an id. public find(id: number): Option { - o = Option from self.lookup(id) + o = Option from lookup(id) if (o.isNone) { return None } diff --git a/CONFORMANCE/syntax/5-self-call.pds b/CONFORMANCE/syntax/5-self-call.pds index f868d685..f084b168 100644 --- a/CONFORMANCE/syntax/5-self-call.pds +++ b/CONFORMANCE/syntax/5-self-call.pds @@ -1,10 +1,10 @@ -// LANG.md §5 (ADR-004): a same-node callable is invoked via `self.Name(args)`. +// LANG.md §5 (ADR-041): a same-node callable is invoked via `Name(args)`. public system Banking; public container Mainframe for Banking { A(): void { - self.B() + B() } B(): void; } diff --git a/CONFORMANCE/syntax/7-chaining.pds b/CONFORMANCE/syntax/7-chaining.pds index bab60bff..699dbcba 100644 --- a/CONFORMANCE/syntax/7-chaining.pds +++ b/CONFORMANCE/syntax/7-chaining.pds @@ -8,6 +8,6 @@ data Info { owner: string } public container Mainframe for Banking { Run(): void { a = string from Store::Repo.fetch(1).value.owner - b = string from self.Pick().first().tag + b = string from Pick().first().tag } } diff --git a/CONFORMANCE/syntax/7-from-expr.pds b/CONFORMANCE/syntax/7-from-expr.pds index c1e281d0..382bf91b 100644 --- a/CONFORMANCE/syntax/7-from-expr.pds +++ b/CONFORMANCE/syntax/7-from-expr.pds @@ -10,8 +10,8 @@ public system Zoo; public container Pen for Zoo { Make(): Cat { - c = Cat from self.fetch() - cats = Cat[] from self.fetchMany() + c = Cat from fetch() + cats = Cat[] from fetchMany() return c } fetch(): Cat; diff --git a/CONFORMANCE/syntax/7-statements.pds b/CONFORMANCE/syntax/7-statements.pds index 6210cce6..1dd8ae22 100644 --- a/CONFORMANCE/syntax/7-statements.pds +++ b/CONFORMANCE/syntax/7-statements.pds @@ -17,10 +17,10 @@ public container Mainframe for Banking { x = BankingInfo from a.value } while (cond) { - self.step() + step() } for (item in items) { - self.handle(item) + handle(item) } } } diff --git a/LANG.md b/LANG.md index 10f53ac8..3450d103 100644 --- a/LANG.md +++ b/LANG.md @@ -47,8 +47,8 @@ Every cross-reference is a **fully-qualified name (FQN)**, derived from the file ``` system container component person data constant for from -public self -return Ok Err Some None +public return Ok Err +Some None if else while in true false feature given when then and but @@ -123,7 +123,7 @@ Option // optional value: Some(T) | None ### 3.3 Type expressions A named type (`BankingInfo`), generic (`Result`, `Option`), or array (`T[]`). `[]` is the only type suffix; an absent value is modeled with `Option` (§6). -Every named type — a field, parameter, or return type, and each generic argument — MUST resolve to a primitive (§3.1), `Result`/`Option` (§3.2), or a declared type or node (§3.4, §3.5, §4); an unresolved type MUST be rejected (ADR-022). A reference to a declared type or node MUST be its FQN (§8.1), including one in the same module; only primitives, `Result`/`Option`, and `self` are bare. +Every named type — a field, parameter, or return type, and each generic argument — MUST resolve to a primitive (§3.1), `Result`/`Option` (§3.2), or a declared type or node (§3.4, §3.5, §4); an unresolved type MUST be rejected (ADR-022). A reference to a declared type or node MUST be its FQN (§8.1), including one in the same module; only primitives and `Result`/`Option` are bare. ### 3.4 Data declarations A `data` type models any payload — DTOs, entities, messages alike. It MAY stay a **black box** with `;` (fields not yet disclosed). @@ -224,8 +224,8 @@ A function-shaped declaration is a callable. - All calls are request/response. A call to a resolvable callable MUST pass one argument per declared parameter, and each inferable argument MUST match its parameter's type; a wrong arity or argument type MUST be rejected (ADR-022, ADR-023). - Every callable MUST declare a return type; a callable without one MUST be rejected (ADR-040). `void` declares that no value is returned. A disclosed non-`void` callable MUST return a value on every path. - A `return` operand whose type is determinable — a literal, an `Ok`/`Err`/`Some`/`None` marker (§6), a `from` (§7.2), a typed binding, or a call to a resolvable callable — MUST match the declared return type; a mismatch MUST be rejected. A union variant satisfies its union type (§3.5). A bare reference resolving to a `data` record or a node is not a value and MUST be rejected (§7.2). -- A bare name in a body MUST resolve to a parameter, a binding, or a `for` binding; it MUST NOT resolve to a node or union variant (ADR-030) — those are referenced by FQN (§8.1). An unresolved bare name MUST be rejected (ADR-022). -- A same-node callable is invoked via `self.Name(args)` (`self` = the enclosing node); this also covers recursion. +- A bare name read as a value MUST resolve to a parameter, a binding, or a `for` binding; it MUST NOT resolve to a node or union variant (ADR-030) — those are referenced by FQN (§8.1). An unresolved bare name MUST be rejected (ADR-022). +- A same-node callable is invoked by a bare call `Name(args)` — a sibling, or the enclosing callable itself for recursion (ADR-041). A bare name in call position MUST resolve to a callable on the enclosing node; one matching no callable on the node MUST be rejected (ADR-022). - A callable's name and its parameter names MUST NOT be reserved words (§2.3) — `container`, `component`, `data`, and `for` are reserved. - A call statement MAY ignore its `Result` (the call still renders as a message). - A black-box callable shows in C4 as a capability; a call to it in a sequence diagram is a single message with no expansion. @@ -402,7 +402,7 @@ Each path segment becomes an FQN segment, which MUST be an identifier (§2.2). A A module has four distinct namespaces: **type names** (`data` declarations and hoisted record variants, §3.5), **node names** (`system`/`container`/`component`/`person`), **feature names** (§5.2), and **value names** (`constant`, §3.6). A name MUST be unique within its namespace; the four do not collide — a `data`, a `container`, a `feature`, and a `constant` MAY share a name. Callable and parameter names are scoped to their owner, not the module. -Every reference to a node, type, union variant, or constant MUST be its FQN, including a reference to one declared in the same module (ADR-030). A bare leaf name MUST NOT resolve to a node, type, variant, or constant; it resolves only to a parameter, a binding, or a `for` binding (§7). `self` and member access (§7.1) are unaffected, as are the primitives (§3.1) and `Result`/`Option` (§3.2). Within `banking/core.pds`, a sibling node is addressed `banking::core::Other`, never `Other`. An FQN names a node by its module path and name only; the system→container→component nesting (§4) is carried by `for`, not the name. A structural drill — a node addressed through its C4 ancestry, `Container::Component` or `module::System::Container::Component` — is not an FQN and MUST NOT resolve (ADR-036). +Every reference to a node, type, union variant, or constant MUST be its FQN, including a reference to one declared in the same module (ADR-030). A bare leaf name read as a value MUST NOT resolve to a node, type, variant, or constant; it resolves only to a parameter, a binding, or a `for` binding (§7). A bare name in call position resolves to a callable on the enclosing node (§5.1, ADR-041). Member access (§7.1) is unaffected, as are the primitives (§3.1) and `Result`/`Option` (§3.2). Within `banking/core.pds`, a sibling node is addressed `banking::core::Other`, never `Other`. An FQN names a node by its module path and name only; the system→container→component nesting (§4) is carried by `for`, not the name. A structural drill — a node addressed through its C4 ancestry, `Container::Component` or `module::System::Container::Component` — is not an FQN and MUST NOT resolve (ADR-036). An FQN's first segment is a **root**. The file-derived module paths above are the local roots; a `[dependencies]` entry (§8.3) adds one root per declared dependency. @@ -453,7 +453,7 @@ From disclosed callables per §7. A **triggered** callable (one bearing a trigge A call to a **disclosed** callee expands inline: the callee becomes the active lifeline, its body traces in place, and each of its `return`s is a return message to its caller's lifeline. A call to a **black-box** callable renders as a single message with no expansion (§5.1). A callee already in flight on the call path (direct or mutual recursion) MUST NOT re-expand; it renders as a single message. -In a chained expression, each call is its own message, emitted left-to-right; field accesses between calls are local. A `self.` call renders as a self-message. +In a chained expression, each call is its own message, emitted left-to-right; field accesses between calls are local. A same-node call (`Name(args)`, §5.1) renders as a self-message and expands its callee's body inline, exactly as a direct call to a disclosed callee does (ADR-041); recursion is stack-guarded as above. A method on a local value or chain intermediate renders as a leaf self-message — it names no node callable and has no body to follow. Each lifeline head card shows the participant's C4 kind and name. A `container` or `component` participant SHOULD also show its `for` ancestry (enclosing node names, outermost first) dimmed beneath the name. Every declared participant SHOULD show its `///` summary, as on a C4 card (§9.1). A synthesised initiator carries neither. @@ -572,11 +572,12 @@ AddExpr = MulExpr { ( "+" | "-" ) MulExpr } ; MulExpr = UnaryExpr { ( "*" | "/" | "%" ) UnaryExpr } ; UnaryExpr = ( "!" | "-" ) UnaryExpr | Postfix ; Postfix = Primary { "." Ident [ "(" [ Args ] ")" ] } ; // field access / call, chained -Primary = Ref | Literal | "(" Expr ")" ; +Primary = Call | Ref | Literal | "(" Expr ")" ; +Call = Ident "(" [ Args ] ")" ; // same-node callable (§5.1) Marker = ( "Ok" | "Err" | "Some" ) [ "(" Expr ")" ] | "None" ; // built-in generic constructors FromExpr = Type "from" ( "{" [ Expr { "," Expr } ] "}" | Expr ) ; // brace source set, or a single value; "[]" target composes an array Args = Expr { "," Expr } ; -Ref = "self" | Ident | Path ; // self or an FQN +Ref = Ident | Path ; // a local name or an FQN Path = Ident { "::" Ident } ; Literal = String | Number | Bool ; diff --git a/crates/pseudoscript-doc/src/highlight.rs b/crates/pseudoscript-doc/src/highlight.rs index 0c441cef..1c7fc154 100644 --- a/crates/pseudoscript-doc/src/highlight.rs +++ b/crates/pseudoscript-doc/src/highlight.rs @@ -88,7 +88,6 @@ fn token_class(token: &Token) -> Option<&'static str> { | TokenKind::KwFor | TokenKind::KwFrom | TokenKind::KwPublic - | TokenKind::KwSelf | TokenKind::KwReturn | TokenKind::KwOk | TokenKind::KwErr diff --git a/crates/pseudoscript-doc/src/render.rs b/crates/pseudoscript-doc/src/render.rs index f15d128c..6d4db153 100644 --- a/crates/pseudoscript-doc/src/render.rs +++ b/crates/pseudoscript-doc/src/render.rs @@ -745,7 +745,7 @@ mod tests { #[test] fn every_diagram_is_adaptive_inline_svg() { - let src = "//! m\n/// Sys.\npublic system S;\npublic container C for m::S {\n #[manual]\n public Run() {\n self.Step()\n }\n Step();\n}\n"; + let src = "//! m\n/// Sys.\npublic system S;\npublic container C for m::S {\n #[manual]\n public Run() {\n Step()\n }\n Step();\n}\n"; let g = graph(&[WorkspaceModule::new("m", src)]); let pages = build_pages( &g, diff --git a/crates/pseudoscript-emit/src/project.rs b/crates/pseudoscript-emit/src/project.rs index 3d74546c..50c2b980 100644 --- a/crates/pseudoscript-emit/src/project.rs +++ b/crates/pseudoscript-emit/src/project.rs @@ -801,6 +801,53 @@ fn project_black_box(graph: &Graph, node: &GraphNode, entry: &str) -> SequenceSc } } +/// A self-message on `active`'s lifeline (from and to itself). +fn self_message(active: &str, method: &str, detail: String) -> SeqItem { + SeqItem::Message(Message { + from: active.to_owned(), + to: active.to_owned(), + kind: MessageKind::SelfMsg, + label: method.to_owned(), + detail, + }) +} + +/// A same-node call (`LANG.md` §9.2, ADR-041): the self-message, then the +/// callee's disclosed body expanded inline on the same lifeline (stack-guarded +/// against recursion), so its cross-boundary calls reach the diagram. A leaf +/// (black-box or in-flight) call needs no synthesised return — the self-message +/// is its own round trip. +fn trace_self_call( + graph: &Graph, + active: &str, + method: &str, + order: &mut Vec, + stack: &mut Vec, +) -> Vec { + let invoked = format!("{active}::{method}"); + let sig = graph.node(&invoked).and_then(|n| n.signature.as_ref()); + let mut items = vec![self_message( + active, + method, + sig.map(call_detail).unwrap_or_default(), + )]; + if let Some(invoked_body) = graph.body(&invoked) + && !stack.iter().any(|f| f == &invoked) + { + stack.push(invoked.clone()); + items.extend(trace_body( + graph, + active, + active, + invoked_body, + order, + stack, + )); + stack.pop(); + } + items +} + /// Traces the body of `active` (the executing node) into ordered items. `caller` /// is the lifeline `active`'s `return`s land on; `stack` is the callables in /// flight. Disclosed callees expand inline (the active lifeline switches to the @@ -857,13 +904,12 @@ fn trace_body( } } Step::SelfCall { method } => { - items.push(SeqItem::Message(Message { - from: active.to_owned(), - to: active.to_owned(), - kind: MessageKind::SelfMsg, - label: method.clone(), - detail: String::new(), - })); + items.extend(trace_self_call(graph, active, method, order, stack)); + } + Step::LocalCall { method } => { + // A method on a local value or chain intermediate: a leaf + // self-message — it names no node callable, no body to follow. + items.push(self_message(active, method, String::new())); } Step::Return { marker } => { // An empty `caller` is the suppressed generic caller (a triggerless @@ -1285,7 +1331,7 @@ mod tests { "m".to_owned(), "//! m\npublic system Shop;\npublic container Api for Shop;\n\ /// Validates orders.\npublic component Validator for m::Api {\n \ - #[http]\n public Check(): void { self.Help() }\n Help(): void;\n}" + #[http]\n public Check(): void { Help() }\n Help(): void;\n}" .to_owned(), ); let Scene::Sequence(seq) = project( diff --git a/crates/pseudoscript-emit/tests/features/svg.feature b/crates/pseudoscript-emit/tests/features/svg.feature index ac508100..6850df40 100644 --- a/crates/pseudoscript-emit/tests/features/svg.feature +++ b/crates/pseudoscript-emit/tests/features/svg.feature @@ -60,3 +60,28 @@ Feature: SVG rendering smoke And the rendered SVG contains "Place" And the rendered SVG contains "Inventory" And the rendered SVG contains "reserve" + + Scenario: a same-node call's downstream cross-node call reaches the sequence (issue #71, ADR-041) + Given the model: + """ + //! chat + public data Event { id: number } + public system Chat; + public container Topic for Chat { + produce(event: chat::Event): void; + } + public container Guest for Chat { + #[manual] + public postMessage(event: chat::Event): void { + sendEvent(event) + } + sendEvent(event: chat::Event): void { + Topic.produce(event) + } + } + """ + When I project the "sequence" view of "chat::Guest::postMessage" + Then the rendered SVG is well-formed + And the rendered SVG contains "sendEvent" + And the rendered SVG contains "Topic" + And the rendered SVG contains "produce" diff --git a/crates/pseudoscript-format/src/printer.rs b/crates/pseudoscript-format/src/printer.rs index 2935ce6a..fe6c96fe 100644 --- a/crates/pseudoscript-format/src/printer.rs +++ b/crates/pseudoscript-format/src/printer.rs @@ -480,6 +480,11 @@ impl Printer { self.write_postfix_seg(seg); } } + // §5.1, ADR-041: a bare same-node call `Name(args)`. + ExprKind::OwnCall { name, args } => { + self.push(&name.name); + self.write_args(args); + } ExprKind::Ref(r) => self.write_ref(r), ExprKind::Literal(lit) => self.write_literal(lit), ExprKind::Unary { op, expr, .. } => { @@ -510,20 +515,24 @@ impl Printer { self.push("."); self.push(&seg.name.name); if let Some(args) = &seg.call_args { - self.push("("); - for (i, arg) in args.iter().enumerate() { - if i > 0 { - self.push(", "); - } - self.write_expr(arg); + self.write_args(args); + } + } + + /// Writes a parenthesised, comma-separated argument list `(a, b)`. + fn write_args(&mut self, args: &[Expr]) { + self.push("("); + for (i, arg) in args.iter().enumerate() { + if i > 0 { + self.push(", "); } - self.push(")"); + self.write_expr(arg); } + self.push(")"); } fn write_ref(&mut self, r: &Ref) { match r { - Ref::SelfNode(_) => self.push("self"), Ref::Path(path) => self.write_path(path), } } diff --git a/crates/pseudoscript-format/tests/features/golden.feature b/crates/pseudoscript-format/tests/features/golden.feature index 05f238d9..fe3679d1 100644 --- a/crates/pseudoscript-format/tests/features/golden.feature +++ b/crates/pseudoscript-format/tests/features/golden.feature @@ -25,9 +25,9 @@ Feature: Golden canonical outputs Then the result equals "public component Repo for S {\n fetch(id: number): Result;\n}\n" Scenario: nested body, control flow, from-composition, and postfix chain - Given the source "public container M for B {\nrun(s:Source):Result{\ntree=Ast from {s.text,s.path}\nx=string from Repo.fetch(id).value.owner\nfor(item in xs){self.handle(item)}\nreturn Ok(tree)\n}\n}" + Given the source "public container M for B {\nrun(s:Source):Result{\ntree=Ast from {s.text,s.path}\nx=string from Repo.fetch(id).value.owner\nfor(item in xs){handle(item)}\nreturn Ok(tree)\n}\n}" When I format it - Then the result equals "public container M for B {\n run(s: Source): Result {\n tree = Ast from { s.text, s.path }\n x = string from Repo.fetch(id).value.owner\n for (item in xs) {\n self.handle(item)\n }\n return Ok(tree)\n }\n}\n" + Then the result equals "public container M for B {\n run(s: Source): Result {\n tree = Ast from { s.text, s.path }\n x = string from Repo.fetch(id).value.owner\n for (item in xs) {\n handle(item)\n }\n return Ok(tree)\n }\n}\n" Scenario: a feature reformats to one step per line Given the source "feature Open for Bank{given \"a\"\nwhen \"b\"\nthen \"c\"}" diff --git a/crates/pseudoscript-format/tests/format.rs b/crates/pseudoscript-format/tests/format.rs index 3ff8e792..e0c84fac 100644 --- a/crates/pseudoscript-format/tests/format.rs +++ b/crates/pseudoscript-format/tests/format.rs @@ -48,7 +48,7 @@ public container Web for Shop { return Err(line.error) } } - r = Result from self.finalize(cart) + r = Result from finalize(cart) return Ok(r.value) } diff --git a/crates/pseudoscript-ide/tests/completion_adversarial.rs b/crates/pseudoscript-ide/tests/completion_adversarial.rs index 56e81600..ca68fd0f 100644 --- a/crates/pseudoscript-ide/tests/completion_adversarial.rs +++ b/crates/pseudoscript-ide/tests/completion_adversarial.rs @@ -103,7 +103,7 @@ fn type_position_offers_types_only() { #[test] fn generic_argument_position_offers_types_only() { let l = labels( - "//! m\npublic data Money { amount: number }\npublic system A {\n find(): Result<~ > { return self.find() }\n}\n", + "//! m\npublic data Money { amount: number }\npublic system A {\n find(): Result<~ > { return find() }\n}\n", ); assert!(has(&l, "Money") || has(&l, "number"), "{l:?}"); assert_none(&l, &["public", "container", "feature", "Ok", "Err"]); @@ -171,15 +171,6 @@ fn binding_type_annotation_offers_types_only() { // --- member `.` : only the receiver's members ---------------------------- -#[test] -fn self_member_offers_own_members_not_other_nodes() { - let l = labels( - "//! m\npublic system A {\n alpha(): void;\n}\npublic container C for m::A {\n beta(): void {\n self.~\n }\n}\n", - ); - assert!(has(&l, "beta"), "own member missing: {l:?}"); - assert_none(&l, &["alpha"]); // A's member must not leak onto C -} - #[test] fn cross_module_member_offers_public_only() { // `dep::core::Svc.` — a private member of a dependency node is not callable diff --git a/crates/pseudoscript-lsp-core/src/analysis.rs b/crates/pseudoscript-lsp-core/src/analysis.rs index 060a8a80..12ccddea 100644 --- a/crates/pseudoscript-lsp-core/src/analysis.rs +++ b/crates/pseudoscript-lsp-core/src/analysis.rs @@ -46,7 +46,7 @@ pub struct DefTarget { } /// Resolves the identifier at `position` in module `from_fqn` to its definition -/// — across files and into `self.`/node members. +/// — across files and into same-node calls / node members. #[must_use] pub fn definition( ws: &Workspace, diff --git a/crates/pseudoscript-lsp-core/src/complete.rs b/crates/pseudoscript-lsp-core/src/complete.rs index f731a089..a1dba09d 100644 --- a/crates/pseudoscript-lsp-core/src/complete.rs +++ b/crates/pseudoscript-lsp-core/src/complete.rs @@ -70,9 +70,12 @@ mod tests { #[test] fn adapter_scopes_members_with_prefix() { - let src = "//! m\n\nsystem S {\n run(): void {\n self.he\n }\n helper(x: number): uuid;\n}\n"; + // Member completion after a node receiver's `.` offers that node's + // members (`helper`), not the general keyword scope, through the LSP + // adapter. + let src = "//! m\n\nsystem S {\n run(): void {\n Engine.he\n }\n}\ncomponent Engine for m::S {\n helper(x: number): uuid;\n}\n"; let ws = workspace(&[("m", src)]); - let offset = (src.find("self.he").unwrap() + "self.he".len()) as u32; + let offset = (src.find("Engine.he").unwrap() + "Engine.he".len()) as u32; let labels = labels_at(&ws, "m", src, offset); assert!(labels.contains(&"helper".to_owned()), "{labels:?}"); assert!( diff --git a/crates/pseudoscript-lsp-core/src/refs.rs b/crates/pseudoscript-lsp-core/src/refs.rs index 6173276e..3c21ddb9 100644 --- a/crates/pseudoscript-lsp-core/src/refs.rs +++ b/crates/pseudoscript-lsp-core/src/refs.rs @@ -163,8 +163,8 @@ mod tests { #[test] fn references_to_an_operation_include_decl_and_calls() { - // An operation's declaration and its `self.`-call are the same symbol. - let src = "//! m\n\nsystem S {\n run(): void { self.op() }\n op(): void {}\n}\n"; + // An operation's declaration and its bare same-node call are the same symbol. + let src = "//! m\n\nsystem S {\n run(): void { op() }\n op(): void {}\n}\n"; let mods = [("m", src)]; let workspace = ws(&mods); let owned = owned(&mods); diff --git a/crates/pseudoscript-lsp/tests/features/semantic_tokens.feature b/crates/pseudoscript-lsp/tests/features/semantic_tokens.feature index 6b020916..f55ea1d2 100644 --- a/crates/pseudoscript-lsp/tests/features/semantic_tokens.feature +++ b/crates/pseudoscript-lsp/tests/features/semantic_tokens.feature @@ -23,12 +23,12 @@ Feature: LSP semantic tokens And the "id" token has type "property" And the "uuid" token has type "type" - Scenario: callables, parameters, types, self, and calls + Scenario: callables, parameters, types, and bare same-node calls Given the inline source: """ system S { run(name: string): uuid { - return self.alloc(name) + return alloc(name) } } """ @@ -37,7 +37,6 @@ Feature: LSP semantic tokens And the "run" token is declared And the "name" token has type "parameter" And the "string" token has type "type" - And the "self" token has type "keyword" And the "alloc" token has type "method" Scenario: a macro invocation is one decorator span diff --git a/crates/pseudoscript-model/src/check.rs b/crates/pseudoscript-model/src/check.rs index d051f361..12eba8ee 100644 --- a/crates/pseudoscript-model/src/check.rs +++ b/crates/pseudoscript-model/src/check.rs @@ -618,22 +618,43 @@ impl Checker<'_> { for_each_expr(block, &mut |expr| self.check_call_at(expr, owner, vars)); } - /// Checks one call's arity and argument types when its receiver resolves to a - /// same-module callable. Only the first segment is the call on the receiver. + /// Checks one call's arity and argument types when it resolves to a + /// same-module callable: a bare same-node call `Name(args)` (§5.1) on the + /// enclosing node, or a `receiver.method(args)` whose first segment resolves + /// to a same-module node. fn check_call_at(&mut self, expr: &Expr, owner: &str, vars: &FxHashMap) { - let ExprKind::Postfix { base, segments } = &expr.kind else { - return; - }; - let Some(seg) = segments.first() else { return }; - let Some(args) = &seg.call_args else { return }; - let Some(node) = self.call_receiver_node(base, owner) else { - return; - }; + match &expr.kind { + ExprKind::OwnCall { name, args } => { + self.check_call_signature(owner, &name.name, name.span, args, vars); + } + ExprKind::Postfix { base, segments } => { + let Some(seg) = segments.first() else { return }; + let Some(args) = &seg.call_args else { return }; + let Some(node) = self.call_receiver_node(base) else { + return; + }; + self.check_call_signature(&node, &seg.name.name, seg.span, args, vars); + } + _ => {} + } + } + + /// Checks a resolved call's arity against `node`'s callable `method`, then + /// its argument types. A name not naming a callable of `node` is left to the + /// existence checks (`check_self_call_at` / cross-module resolution). + fn check_call_signature( + &mut self, + node: &str, + method: &str, + span: Span, + args: &[Expr], + vars: &FxHashMap, + ) { let Some(params) = self .model - .members(&node) + .members(node) .iter() - .find(|m| m.kind == MemberKind::Callable && m.name == seg.name.name) + .find(|m| m.kind == MemberKind::Callable && m.name == method) .map(|m| m.param_types.clone()) else { return; @@ -642,10 +663,9 @@ impl Checker<'_> { self.check_arg_types(args, ¶ms, vars); } else { self.error( - seg.span, + span, format!( - "callable `{}` expects {} argument(s), got {}", - seg.name.name, + "callable `{method}` expects {} argument(s), got {}", params.len(), args.len() ), @@ -674,14 +694,14 @@ impl Checker<'_> { } } - /// The same-module node a call's receiver resolves to: the enclosing node for - /// `self`, or a node named by the receiver path's leaf. `None` for a value - /// receiver, a name that is not a node, or a qualified path into another - /// module — a cross-module node's members are not in this single-module - /// model (ADR-022), and a leaf-only match would land on the wrong node. - fn call_receiver_node(&self, base: &Expr, owner: &str) -> Option { + /// The same-module node a `receiver.method` call's receiver resolves to: a + /// node named by the receiver path's leaf. `None` for a value receiver, a + /// name that is not a node, or a qualified path into another module — a + /// cross-module node's members are not in this single-module model (ADR-022), + /// and a leaf-only match would land on the wrong node. A bare same-node call + /// `Name(args)` resolves directly on the enclosing node, not here (§5.1). + fn call_receiver_node(&self, base: &Expr) -> Option { match &base.kind { - ExprKind::Ref(Ref::SelfNode(_)) => Some(owner.to_owned()), ExprKind::Ref(Ref::Path(path)) => { if path.segments.len() > 1 && !self.model.module_path.is_empty() { let module = &path.segments[..path.segments.len() - 1]; @@ -902,23 +922,19 @@ impl Checker<'_> { } } - /// §5.1 / ADR-004: `self.Name(args)` MUST name a callable of the enclosing - /// node `owner`. + /// §5.1 / ADR-041: a bare same-node call `Name(args)` MUST name a callable of + /// the enclosing node `owner`. fn check_self_calls(&mut self, block: &Block, owner: &str) { for_each_expr(block, &mut |expr| self.check_self_call_at(expr, owner)); } - /// Checks one `self.Method(args)` call: the first segment, when a call - /// directly on `self`, must name a callable of the enclosing node. + /// Checks one bare same-node call `Name(args)`: `Name` must name a callable + /// of the enclosing node. fn check_self_call_at(&mut self, expr: &Expr, owner: &str) { - let ExprKind::Postfix { base, segments } = &expr.kind else { + let ExprKind::OwnCall { name, .. } = &expr.kind else { return; }; - if matches!(&base.kind, ExprKind::Ref(Ref::SelfNode(_))) - && let Some(seg) = segments.first() - && seg.call_args.is_some() - && !self.owner_has_callable(owner, &seg.name.name) - { + if !self.owner_has_callable(owner, &name.name) { let hint = { let cands: Vec<&str> = self .model @@ -927,13 +943,13 @@ impl Checker<'_> { .filter(|m| m.kind == MemberKind::Callable) .map(|m| m.name.as_str()) .collect(); - suggest(&seg.name.name, &cands) + suggest(&name.name, &cands) }; self.error( - seg.span, + name.span, format!( - "`self.{}` does not name a callable of `{owner}`{hint}", - seg.name.name + "`{}` does not name a callable of `{owner}`{hint}", + name.name ), ); } @@ -1150,27 +1166,31 @@ impl Checker<'_> { } } - /// The declared return type of a `Receiver.Method(args)` call whose receiver - /// resolves in-module (`self` or a same-module node) and whose `Method` is a - /// callable of that node, as a [`Ty`]; `None` otherwise. + /// The declared return type of a call whose callee resolves in-module, as a + /// [`Ty`]; `None` otherwise. Covers a bare same-node call `Name(args)` on the + /// enclosing node (§5.1) and a `Receiver.Method(args)` whose receiver is a + /// same-module node, when the callee is a callable of that node. fn call_return(&self, expr: &Expr, owner: &str) -> Option { let mut expr = expr; while let ExprKind::Paren(inner) = &expr.kind { expr = inner; } - let ExprKind::Postfix { base, segments } = &expr.kind else { - return None; - }; - let [seg] = segments.as_slice() else { - return None; + let (node, method): (String, &str) = match &expr.kind { + ExprKind::OwnCall { name, .. } => (owner.to_owned(), name.name.as_str()), + ExprKind::Postfix { base, segments } => { + let [seg] = segments.as_slice() else { + return None; + }; + seg.call_args.as_ref()?; // a call segment, not a field access + (self.call_receiver_node(base)?, seg.name.name.as_str()) + } + _ => return None, }; - seg.call_args.as_ref()?; // a call segment, not a field access - let node = self.call_receiver_node(base, owner)?; let member = self .model .members(&node) .iter() - .find(|m| m.kind == MemberKind::Callable && m.name == seg.name.name)?; + .find(|m| m.kind == MemberKind::Callable && m.name == method)?; Some(ty_from_rendered(&member.ty)) } @@ -1726,6 +1746,11 @@ fn walk_expr(expr: &Expr, f: &mut impl FnMut(&Expr)) { payload: Some(payload), .. } => walk_expr(payload, f), + ExprKind::OwnCall { args, .. } => { + for arg in args { + walk_expr(arg, f); + } + } ExprKind::Unary { expr, .. } | ExprKind::Paren(expr) => walk_expr(expr, f), ExprKind::Binary { left, right, .. } => { walk_expr(left, f); diff --git a/crates/pseudoscript-model/src/check/cross_module.rs b/crates/pseudoscript-model/src/check/cross_module.rs index cde1cc1c..cb11fb51 100644 --- a/crates/pseudoscript-model/src/check/cross_module.rs +++ b/crates/pseudoscript-model/src/check/cross_module.rs @@ -197,7 +197,13 @@ impl Ctx<'_> { self.check_expr(right); } ExprKind::Ref(Ref::Path(path)) => self.check_value_ref(path), - ExprKind::Ref(Ref::SelfNode(_)) | ExprKind::Literal(_) => {} + // A same-node call is in-module; only its args can cross a boundary. + ExprKind::OwnCall { args, .. } => { + for arg in args { + self.check_expr(arg); + } + } + ExprKind::Literal(_) => {} } } diff --git a/crates/pseudoscript-model/src/check/result_flow.rs b/crates/pseudoscript-model/src/check/result_flow.rs index e94eff8d..09604cf2 100644 --- a/crates/pseudoscript-model/src/check/result_flow.rs +++ b/crates/pseudoscript-model/src/check/result_flow.rs @@ -207,6 +207,11 @@ fn check_expr(expr: &Expr, env: &Bindings, out: &mut Vec) { check_expr(left, env, out); check_expr(right, env, out); } + ExprKind::OwnCall { args, .. } => { + for arg in args { + check_expr(arg, env, out); + } + } ExprKind::Ref(_) | ExprKind::Literal(_) => {} } } diff --git a/crates/pseudoscript-model/src/complete.rs b/crates/pseudoscript-model/src/complete.rs index e9e27d2c..791d6ff4 100644 --- a/crates/pseudoscript-model/src/complete.rs +++ b/crates/pseudoscript-model/src/complete.rs @@ -82,7 +82,7 @@ pub fn completion(ws: &Workspace, from_fqn: &str, src: &str, offset: u32) -> Vec // A `container`'s parent must be a `system`, a `component`'s a `container` // (§4); only those kinds are offered. A feature target is any node. Some((i, TokenKind::KwFor)) => node_items(ws, from_fqn, parent_kind(&tokens, i)), - _ => general_items(ws, from_fqn), + _ => general_items(ws, from_fqn, offset), } } @@ -179,7 +179,7 @@ fn member_items( tokens: &[Token], dot: usize, ) -> Vec { - // The token-based path covers `self.`, `Module::Node.`, and a bare binding. + // The token-based path covers `Module::Node.` and a bare binding. // A multi-step chain (`Repo.fetch(id).value.`) has no single base token, so // fall back to typing the receiver expression at the cursor via the AST. let owner = owner_before(ws, from_fqn, tokens, dot) @@ -208,8 +208,8 @@ fn member_items( } /// The `(module, node-name)` the base token before `tokens[dot]` denotes: -/// `self`'s enclosing node, a `::`-qualified node in another module -/// (`identity::sessions.`), or an in-scope node name in this module. +/// a `::`-qualified node in another module (`identity::sessions.`), or an +/// in-scope node name in this module. fn owner_before( ws: &Workspace, from_fqn: &str, @@ -218,10 +218,6 @@ fn owner_before( ) -> Option<(String, String)> { let base = tokens.get(dot.checked_sub(1)?)?; match base.kind { - TokenKind::KwSelf => { - let node = enclosing_node(&ws.module(from_fqn)?.ast, base.span.start)?; - Some((from_fqn.to_owned(), node)) - } // `a::b::Node.` — the base is qualified, so it names a symbol in the // module its qualifiers spell, not this one. Offered only when public // (or in this module), mirroring `::` path resolution (§8.2). @@ -323,10 +319,11 @@ fn type_items(ws: &Workspace) -> Vec { primitives.chain(declared).collect() } -/// Keywords, this module's own symbols, and the other modules in the workspace -/// (so a cross-module reference can be started — pick the module, then `::` -/// drills into its public symbols). -fn general_items(ws: &Workspace, from_fqn: &str) -> Vec { +/// Keywords, this module's own symbols, the enclosing node's callables (bare +/// same-node calls, §5.1, ADR-041), and the other modules in the workspace (so a +/// cross-module reference can be started — pick the module, then `::` drills into +/// its public symbols). +fn general_items(ws: &Workspace, from_fqn: &str, offset: u32) -> Vec { let keywords = TokenKind::KEYWORDS .iter() .map(|k| item(k, CompletionKind::Keyword, "keyword")); @@ -338,7 +335,24 @@ fn general_items(ws: &Workspace, from_fqn: &str) -> Vec { .model .symbols() .map(|s| item(&s.name, symbol_kind(s.kind), &s.fqn)); - keywords.chain(symbols).chain(modules).collect() + // Inside a node body, the enclosing node's own callables are reachable as + // bare same-node calls `Name(args)` — private siblings included (same node). + let own_calls: Vec = enclosing_node(&entry.ast, offset) + .map(|node| { + entry + .model + .members(&node) + .iter() + .filter(|m| m.kind == MemberKind::Callable) + .map(|m| item(&m.name, CompletionKind::Method, &m.detail)) + .collect() + }) + .unwrap_or_default(); + keywords + .chain(symbols) + .chain(own_calls) + .chain(modules) + .collect() } /// The completion kind for a declared symbol. @@ -482,11 +496,13 @@ mod tests { } #[test] - fn members_after_self_dot() { + fn siblings_complete_as_bare_calls() { + // §5.1, ADR-041: inside a node body a sibling callable completes as a + // bare same-node call `Name(args)` — no `self.` qualifier. let src = - "//! m\n\nsystem S {\n run(): void {\n self.\n }\n helper(x: number): uuid;\n}\n"; + "//! m\n\nsystem S {\n run(): void {\n he\n }\n helper(x: number): uuid;\n}\n"; let ws = workspace(&[("m", src)]); - let offset = (src.find("self.").unwrap() + "self.".len()) as u32; + let offset = (src.find(" he").unwrap() + " he".len()) as u32; let labels = labels_at(&ws, "m", src, offset); assert!(labels.contains(&"helper".to_owned()), "{labels:?}"); assert!(labels.contains(&"run".to_owned()), "{labels:?}"); @@ -587,20 +603,6 @@ mod tests { // Each narrowing context must stay scoped — the trigger before the prefix // governs — and must not leak the general keyword set. - #[test] - fn members_after_self_dot_with_prefix() { - let src = "//! m\n\nsystem S {\n run(): void {\n self.he\n }\n helper(x: number): uuid;\n}\n"; - let ws = workspace(&[("m", src)]); - let offset = (src.find("self.he").unwrap() + "self.he".len()) as u32; - let labels = labels_at(&ws, "m", src, offset); - assert!(labels.contains(&"helper".to_owned()), "{labels:?}"); - assert!(labels.contains(&"run".to_owned()), "{labels:?}"); - assert!( - !labels.contains(&"system".to_owned()), - "general scope leaked: {labels:?}" - ); - } - #[test] fn types_after_colon_with_prefix() { let src = "//! m\n\ndata D { x: numb }\n"; diff --git a/crates/pseudoscript-model/src/graph.rs b/crates/pseudoscript-model/src/graph.rs index acaa592c..d6c991d8 100644 --- a/crates/pseudoscript-model/src/graph.rs +++ b/crates/pseudoscript-model/src/graph.rs @@ -282,8 +282,16 @@ pub enum Step { /// The invoked method name. method: String, }, - /// A `self.method(..)` call — a self-message (ADR-004). + /// A same-node call `method(..)` — a self-message whose callee body the + /// renderer follows, exactly as a direct call to a disclosed callee + /// (§5.1, §9.2, ADR-041). SelfCall { + /// The invoked callable, resolved on the enclosing node. + method: String, + }, + /// A method on a local value or chain intermediate (`x.f()`) — a leaf + /// self-message; it names no node callable and has no body to follow. + LocalCall { /// The invoked method name. method: String, }, @@ -707,7 +715,7 @@ impl Builder<'_> { for arg in args { self.trace_expr(arg, owner_fqn, module, entry, out); } - self.emit_call(base.as_ref(), &base_target, seg, owner_fqn, out); + self.emit_call(&base_target, seg, owner_fqn, out); } } } @@ -730,6 +738,16 @@ impl Builder<'_> { } } } + ExprKind::OwnCall { name, args } => { + // §9.2: a same-node call. Args trace left-to-right first, then + // the call itself; the renderer follows the callee's body. + for arg in args { + self.trace_expr(arg, owner_fqn, module, entry, out); + } + out.push(Step::SelfCall { + method: name.name.clone(), + }); + } ExprKind::Marker { payload, .. } => { if let Some(payload) = payload { self.trace_expr(payload, owner_fqn, module, entry, out); @@ -756,15 +774,14 @@ impl Builder<'_> { entry: &ModuleEntry, out: &mut Vec, ) -> CallTarget { - match &base.kind { - ExprKind::Ref(Ref::SelfNode(_)) => CallTarget::SelfNode, - ExprKind::Ref(Ref::Path(path)) => { - CallTarget::Node(self.canonicalize(&path_str(path), module)) - } - _ => { - self.trace_expr(base, owner_fqn, module, entry, out); - CallTarget::Local - } + // A local value, a chain intermediate, or a same-node call result: + // trace the base (a same-node call emits its own step) and treat any + // `.method(..)` over it as local. + if let ExprKind::Ref(Ref::Path(path)) = &base.kind { + CallTarget::Node(self.canonicalize(&path_str(path), module)) + } else { + self.trace_expr(base, owner_fqn, module, entry, out); + CallTarget::Local } } @@ -772,7 +789,6 @@ impl Builder<'_> { /// `.method(args)` segment over a resolved base. fn emit_call( &mut self, - base: &Expr, base_target: &CallTarget, seg: &PostfixSeg, owner_fqn: &str, @@ -780,9 +796,6 @@ impl Builder<'_> { ) { let method = seg.name.name.clone(); match base_target { - CallTarget::SelfNode if is_self(base) => { - out.push(Step::SelfCall { method }); - } CallTarget::Node(target_fqn) => { out.push(Step::Call { target_fqn: target_fqn.clone(), @@ -801,8 +814,8 @@ impl Builder<'_> { } } // A method on a local value / intermediate result (`x.f()`, - // `a.b().c()`): a self-message on the owner — local to the body. - _ => out.push(Step::SelfCall { method }), + // `a.b().c()`): a leaf self-message on the owner — local to the body. + CallTarget::Local => out.push(Step::LocalCall { method }), } } @@ -856,8 +869,6 @@ impl Builder<'_> { /// How a postfix chain's base resolves for call-edge purposes. enum CallTarget { - /// `self` — calls are self-messages on the owner. - SelfNode, /// A named node, resolved to this FQN. Node(String), /// A local value or an intermediate result of an earlier call. @@ -877,7 +888,7 @@ fn resolve_path(path: &Path, module: &str) -> String { /// call whose leading target is a node (`Web.lookup()` → `Web`), or `self`. A /// bare binding reference (`a`) does not name a node, so it yields `None`: only /// a call's *receiver* is a node, a lone identifier is a local binding. -fn expr_node_fqn(expr: &Expr, owner_fqn: &str, _module: &str) -> Option { +fn expr_node_fqn(expr: &Expr, _owner_fqn: &str, _module: &str) -> Option { let ExprKind::Postfix { base, segments } = &expr.kind else { return None; }; @@ -888,7 +899,6 @@ fn expr_node_fqn(expr: &Expr, owner_fqn: &str, _module: &str) -> Option } match &base.kind { ExprKind::Ref(Ref::Path(path)) => Some(path_str(path)), - ExprKind::Ref(Ref::SelfNode(_)) => Some(owner_fqn.to_owned()), _ => None, } } @@ -1019,11 +1029,6 @@ fn path_str(path: &Path) -> String { .join("::") } -/// Whether an expression is exactly `self`. -fn is_self(expr: &Expr) -> bool { - matches!(&expr.kind, ExprKind::Ref(Ref::SelfNode(_))) -} - /// The `Ok`/`Err`/`Some`/`None` marker of a returned expression, or empty if it /// is none of them. fn return_marker(expr: Option<&Expr>) -> String { @@ -1049,7 +1054,7 @@ fn expr_label(expr: &Expr) -> String { } label } - ExprKind::Ref(Ref::SelfNode(_)) => "self".to_owned(), + ExprKind::OwnCall { name, .. } => format!("{}()", name.name), ExprKind::Ref(Ref::Path(path)) => path_str(path), ExprKind::Unary { op, expr, .. } => format!("{}{}", op.spelling(), expr_label(expr)), // §7.5: a binary condition renders through the same labeller, so an diff --git a/crates/pseudoscript-model/src/infer.rs b/crates/pseudoscript-model/src/infer.rs index a3a274ad..6ab9d23d 100644 --- a/crates/pseudoscript-model/src/infer.rs +++ b/crates/pseudoscript-model/src/infer.rs @@ -123,6 +123,11 @@ fn expr_type(ws: &Workspace, from_fqn: &str, expr: &ast::Expr) -> Option // `T from …` produces a value of the target type (ADR-035). ast::ExprKind::From { ty, .. } => Some(crate::model::render_type(ty)), ast::ExprKind::Postfix { base, segments } => postfix_type(ws, from_fqn, base, segments), + // §5.1: a bare same-node call resolves to a callable on the enclosing + // node; its type is that callable's declared return type (ADR-041). + ast::ExprKind::OwnCall { name, .. } => { + own_call_type(ws, from_fqn, expr.span.start, &name.name) + } // §7.5: arithmetic yields `number`, every other operator yields `bool`. ast::ExprKind::Binary { op, .. } => Some( match op { @@ -187,13 +192,27 @@ fn postfix_type( result } -/// The node/data owner a postfix base denotes: `self`'s enclosing node, or the -/// node/data a path names. +/// The declared return type of a bare same-node call (`Name(args)`, ADR-041): +/// find the callable named `name` on the node enclosing byte `at`, return its +/// declared type. Shared by `expr_type` (the direct result) and `base_owner` +/// (a postfix chain rooted on that result). +fn own_call_type(ws: &Workspace, from_fqn: &str, at: u32, name: &str) -> Option { + let node = enclosing_node(&ws.module(from_fqn)?.ast, at)?; + ws.module_model(from_fqn)? + .members(&node) + .iter() + .find(|m| m.name == name && m.kind == crate::MemberKind::Callable) + .map(|m| m.ty.clone()) +} + +/// The node/data owner a postfix base denotes: the node/data a path names. fn base_owner(ws: &Workspace, from_fqn: &str, base: &ast::Expr) -> Option<(String, String)> { match &base.kind { - ast::ExprKind::Ref(ast::Ref::SelfNode(span)) => { - let node = enclosing_node(&ws.module(from_fqn)?.ast, span.start)?; - Some((from_fqn.to_owned(), node)) + // A chain rooted at a bare same-node call: `Pick().first()` — resolve + // the call's return type, then the node/data that type names (ADR-041). + ast::ExprKind::OwnCall { name, .. } => { + let ty = own_call_type(ws, from_fqn, base.span.start, &name.name)?; + type_owner(ws, from_fqn, &ty) } ast::ExprKind::Ref(ast::Ref::Path(path)) => { let segments: Vec<&str> = path.segments.iter().map(|s| s.name.as_str()).collect(); @@ -402,6 +421,24 @@ mod tests { assert_eq!(owner, ("syntax".to_owned(), "Token".to_owned())); } + #[test] + fn owner_at_dot_types_a_chain_rooted_at_a_bare_same_node_call() { + // Regression (#71/#73): a postfix chain rooted at a bare same-node call — + // `pick().kind` — must type. `pick()` returns `Token`, so the `.` after + // the call resolves to the `Token` owner for member completion. Before + // the `OwnCall` arm in `base_owner`, the chain fell to `None` (ADR-041). + let src = "//! m\n\n\ + public data Token { kind: string }\n\n\ + public component Picker for M {\n \ + pick(): Token;\n \ + public run(): string {\n \ + return pick().kind\n }\n}\n"; + let workspace = ws(&[("m", src)]); + let dot = (src.find("pick().kind").unwrap() + "pick()".len()) as u32; + let owner = owner_at_dot(&workspace, "m", src, dot).expect("typed chain receiver"); + assert_eq!(owner, ("m".to_owned(), "Token".to_owned())); + } + #[test] fn local_types_read_literal_and_from_annotations() { let src = "//! m\n\nsystem S {\n go(): void {\n n: number = 42\n p: Parsed = Parsed from { n }\n }\n}\n\npublic data Parsed { x: number }\n"; diff --git a/crates/pseudoscript-model/src/resolve.rs b/crates/pseudoscript-model/src/resolve.rs index 6c12bdf5..6feec103 100644 --- a/crates/pseudoscript-model/src/resolve.rs +++ b/crates/pseudoscript-model/src/resolve.rs @@ -43,8 +43,8 @@ pub fn resolve_at(ws: &Workspace, from_fqn: &str, src: &str, offset: u32) -> Opt } // A member's own declaration name (a callable or field) resolves to itself; - // members live in the member index, not the symbol table, so this is the - // only bare-name form that reaches them (ADR-004: no bare member references). + // members live in the member index, not the symbol table. The other bare-name + // form that reaches a member is a same-node call, handled below (ADR-041). if let Some((owner, member)) = ws .module(from_fqn) .and_then(|entry| entry.model.member_at(clicked)) @@ -59,6 +59,33 @@ pub fn resolve_at(ws: &Workspace, from_fqn: &str, src: &str, offset: u32) -> Opt }); } + // A bare same-node call `Name(args)` (§5.1, ADR-041): the clicked Ident is + // followed by `(` and is not a `::` path tail; it names a callable of the + // enclosing node. (A bare name in value position still does not reach a + // member — ADR-030; the declaration form is handled above.) + let prev_is_colon = idx >= 1 && tokens[idx - 1].kind == TokenKind::ColonColon; + if !prev_is_colon + && tokens + .get(idx + 1) + .is_some_and(|t| t.kind == TokenKind::LParen) + && let Some(entry) = ws.module(from_fqn) + && let Some(node) = enclosing_node(&entry.ast, clicked.start) + && let Some(member) = entry + .model + .members(&node) + .iter() + .find(|m| m.name == tokens[idx].text && m.kind == crate::MemberKind::Callable) + { + let owner_fqn = entry + .model + .symbol(&node) + .map_or_else(|| node.clone(), |s| s.fqn.clone()); + return Some(Hit { + clicked, + ..member_hit(member, &node, &owner_fqn, from_fqn.to_owned()) + }); + } + // Otherwise the clicked token is part of a `::` path (possibly one segment). let segments = path_segments(&tokens, idx); resolve_path(ws, from_fqn, &segments).map(|hit| Hit { clicked, ..hit }) @@ -168,7 +195,7 @@ fn unique_symbol<'a>(ws: &'a Workspace, from_fqn: &str, name: &str) -> Option<&' matches.next().is_none().then_some(first) } -/// Resolves `.member` after a `self` or node/data base — including a qualified, +/// Resolves `.member` after a node/data base — including a qualified, /// cross-module base (`a::Svc.op`). fn resolve_member(ws: &Workspace, from_fqn: &str, tokens: &[Token], idx: usize) -> Option { let member_name = tokens[idx].text.as_str(); @@ -177,10 +204,6 @@ fn resolve_member(ws: &Workspace, from_fqn: &str, tokens: &[Token], idx: usize) let base = tokens.get(base_idx)?; let symbol = match base.kind { - TokenKind::KwSelf => { - let node = enclosing_node(&ws.module(from_fqn)?.ast, base.span.start)?; - ws.module(from_fqn)?.model.symbol(&node)? - } TokenKind::Ident => { let segments = path_segments(tokens, base_idx); resolve_node(ws, from_fqn, &segments)? @@ -358,8 +381,10 @@ mod tests { } #[test] - fn self_member_resolves_to_callable() { - let src = "//! m\n\nsystem S {\n run(): void { self.helper() }\n helper(): void {}\n}\n"; + fn bare_same_node_call_resolves_to_callable() { + // §5.1, ADR-041: a bare same-node call `helper()` resolves to the + // enclosing node's callable — goto-definition lands on its declaration. + let src = "//! m\n\nsystem S {\n run(): void { helper() }\n helper(): void {}\n}\n"; let mods = [("m", src)]; let ws = workspace(&mods); let hit = hit_at(&ws, "m", src, "helper", 1); diff --git a/crates/pseudoscript-model/src/semantic.rs b/crates/pseudoscript-model/src/semantic.rs index f069d061..9fda6794 100644 --- a/crates/pseudoscript-model/src/semantic.rs +++ b/crates/pseudoscript-model/src/semantic.rs @@ -120,7 +120,6 @@ fn is_keyword(kind: TokenKind) -> bool { | TokenKind::KwFor | TokenKind::KwFrom | TokenKind::KwPublic - | TokenKind::KwSelf | TokenKind::KwReturn | TokenKind::KwOk | TokenKind::KwErr @@ -311,8 +310,15 @@ fn expr_tokens(expr: &ast::Expr, out: &mut Vec) { } } ast::ExprKind::Ref(ast::Ref::Path(path)) => ref_path(path, out), - // `self` is a keyword and literals are coloured by the token pass. - ast::ExprKind::Ref(ast::Ref::SelfNode(_)) | ast::ExprKind::Literal(_) => {} + // A bare same-node call: the callee name colours as a method (§5.1). + ast::ExprKind::OwnCall { name, args } => { + push(out, name.span, SemKind::Method, false); + for arg in args { + expr_tokens(arg, out); + } + } + // Literals are coloured by the token pass. + ast::ExprKind::Literal(_) => {} ast::ExprKind::Unary { expr, .. } => expr_tokens(expr, out), ast::ExprKind::Binary { left, right, .. } => { expr_tokens(left, out); @@ -444,12 +450,12 @@ mod tests { #[test] fn callable_param_type_and_calls() { - let src = "//! m\n\nsystem S {\n run(name: string): uuid {\n return self.alloc(name)\n }\n}\n"; + let src = "//! m\n\nsystem S {\n run(name: string): uuid {\n return alloc(name)\n }\n alloc(n: string): uuid;\n}\n"; let tokens = semantic_tokens(src); assert_eq!(at(&tokens, src, "run").kind, SemKind::Method); assert_eq!(at(&tokens, src, "name").kind, SemKind::Parameter); assert_eq!(at(&tokens, src, "string").kind, SemKind::Type); - assert_eq!(at(&tokens, src, "self").kind, SemKind::Keyword); + // A bare same-node call (ADR-041) colours as a method invocation. assert_eq!(at(&tokens, src, "alloc").kind, SemKind::Method); } diff --git a/crates/pseudoscript-model/tests/conformance.rs b/crates/pseudoscript-model/tests/conformance.rs index 20c631d3..683b2188 100644 --- a/crates/pseudoscript-model/tests/conformance.rs +++ b/crates/pseudoscript-model/tests/conformance.rs @@ -392,7 +392,10 @@ fn render_steps(steps: &[Step], indent: usize, out: &mut String) { let _ = writeln!(out, "{pad}call {target_fqn}.{method}"); } Step::SelfCall { method } => { - let _ = writeln!(out, "{pad}self.{method}"); + let _ = writeln!(out, "{pad}{method}()"); + } + Step::LocalCall { method } => { + let _ = writeln!(out, "{pad}.{method}()"); } Step::Return { marker } if marker.is_empty() => { let _ = writeln!(out, "{pad}return"); diff --git a/crates/pseudoscript-model/tests/features/argument_types.feature b/crates/pseudoscript-model/tests/features/argument_types.feature index ffd8c3b3..6c8316ee 100644 --- a/crates/pseudoscript-model/tests/features/argument_types.feature +++ b/crates/pseudoscript-model/tests/features/argument_types.feature @@ -11,7 +11,7 @@ Feature: Call argument types (LANG.md §5.1) //! example public system S; public container C for S { - run(): void { self.take("x") } + run(): void { take("x") } take(n: number): void { } } """ @@ -24,7 +24,7 @@ Feature: Call argument types (LANG.md §5.1) //! example public system S; public container C for S { - run(): void { self.take(5) } + run(): void { take(5) } take(n: number): void { } } """ @@ -37,7 +37,7 @@ Feature: Call argument types (LANG.md §5.1) public data Item { id: number } public system S; public container C for S { - run(a: Item, b: Item): void { self.take(Item[] from { a, b }) } + run(a: Item, b: Item): void { take(Item[] from { a, b }) } take(xs: Item[]): void { } } """ diff --git a/crates/pseudoscript-model/tests/features/call_arity.feature b/crates/pseudoscript-model/tests/features/call_arity.feature index 255ccd36..bc402a08 100644 --- a/crates/pseudoscript-model/tests/features/call_arity.feature +++ b/crates/pseudoscript-model/tests/features/call_arity.feature @@ -1,6 +1,6 @@ Feature: Call arity matches the callable's parameters (LANG.md §5.1) - A call to a resolvable same-module callable — via `self.` or a node — must + A call to a resolvable same-module callable — a bare same-node call or a node — must pass as many arguments as the callable declares. Cross-module callees are not visible here and are not checked. @@ -10,7 +10,7 @@ Feature: Call arity matches the callable's parameters (LANG.md §5.1) //! example public system S; public container C for S { - run(): void { self.add(1, 2, 3) } + run(): void { add(1, 2, 3) } add(a: number, b: number): number { return a } } """ @@ -38,7 +38,7 @@ Feature: Call arity matches the callable's parameters (LANG.md §5.1) //! example public system S; public container C for S { - run(): number { return self.add(1, 2) } + run(): number { return add(1, 2) } add(a: number, b: number): number { return a } } """ diff --git a/crates/pseudoscript-model/tests/features/conditions.feature b/crates/pseudoscript-model/tests/features/conditions.feature index e3b9b6d8..41c6f908 100644 --- a/crates/pseudoscript-model/tests/features/conditions.feature +++ b/crates/pseudoscript-model/tests/features/conditions.feature @@ -1,7 +1,7 @@ Feature: `if`/`while` conditions are boolean (LANG.md §7) A condition whose type is statically determinable must be `bool`. Accessor and - call conditions (`r.isErr`, `self.ready()`) infer to `Unknown` and are not + call conditions (`r.isErr`, `ready()`) infer to `Unknown` and are not checked. Scenario: A non-bool `if` condition is rejected diff --git a/crates/pseudoscript-model/tests/features/cross_module.feature b/crates/pseudoscript-model/tests/features/cross_module.feature index 0a92c410..465e5cc9 100644 --- a/crates/pseudoscript-model/tests/features/cross_module.feature +++ b/crates/pseudoscript-model/tests/features/cross_module.feature @@ -106,7 +106,7 @@ Feature: Cross-module visibility resolution (LANG.md §8.2, ADR-010) Given the workspace modules: | fqn | source | | a | //! a\npublic data Money { amount: number } | - | b | //! b\npublic system Sys;\npublic container Box for b::Sys {\n total(): a::Money { return self.total() }\n} | + | b | //! b\npublic system Sys;\npublic container Box for b::Sys {\n total(): a::Money { return total() }\n} | When I check the workspace Then the workspace has no errors @@ -114,7 +114,7 @@ Feature: Cross-module visibility resolution (LANG.md §8.2, ADR-010) Given the workspace modules: | fqn | source | | a | //! a\ndata Money { amount: number } | - | b | //! b\npublic system Sys;\npublic container Box for b::Sys {\n total(): a::Money { return self.total() }\n} | + | b | //! b\npublic system Sys;\npublic container Box for b::Sys {\n total(): a::Money { return total() }\n} | When I check the workspace Then the workspace diagnostics include "type `a::Money` is private to its module" @@ -130,6 +130,6 @@ Feature: Cross-module visibility resolution (LANG.md §8.2, ADR-010) Given the workspace modules: | fqn | source | | a | //! a\npublic system Sys; | - | b | //! b\npublic system Other;\npublic container Box for b::Other {\n find(): Option { return self.find() }\n} | + | b | //! b\npublic system Other;\npublic container Box for b::Other {\n find(): Option { return find() }\n} | When I check the workspace Then the workspace diagnostics include "dangling cross-module reference `a::Missing`: target does not resolve" diff --git a/crates/pseudoscript-model/tests/features/graph.feature b/crates/pseudoscript-model/tests/features/graph.feature index bf6fc681..aaf4174d 100644 --- a/crates/pseudoscript-model/tests/features/graph.feature +++ b/crates/pseudoscript-model/tests/features/graph.feature @@ -69,7 +69,7 @@ Feature: Resolved relationship graph (LANG.md §9) public system Shop; public container Web for shop::Shop { public checkout(): void { - a = number from self.price() + a = number from price() cart = shop::Cart from { Web.lookup() } } price(): number { return 0 } @@ -78,7 +78,7 @@ Feature: Resolved relationship graph (LANG.md §9) """ Then the trace of "shop::Web::checkout" is: """ - self.price + price() call shop::Web.lookup """ And there is a "provenance" edge from "shop::Web" to "shop::Cart" @@ -92,7 +92,7 @@ Feature: Resolved relationship graph (LANG.md §9) public container Web for shop::Shop { public each(items: shop::Item[]): void { for (it in items) { - self.handle() + handle() } } handle(): void { } @@ -101,7 +101,7 @@ Feature: Resolved relationship graph (LANG.md §9) Then the trace of "shop::Web::each" is: """ loop (items) - self.handle + handle() """ Scenario: a binary condition renders in the alt frame label (§7.5) @@ -113,7 +113,7 @@ Feature: Resolved relationship graph (LANG.md §9) public container Web for shop::Shop { public guard(x: number): void { if (x > shop::LIMIT && x >= 0) { - self.handle() + handle() } } handle(): void { } @@ -122,5 +122,5 @@ Feature: Resolved relationship graph (LANG.md §9) Then the trace of "shop::Web::guard" is: """ alt (x > shop::LIMIT && x >= 0) - self.handle + handle() """ diff --git a/crates/pseudoscript-model/tests/features/member_calls.feature b/crates/pseudoscript-model/tests/features/member_calls.feature index c64ed8ee..69a59770 100644 --- a/crates/pseudoscript-model/tests/features/member_calls.feature +++ b/crates/pseudoscript-model/tests/features/member_calls.feature @@ -4,7 +4,7 @@ Feature: Method calls resolve to a declared callable (LANG.md §6) receiver resolves to a same-module node or `data` record. A node receiver must name one of its callables; a `data` record has only fields, so any call on a record value is rejected. Receivers whose type is not inferred (call results, - `::` paths) are not checked (ADR-022); `self.` calls are checked separately. + `::` paths) are not checked (ADR-022); bare same-node calls are checked separately. Scenario: Calling an undeclared method on a node receiver is rejected Given the model file: @@ -72,7 +72,7 @@ Feature: Method calls resolve to a declared callable (LANG.md §6) public system S; public container C for S { fetch(): Conv; - run(): void { self.fetch().anything() } + run(): void { fetch().anything() } } """ Then there are no diagnostics diff --git a/crates/pseudoscript-model/tests/features/rules.feature b/crates/pseudoscript-model/tests/features/rules.feature index 5d11a062..5cd7cce6 100644 --- a/crates/pseudoscript-model/tests/features/rules.feature +++ b/crates/pseudoscript-model/tests/features/rules.feature @@ -97,7 +97,7 @@ Feature: Static rules, one scenario each (LANG.md §2.4, §3.5, §4, §5.1, §6, public system Banking; public container Mainframe for Banking { Get(): Result { - r = Result from self.Fetch() + r = Result from Fetch() if (r.isErr) { return Err(r.error) } @@ -116,7 +116,7 @@ Feature: Static rules, one scenario each (LANG.md §2.4, §3.5, §4, §5.1, §6, public system Banking; public container Mainframe for Banking { Run(): void { - self.Make() + Make() } Make(): Info; } @@ -131,8 +131,8 @@ Feature: Static rules, one scenario each (LANG.md §2.4, §3.5, §4, §5.1, §6, public system Banking; public container Mainframe for Banking { Run(): void { - r = Info from self.Make() - r = Info from self.Make() + r = Info from Make() + r = Info from Make() } Make(): Info; } @@ -173,7 +173,7 @@ Feature: Static rules, one scenario each (LANG.md §2.4, §3.5, §4, §5.1, §6, public system Banking; public container Mainframe for Banking { Get(): Result { - r = Result from self.Fetch() + r = Result from Fetch() if (r.isOk) { return Ok(r.error) } diff --git a/crates/pseudoscript-model/tests/features/self_calls.feature b/crates/pseudoscript-model/tests/features/self_calls.feature index ec61196f..5f99741a 100644 --- a/crates/pseudoscript-model/tests/features/self_calls.feature +++ b/crates/pseudoscript-model/tests/features/self_calls.feature @@ -1,27 +1,28 @@ -Feature: `self.` calls resolve to a same-node callable (LANG.md §5.1, ADR-004) +Feature: Bare same-node calls resolve to a same-node callable (LANG.md §5.1, ADR-041) - `self` is the enclosing node; `self.Name(args)` invokes one of its callables. - A `self.` call naming no callable of the enclosing node MUST be rejected. + A same-node callable is invoked by a bare call `Name(args)`. A bare call + naming no callable of the enclosing node MUST be rejected. `self` is no longer + reserved: `self.Name(args)` resolves nothing — `self` is an unknown reference. - Scenario: A self-call to an undeclared callable is rejected + Scenario: A same-node call to an undeclared callable is rejected Given the model file: """ //! example public system S; public container C for S { - run(): void { self.missing() } + run(): void { missing() } } """ - Then the diagnostics include "`self.missing` does not name a callable of `C`" + Then the diagnostics include "`missing` does not name a callable of `C`" And there is exactly 1 diagnostic - Scenario: A self-call to a same-node callable is well-formed + Scenario: A same-node call to a sibling callable is well-formed Given the model file: """ //! example public system S; public container C for S { - run(): void { self.helper() } + run(): void { helper() } helper(): void { } } """ @@ -33,7 +34,19 @@ Feature: `self.` calls resolve to a same-node callable (LANG.md §5.1, ADR-004) //! example public system S; public container C for S { - loopy(): void { self.loopy() } + loopy(): void { loopy() } } """ Then there are no diagnostics + + Scenario: `self` is an ordinary identifier — `self.Name(args)` is unresolved + Given the model file: + """ + //! example + public system S; + public container C for S { + run(): void { helper() } + helper(): void { self.helper() } + } + """ + Then the diagnostics include "unresolved reference `self`" diff --git a/crates/pseudoscript-model/tests/features/suggestions.feature b/crates/pseudoscript-model/tests/features/suggestions.feature index 240fb5f3..c7e4457c 100644 --- a/crates/pseudoscript-model/tests/features/suggestions.feature +++ b/crates/pseudoscript-model/tests/features/suggestions.feature @@ -33,11 +33,11 @@ Feature: "Did you mean" suggestions (Levenshtein) //! example public system S; public container C for S { - run(): void { self.helpr() } + run(): void { helpr() } helper(): void { } } """ - Then the diagnostics include "`self.helpr` does not name a callable of `C`; did you mean `helper`?" + Then the diagnostics include "`helpr` does not name a callable of `C`; did you mean `helper`?" Scenario: A distant typo gets no suggestion Given the model file: diff --git a/crates/pseudoscript-syntax/src/ast.rs b/crates/pseudoscript-syntax/src/ast.rs index b14ce4d6..a935eaa5 100644 --- a/crates/pseudoscript-syntax/src/ast.rs +++ b/crates/pseudoscript-syntax/src/ast.rs @@ -409,7 +409,15 @@ pub enum ExprKind { base: Box, segments: Vec, }, - /// `self` or an FQN (§10 `Ref`). + /// A bare same-node call `Name(args)` — a sibling callable, or the enclosing + /// callable itself for recursion (§5.1, §10 `Call`, ADR-041). + OwnCall { + /// The invoked callable's name; resolves on the enclosing node. + name: Ident, + /// The call arguments. + args: Vec, + }, + /// An FQN (§10 `Ref`). Ref(Ref), /// A string / number / bool literal (ADR-013). Literal(Literal), @@ -545,8 +553,6 @@ pub struct PostfixSeg { /// A reference primary (§10 `Ref`). #[derive(Debug, Clone, PartialEq)] pub enum Ref { - /// `self` — the enclosing node (ADR-004). - SelfNode(Span), /// An identifier or `::`-separated path (a node FQN). Path(Path), } diff --git a/crates/pseudoscript-syntax/src/parser.rs b/crates/pseudoscript-syntax/src/parser.rs index e1610321..8e8894dd 100644 --- a/crates/pseudoscript-syntax/src/parser.rs +++ b/crates/pseudoscript-syntax/src/parser.rs @@ -1027,7 +1027,6 @@ impl Parser { | TokenKind::KwErr | TokenKind::KwSome | TokenKind::KwNone - | TokenKind::KwSelf | TokenKind::Ident | TokenKind::String | TokenKind::Number @@ -1258,13 +1257,6 @@ impl Parser { kind: ExprKind::Literal(lit), } } - Some(TokenKind::KwSelf) => { - let token = self.bump().expect("peeked self"); - Expr { - kind: ExprKind::Ref(Ref::SelfNode(token.span)), - span: token.span, - } - } Some(TokenKind::LParen) => { let open = self.bump().expect("peeked `(`"); let inner = self.parse_expr(); @@ -1282,9 +1274,22 @@ impl Parser { // plain value reference; a following `<` is the less-than // operator (§7.5), left for the precedence cascade. let path = self.parse_path(); - Expr { - span: path.span, - kind: ExprKind::Ref(Ref::Path(path)), + // A single-segment path immediately followed by `(` is a bare + // same-node call `Name(args)` (§5.1, ADR-041); a longer path or + // no `(` is a value reference, any `.method(…)` left to postfix. + if path.segments.len() == 1 && self.at(TokenKind::LParen) { + let start = path.span.start; + let name = path.segments.into_iter().next().expect("len == 1"); + let (args, end) = self.parse_call_args(); + Expr { + span: Span::new(start, end), + kind: ExprKind::OwnCall { name, args }, + } + } else { + Expr { + span: path.span, + kind: ExprKind::Ref(Ref::Path(path)), + } } } _ => { @@ -1405,20 +1410,11 @@ impl Parser { while self.at(TokenKind::Dot) { let dot = self.bump().expect("peeked `.`"); let name = self.expect_ident("member name"); - let mut end = name.span.end; - let call_args = if self.at(TokenKind::LParen) { - self.bump(); - let mut args = Vec::new(); - while !self.is_eof() && !self.at(TokenKind::RParen) { - args.push(self.parse_expr()); - if self.eat(TokenKind::Comma).is_none() { - break; - } - } - end = self.eat(TokenKind::RParen).map_or(end, |t| t.span.end); - Some(args) + let (call_args, end) = if self.at(TokenKind::LParen) { + let (args, end) = self.parse_call_args(); + (Some(args), end) } else { - None + (None, name.span.end) }; segments.push(PostfixSeg { span: Span::new(dot.span.start, end), @@ -1436,6 +1432,24 @@ impl Parser { } } + /// Parses a call argument list `"(" [ Expr { "," Expr } ] ")"`, the current + /// token being the open paren. Returns the arguments and the end position + /// (the open paren's end when the list is unterminated). + fn parse_call_args(&mut self) -> (Vec, u32) { + let open = self.bump().expect("peeked `(`"); + let mut args = Vec::new(); + while !self.is_eof() && !self.at(TokenKind::RParen) { + args.push(self.parse_expr()); + if self.eat(TokenKind::Comma).is_none() { + break; + } + } + let end = self + .eat(TokenKind::RParen) + .map_or(open.span.end, |t| t.span.end); + (args, end) + } + fn parse_literal(&mut self) -> Option { match self.peek_kind()? { TokenKind::String => { @@ -1527,7 +1541,6 @@ impl Parser { | TokenKind::KwFor | TokenKind::KwFrom | TokenKind::KwPublic - | TokenKind::KwSelf | TokenKind::KwReturn | TokenKind::KwOk | TokenKind::KwErr diff --git a/crates/pseudoscript-syntax/src/token.rs b/crates/pseudoscript-syntax/src/token.rs index 35232a60..e798e089 100644 --- a/crates/pseudoscript-syntax/src/token.rs +++ b/crates/pseudoscript-syntax/src/token.rs @@ -22,7 +22,6 @@ pub enum TokenKind { KwFor, KwFrom, KwPublic, - KwSelf, KwReturn, KwOk, KwErr, @@ -106,7 +105,6 @@ impl TokenKind { TokenKind::KwFor => "KW_FOR", TokenKind::KwFrom => "KW_FROM", TokenKind::KwPublic => "KW_PUBLIC", - TokenKind::KwSelf => "KW_SELF", TokenKind::KwReturn => "KW_RETURN", TokenKind::KwOk => "KW_OK", TokenKind::KwErr => "KW_ERR", @@ -164,7 +162,7 @@ impl TokenKind { /// The reserved keyword spellings (§2.3), in declaration order. Every entry /// is recognised by [`TokenKind::keyword`]; a test pins them in sync. - pub const KEYWORDS: [&str; 27] = [ + pub const KEYWORDS: [&str; 26] = [ "system", "container", "component", @@ -174,7 +172,6 @@ impl TokenKind { "for", "from", "public", - "self", "return", "Ok", "Err", @@ -211,7 +208,6 @@ impl TokenKind { "for" => TokenKind::KwFor, "from" => TokenKind::KwFrom, "public" => TokenKind::KwPublic, - "self" => TokenKind::KwSelf, "return" => TokenKind::KwReturn, "Ok" => TokenKind::KwOk, "Err" => TokenKind::KwErr, diff --git a/crates/pseudoscript-syntax/tests/conformance.rs b/crates/pseudoscript-syntax/tests/conformance.rs index c41e1cbc..70cfca1d 100644 --- a/crates/pseudoscript-syntax/tests/conformance.rs +++ b/crates/pseudoscript-syntax/tests/conformance.rs @@ -48,7 +48,7 @@ public container Web for Shop { return Err(line.error) } } - r = Result from self.finalize(cart) + r = Result from finalize(cart) return Ok(r.value) } diff --git a/crates/pseudoscript/tests/eval.rs b/crates/pseudoscript/tests/eval.rs index 049ae2c9..ccfa78cd 100644 --- a/crates/pseudoscript/tests/eval.rs +++ b/crates/pseudoscript/tests/eval.rs @@ -27,7 +27,7 @@ public system S;\n\ public data Money;\n\ \n\ public container C for S {\n\ - run(): void { self.charge(\"free\") }\n\ + run(): void { charge(\"free\") }\n\ charge(amt: Money): void { }\n\ }\n"; let output = pds_eval().write_stdin(src).output().expect("run pds eval"); diff --git a/decisions/041-drop-self-qualifier.md b/decisions/041-drop-self-qualifier.md new file mode 100644 index 00000000..4788a380 --- /dev/null +++ b/decisions/041-drop-self-qualifier.md @@ -0,0 +1,33 @@ +# ADR-041 — Same-node calls are bare; `self.` is dropped + +**Status:** Accepted +**Supersedes:** ADR-004 +**Affects:** LANG.md §2.3, §3.3, §5.1, §8.1, §9.2, §10 + +## Context + +ADR-004 gave a same-node callable the notation `self.Name(args)` and reserved bare `Name(args)` as non-resolving. The qualifier became the only notation for a sibling or recursive call. + +It also became a silent modelling trap. A `self.` call renders as a collapsed self-message: the renderer draws the self-arrow but does not follow the callee's body, so any cross-boundary call inside that body never reaches the sequence diagram. A callable `postMessage` delegating to `sendEvent`, whose body is `Topic.produce(event)`, rendered the self-arrow to `sendEvent` and dropped the Kafka publish entirely — with no diagnostic. The model passed `pds check`; the downstream interaction was invisible (issue #71). + +A direct call (`Node.method(args)`) does not have this problem: the renderer follows a disclosed callee's body and emits its calls (§9.2). The divergence was the `self.`-specific collapse. + +## Decision + +- A same-node callable is invoked by a bare call `Name(args)` — a sibling, or the enclosing callable itself for recursion. The `self.` qualifier is removed. +- A bare name **in call position** (immediately followed by `(`) resolves to a callable on the enclosing node. A bare name not in call position is unchanged: it resolves only to a parameter, a binding, or a `for` binding (§8.1, ADR-030). +- `self` is not reserved (§2.3). It is an ordinary identifier with no special meaning; `self.Name(args)` parses as member access on an undeclared `self` and fails resolution like any unknown name. No `self`-specific diagnostic. +- Sequence rendering: a same-node call renders as a self-message **and** the renderer follows the callable's body inline, emitting its cross-boundary calls, exactly as a direct call to a disclosed callee does. Recursion is stack-guarded — an in-flight callee renders as a single self-message with no expansion (§9.2). A method on a local value or chain intermediate (`x.f()`) stays a leaf self-message; it names no node callable and has no body to follow. + +## Consequences + +- §10: `Ref` drops `self`; a bare call `Name "(" [ Args ] ")"` is added as a primary. +- §5.1: the same-node-call clause is rewritten — bare call, recursion included. +- §8.1: a bare name in call position resolves to a same-node callable; the value-position rule is unchanged. +- §9.2: a same-node call expands its callee's body; the silent drop is gone. +- The checker resolves a bare call to the enclosing node's callables and applies the same arity and argument-type checks as any call (ADR-022, ADR-023); a name matching no callable on the node is reported as unresolved. +- The implementation splits the trace step: a same-node call follows its body, a local-value method stays a leaf — both still render as self-messages. +- The worked model, the bundled samples, the conformance cases, and the authoring skill are migrated to the bare form. +- Rejected alternative: keep `self` reserved to emit a migration diagnostic on the removed `self.` form. The value is transitional only, and it leaves a dead keyword in the grammar — every lexer, highlighter, and completion path carries a `self` case for a token no valid model uses. Pre-1.0, with no published models to migrate, the cost outweighs the guided error; `self` is freed as an ordinary identifier instead. +- Rejected alternative: keep `self.` and only fix the renderer to follow its body (issue #71's fallback). The qualifier carries no information the enclosing scope does not already supply, and its collapsed self-message was the source of the silent drop. A bare call is indistinguishable from any other call in the model, so the renderer follows it under one rule — one call shape, one rendering. Two spellings for one call is the divergence ADR-036 settled against. +- The call-vs-construction ambiguity does not arise: construction is `Type from { … }` (§7.2), never `Name(args)`; a bare name followed by `(` is unambiguously a call. diff --git a/decisions/README.md b/decisions/README.md index 30f48f74..bbebe6a4 100644 --- a/decisions/README.md +++ b/decisions/README.md @@ -22,7 +22,7 @@ Read in full to see why `CtorExpr` became `ResultMarker` and how this reinforces ## [004 — Self/sibling calls via `self.`](004-self-calls.md) -`self` refers to the enclosing node; same-node callables are invoked as `self.Name(args)`. Bare `Name(args)` does not resolve. +`self` refers to the enclosing node; same-node callables are invoked as `self.Name(args)`. Bare `Name(args)` does not resolve. **Superseded by ADR-041** — `self.` is dropped; a same-node call is bare `Name(args)`. Read in full for the sequence-diagram rendering (self-message on the lifeline) and the `self`-only-in-body scoping rule. @@ -178,7 +178,7 @@ Read in full for the `//! Configuration` shadowing bug that motivated it, and th ## [030 — A node, type, or variant reference is always its FQN](030-require-full-qualification.md) -Every reference to a node, type, or union variant MUST be its FQN, including one in the same module; a bare leaf name resolves only to a parameter, a binding, or a `for` binding. `self`, member access, primitives, and `Result`/`Option` stay bare. The checker flags a bare same-module node/type/variant name and is gated to a named module — the path-less single-file check stays lenient. +Every reference to a node, type, or union variant MUST be its FQN, including one in the same module; a bare leaf name resolves only to a parameter, a binding, or a `for` binding. Member access, primitives, and `Result`/`Option` stay bare. The checker flags a bare same-module node/type/variant name and is gated to a named module — the path-less single-file check stays lenient. Read in full for the four-meanings-per-bare-name ambiguity it removes, and the workspace-vs-anonymous gating. @@ -241,3 +241,9 @@ Read in full for the value-namespace rule, the FQN-only resolution, and the reje Every callable MUST declare a return type; `F()` without one is rejected and `void` is the explicit nothing-spelling (`F(): void`, still composable as `Result`). §10's `Callable` production requires `":" Type`; the parser recovers a missing type as `void` with a syntax diagnostic. Mandatory signatures make a call to any resolvable callable a determinable `return` operand, extending the §5.1 type-match clause cross-module without widening ADR-022's inference. Read in full for the Java/C#-precedent rationale and the rejected implicit-void and full-typing alternatives. + +## [041 — Same-node calls are bare; `self.` is dropped](041-drop-self-qualifier.md) + +`self.Name(args)` is removed; a same-node callable is invoked by a bare call `Name(args)`. A bare name in call position resolves to a callable on the enclosing node; `self` is not reserved — it is an ordinary identifier with no special meaning. The renderer follows a same-node call's body and emits its cross-boundary calls, ending the silent-drop trap (issue #71) ADR-004's collapsed self-message caused. **Supersedes ADR-004.** + +Read in full for the call-vs-construction non-ambiguity, the same-node-vs-local-value rendering split, and the rejected fix-renderer-only alternative. diff --git a/model/cli.pds b/model/cli.pds index 46e53411..eb5883f0 100644 --- a/model/cli.pds +++ b/model/cli.pds @@ -55,7 +55,7 @@ public container Cli for context::Pseudoscript { /// Generate the documentation site for the workspace containing `path`, with no /// `--format` override so the manifest `[doc].format` or HTML decides. public runDoc(path: string): Result { - built = Result from cli::DocCmd.run(path, self.noFormatOverride()) + built = Result from cli::DocCmd.run(path, noFormatOverride()) if (built.isErr) { return Err(built.error) } @@ -92,7 +92,7 @@ component Loader for cli::Cli { findRoot(start: string): Result { found = Result from project::Project.findRoot(start) if (found.isErr) { - return Err(self.ioError(found.error)) + return Err(ioError(found.error)) } return Ok(found.value) } @@ -101,15 +101,15 @@ component Loader for cli::Cli { /// resolve its declared dependency modules (§8.3) into the workspace. The /// resolved output directory is `/`, taken from the manifest. load(root: string): Result { - config = Result from self.loadManifest(root) + config = Result from loadManifest(root) if (config.isErr) { return Err(config.error) } - modules = Result from self.loadModules(root) + modules = Result from loadModules(root) if (modules.isErr) { return Err(modules.error) } - dependencies = Result from self.loadDependencies(root) + dependencies = Result from loadDependencies(root) if (dependencies.isErr) { return Err(dependencies.error) } @@ -126,7 +126,7 @@ component Loader for cli::Cli { loadModules(root: string): Result { loaded = Result from project::Project.load(root) if (loaded.isErr) { - return Err(self.ioError(loaded.error)) + return Err(ioError(loaded.error)) } return Ok(loaded.value) } @@ -151,11 +151,11 @@ component Loader for cli::Cli { component InitCmd for cli::Cli { #[manual] public run(path: string, name: string): Result { - exists = Result from self.manifestExists(path) + exists = Result from manifestExists(path) if (exists.isErr) { return Err(exists.error) } - return self.writeFiles(path, name) + return writeFiles(path, name) } /// Whether `/pds.toml` already exists — refuse to clobber a workspace. @@ -190,21 +190,21 @@ component DocCmd for cli::Cli { if (project.isErr) { return Err(project.error) } - format = string from self.resolveFormat(cliFormat, project.value.format) + format = string from resolveFormat(cliFormat, project.value.format) perModule = model::ModuleDiagnostics[] from model::Model.checkWorkspaceModules(project.value.modules, project.value.dependencies) - self.reportModules(perModule) + reportModules(perModule) prepared = doc::DiagnosticInput[] from doc::Doc.prepareDiagnostics(project.value.modules, perModule) graph = Graph from model::Model.graph(project.value.modules) - site = Result from self.renderSite(graph, project.value.config, format, prepared) + site = Result from renderSite(graph, project.value.config, format, prepared) if (site.isErr) { return Err(site.error) } - written = Result from self.write(site.value, project.value.outDir) + written = Result from write(site.value, project.value.outDir) if (written.isErr) { return Err(written.error) } - if (self.isHtml(format)) { - self.copyLogo(root.value, project.value) + if (isHtml(format)) { + copyLogo(root.value, project.value) } return Ok(format) } @@ -223,14 +223,14 @@ component DocCmd for cli::Cli { /// infallible flat-Markdown site. Both receive the prepared diagnostics so /// the health report ships in either format. renderSite(graph: model::Graph, config: doc::DocConfig, format: string, diagnostics: doc::DiagnosticInput[]): Result { - if (self.isHtml(format)) { + if (isHtml(format)) { html = Result from doc::Doc.render(graph, config, diagnostics) if (html.isErr) { - return Err(self.renderError(html.error)) + return Err(renderError(html.error)) } return Ok(html.value) } - return Ok(self.renderMarkdown(graph, config, diagnostics)) + return Ok(renderMarkdown(graph, config, diagnostics)) } /// Whether the resolved format is HTML (the SSR site) rather than Markdown. @@ -256,7 +256,7 @@ component DocCmd for cli::Cli { if (roots.isErr) { return Err(roots.error) } - return self.buildEach(roots.value, cliFormat) + return buildEach(roots.value, cliFormat) } /// Build each discovered workspace in order, collecting failures; errors after @@ -270,14 +270,14 @@ component DocCmd for cli::Cli { /// returns after writing the site. #[manual] public serve(path: string, cliFormat: string, serve: bool, watch: bool, port: number): Result { - built = Result from self.run(path, cliFormat) + built = Result from run(path, cliFormat) if (built.isErr) { return Err(built.error) } - if (!self.isHtml(built.value)) { + if (!isHtml(built.value)) { return Ok } - if (!self.shouldServe(serve, watch)) { + if (!shouldServe(serve, watch)) { return Ok } if (watch) { @@ -310,12 +310,12 @@ component CheckCmd for cli::Cli { public run(path: string): Result { root = Result from cli::Loader.findRoot(path) if (root.isErr) { - if (self.isDirectory(path)) { + if (isDirectory(path)) { return Err(root.error) } - return self.checkRootless(path) + return checkRootless(path) } - return self.checkWorkspace(root.value) + return checkWorkspace(root.value) } /// Whether `path` is a directory — only a rootless file falls back to the @@ -334,8 +334,8 @@ component CheckCmd for cli::Cli { return Err(project.error) } perModule = model::ModuleDiagnostics[] from model::Model.checkWorkspaceModules(project.value.modules, project.value.dependencies) - self.reportModules(perModule) - if (self.anyModuleError(perModule)) { + reportModules(perModule) + if (anyModuleError(perModule)) { err = IoError from { perModule } return Err(err) } @@ -356,12 +356,12 @@ component CheckCmd for cli::Cli { /// The lenient path: a file outside any workspace is checked as one anonymous /// module, lenient about full qualification. checkRootless(path: string): Result { - text = Result from self.readSource(path) + text = Result from readSource(path) if (text.isErr) { return Err(text.error) } diagnostics = syntax::Diagnostic[] from model::Model.check(text.value) - if (self.anyError(diagnostics)) { + if (anyError(diagnostics)) { err = IoError from { diagnostics } return Err(err) } @@ -383,7 +383,7 @@ component CheckCmd for cli::Cli { if (roots.isErr) { return Err(roots.error) } - return self.checkEach(roots.value) + return checkEach(roots.value) } /// Check each discovered workspace in order, collecting failures. @@ -398,7 +398,7 @@ component EvalCmd for cli::Cli { #[manual] public run(text: string): Result { diagnostics = syntax::Diagnostic[] from model::Model.check(text) - if (self.anyError(diagnostics)) { + if (anyError(diagnostics)) { err = IoError from { diagnostics } return Err(err) } @@ -420,9 +420,9 @@ component FmtCmd for cli::Cli { return Err(out.error) } if (write) { - self.overwrite(out.value) + overwrite(out.value) } else { - self.print(out.value) + print(out.value) } return Ok } @@ -439,7 +439,7 @@ component TokensCmd for cli::Cli { #[manual] public run(text: string): Result { stream = string from syntax::Syntax.renderTokens(text) - self.print(stream) + print(stream) return Ok } @@ -486,7 +486,7 @@ component OutlineCmd for cli::Cli { return Err(modules.error) } graph = Graph from model::Model.graph(modules.value) - self.print(self.render(graph)) + print(render(graph)) return Ok } @@ -507,8 +507,8 @@ component OutlineCmd for cli::Cli { component SvgCmd for cli::Cli { #[manual] public run(path: string, symbol: string, view: string, target: string, theme: string): Result { - if (!self.isKnownTheme(theme)) { - return Err(self.unknownTheme(theme)) + if (!isKnownTheme(theme)) { + return Err(unknownTheme(theme)) } root = Result from cli::Loader.findRoot(path) if (root.isErr) { @@ -519,11 +519,11 @@ component SvgCmd for cli::Cli { return Err(modules.error) } graph = Graph from model::Model.graph(modules.value) - scene = Result from self.projectChosen(graph, symbol, view, target) + scene = Result from projectChosen(graph, symbol, view, target) if (scene.isErr) { - return Err(self.emitError(scene.error)) + return Err(emitError(scene.error)) } - self.print(self.renderThemed(scene.value, theme)) + print(renderThemed(scene.value, theme)) return Ok } diff --git a/model/doc.pds b/model/doc.pds index e16d388a..e03107a5 100644 --- a/model/doc.pds +++ b/model/doc.pds @@ -221,23 +221,23 @@ component SiteBuilder for doc::Doc { public render(graph: model::Graph, config: doc::DocConfig, diagnostics: doc::DiagnosticInput[]): Result { doc::Urls.build(graph) modules = string[] from doc::Pages.sortedModules(graph) - index = Result from self.emitPage("index.html", doc::Pages.index(graph, config, modules, diagnostics)) + index = Result from emitPage("index.html", doc::Pages.index(graph, config, modules, diagnostics)) if (index.isErr) { return Err(index.error) } - docPages = Result from self.emitDocPages(config, diagnostics) + docPages = Result from emitDocPages(config, diagnostics) if (docPages.isErr) { return Err(docPages.error) } - modulePages = Result from self.emitModulePages(graph, config, modules, diagnostics) + modulePages = Result from emitModulePages(graph, config, modules, diagnostics) if (modulePages.isErr) { return Err(modulePages.error) } - universe = Result from self.emitPage("universe.html", doc::Pages.universePage(graph, config, modules, diagnostics)) + universe = Result from emitPage("universe.html", doc::Pages.universePage(graph, config, modules, diagnostics)) if (universe.isErr) { return Err(universe.error) } - health = Result from self.emitPage("health.html", doc::Pages.healthPage(graph, config, modules, diagnostics)) + health = Result from emitPage("health.html", doc::Pages.healthPage(graph, config, modules, diagnostics)) if (health.isErr) { return Err(health.error) } @@ -273,13 +273,13 @@ component Pages for doc::Doc { /// The index props: the shared chrome, a card per module, the system-context /// diagram, and the stats strip summarising the model and its findings. index(graph: model::Graph, config: doc::DocConfig, modules: string[], diagnostics: doc::DiagnosticInput[]): doc::PageProps { - docGroups = SidebarDocGroup[] from self.docGroups(config, "") - sidebar = SidebarModule[] from self.sidebar(graph, modules, "") - nav = NavLink[] from self.navLinks(diagnostics, "") - crumbs = Crumb[] from self.crumbsOf("index.html") - cards = ModuleCard[] from self.moduleCards(graph, modules) + docGroups = SidebarDocGroup[] from docGroups(config, "") + sidebar = SidebarModule[] from sidebar(graph, modules, "") + nav = NavLink[] from navLinks(diagnostics, "") + crumbs = Crumb[] from crumbsOf("index.html") + cards = ModuleCard[] from moduleCards(graph, modules) context = Diagram from doc::Diagrams.context(graph) - stats = SiteStats from self.stats(graph, diagnostics) + stats = SiteStats from stats(graph, diagnostics) body = IndexPage from { config, context, cards, stats } return PageProps from { config, docGroups, sidebar, nav, crumbs, body } } @@ -287,13 +287,13 @@ component Pages for doc::Doc { /// The universe page's props: the flat snapshot, the traced flows, and each /// placed node's documentation href — everything the 3D island draws. universePage(graph: model::Graph, config: doc::DocConfig, modules: string[], diagnostics: doc::DiagnosticInput[]): doc::PageProps { - docGroups = SidebarDocGroup[] from self.docGroups(config, "") - sidebar = SidebarModule[] from self.sidebar(graph, modules, "") - nav = NavLink[] from self.navLinks(diagnostics, "") - crumbs = Crumb[] from self.crumbsOf("universe.html") + docGroups = SidebarDocGroup[] from docGroups(config, "") + sidebar = SidebarModule[] from sidebar(graph, modules, "") + nav = NavLink[] from navLinks(diagnostics, "") + crumbs = Crumb[] from crumbsOf("universe.html") snapshot = universe::Snapshot from universe::Universe.snapshot(universe::Universe.fromModel(graph)) flows = universe::FlowDef[] from universe::Universe.flows(graph) - hrefs = NodeHref[] from self.nodeHrefs(snapshot) + hrefs = NodeHref[] from nodeHrefs(snapshot) body = UniversePage from { snapshot, flows, hrefs } return PageProps from { config, docGroups, sidebar, nav, crumbs, body } } @@ -301,10 +301,10 @@ component Pages for doc::Doc { /// The health page's props: every prepared diagnostic attributed to its node, /// sorted by severity, module, then line, with the error/warning counts. healthPage(graph: model::Graph, config: doc::DocConfig, modules: string[], diagnostics: doc::DiagnosticInput[]): doc::PageProps { - docGroups = SidebarDocGroup[] from self.docGroups(config, "") - sidebar = SidebarModule[] from self.sidebar(graph, modules, "") - nav = NavLink[] from self.navLinks(diagnostics, "") - crumbs = Crumb[] from self.crumbsOf("health.html") + docGroups = SidebarDocGroup[] from docGroups(config, "") + sidebar = SidebarModule[] from sidebar(graph, modules, "") + nav = NavLink[] from navLinks(diagnostics, "") + crumbs = Crumb[] from crumbsOf("health.html") body = PageBody from doc::Health.page(graph, diagnostics) return PageProps from { config, docGroups, sidebar, nav, crumbs, body } } @@ -328,10 +328,10 @@ component Pages for doc::Doc { /// directory deep (`docs/.html`), so the `../` depth prefix is used for /// the shared chrome. docPage(config: doc::DocConfig, page: doc::DocPage, diagnostics: doc::DiagnosticInput[]): doc::PageProps { - docGroups = SidebarDocGroup[] from self.docGroups(config, "../") - sidebar = SidebarModule[] from self.emptySidebar() - nav = NavLink[] from self.navLinks(diagnostics, "../") - crumbs = Crumb[] from self.crumbsOf(page.path) + docGroups = SidebarDocGroup[] from docGroups(config, "../") + sidebar = SidebarModule[] from emptySidebar() + nav = NavLink[] from navLinks(diagnostics, "../") + crumbs = Crumb[] from crumbsOf(page.path) html = string from doc::Markdown.render(page.markdown) body = DocBody from { page, html } return PageProps from { config, docGroups, sidebar, nav, crumbs, body } @@ -353,12 +353,12 @@ component Pages for doc::Doc { /// One module page's props: the chrome (prefixed `../` for the page's depth) and /// a section per non-callable node declared in the module, sorted by FQN. modulePage(graph: model::Graph, config: doc::DocConfig, module: string, modules: string[], diagnostics: doc::DiagnosticInput[]): doc::PageProps { - docGroups = SidebarDocGroup[] from self.docGroups(config, "../") - sidebar = SidebarModule[] from self.sidebar(graph, modules, "../") - nav = NavLink[] from self.navLinks(diagnostics, "../") - crumbs = Crumb[] from self.crumbsOf(module) - nodes = model::GraphNode[] from self.moduleNodes(graph, module) - sections = NodeSection[] from self.sections(graph, nodes, diagnostics) + docGroups = SidebarDocGroup[] from docGroups(config, "../") + sidebar = SidebarModule[] from sidebar(graph, modules, "../") + nav = NavLink[] from navLinks(diagnostics, "../") + crumbs = Crumb[] from crumbsOf(module) + nodes = model::GraphNode[] from moduleNodes(graph, module) + sections = NodeSection[] from sections(graph, nodes, diagnostics) body = ModulePage from { module, sections } return PageProps from { config, docGroups, sidebar, nav, crumbs, body } } @@ -430,7 +430,7 @@ component Shell for doc::Doc { /// #app wrap(path: string, props: doc::PageProps, rendered: doc::RenderedPage): string { title = string from doc::Escape.text(props.site.name) - return self.document(path, title, props, rendered) + return document(path, title, props, rendered) } /// Assemble the document string from its escaped title, the rendered @@ -446,10 +446,10 @@ component Relationships for doc::Doc { /// The relationship groups for `node`: parent, inbound, then outbound. Empty when /// the node has no parent and no edges. groups(graph: model::Graph, node: model::GraphNode): doc::RelGroup[] { - parent = RelGroup[] from self.parentGroup(graph, node) - inbound = RelGroup[] from self.inboundGroup(graph, node) - outbound = RelGroup[] from self.outboundGroup(graph, node) - return self.collect(parent, inbound, outbound) + parent = RelGroup[] from parentGroup(graph, node) + inbound = RelGroup[] from inboundGroup(graph, node) + outbound = RelGroup[] from outboundGroup(graph, node) + return collect(parent, inbound, outbound) } /// The parent group: a `for` link for a container/component, an `in` link @@ -470,7 +470,7 @@ component Relationships for doc::Doc { /// A `RelItem` for `fqn`: a resolved href when it names a node, absent (rendering /// plain text) otherwise (LANG.md §9.3). item(fqn: string, kind: model::EdgeKind, arrow: bool, label: string): doc::RelItem { - edgeKind = string from self.edgeKindLabel(kind) + edgeKind = string from edgeKindLabel(kind) href = Result from doc::Urls.hrefTo(fqn) if (href.isErr) { return RelItem from { fqn, edgeKind, arrow, label } @@ -490,9 +490,9 @@ component Scenarios for doc::Doc { /// docs, tags, ordered steps, and its flow figure. Empty when the node has no /// scenarios. cards(graph: model::Graph, node: model::GraphNode): doc::ScenarioCard[] { - scenarios = model::Scenario[] from self.scenariosOf(graph, node) + scenarios = model::Scenario[] from scenariosOf(graph, node) flows = doc::Diagram[] from doc::Diagrams.flows(graph, scenarios) - return self.toCards(scenarios, flows) + return toCards(scenarios, flows) } /// The scenarios in the graph whose target FQN is this node. @@ -514,22 +514,22 @@ component Diagrams for doc::Doc { /// diagram on a `container`, an entity diagram on a `data` (LANG.md §9.4), plus a /// sequence diagram per triggered callable it owns. Other kinds embed nothing. forNode(graph: model::Graph, node: model::GraphNode): doc::Diagram[] { - return self.collect(self.boundary(graph, node), self.sequences(graph, node)) + return collect(boundary(graph, node), sequences(graph, node)) } /// The boundary diagram for a node: a container view for a `system`, a component /// view for a `container`, an entity view for a `data`, none for any other kind. boundary(graph: model::Graph, node: model::GraphNode): doc::Diagram[] { - if (self.isSystem(node)) { - return self.one(self.figure(graph, self.containerView(node.fqn), "Containers", "Container diagram")) + if (isSystem(node)) { + return one(figure(graph, containerView(node.fqn), "Containers", "Container diagram")) } - if (self.isContainer(node)) { - return self.one(self.figure(graph, self.componentView(node.fqn), "Components", "Component diagram")) + if (isContainer(node)) { + return one(figure(graph, componentView(node.fqn), "Components", "Component diagram")) } - if (self.isData(node)) { - return self.one(self.figure(graph, self.dataView(node.fqn), "Entities", "Entity diagram")) + if (isData(node)) { + return one(figure(graph, dataView(node.fqn), "Entities", "Entity diagram")) } - return self.none() + return none() } /// One flow figure per scenario: its feature view (LANG.md §9.5), in source order. @@ -537,7 +537,7 @@ component Diagrams for doc::Doc { /// The context diagram for the index page (persons and systems). context(graph: model::Graph): doc::Diagram { - return self.figure(graph, self.contextView(), "Context", "System context") + return figure(graph, contextView(), "Context", "System context") } /// Project `view` into a `Diagram`: render the scene to SVG on success, or an @@ -548,14 +548,14 @@ component Diagrams for doc::Doc { if (scene.isErr) { return Empty from { caption, eyebrow } } - return self.embed(scene.value, caption, eyebrow) + return embed(scene.value, caption, eyebrow) } /// Wrap a laid-out scene in its figure: every variant — C4, sequence, data, /// feature — renders through the emit face under the adaptive palette and /// ships as an `Svg` figure tagged with its view kind. embed(scene: emit::Scene, caption: string, eyebrow: string): doc::Diagram { - return self.svgFigure(scene, caption, eyebrow) + return svgFigure(scene, caption, eyebrow) } /// Render a scene to adaptive-palette SVG through the emit face and wrap it @@ -612,9 +612,9 @@ component Health for doc::Doc { /// The health page body: every finding attributed and sorted by severity, /// module, then line, with the error and warning counts. page(graph: model::Graph, diagnostics: doc::DiagnosticInput[]): doc::PageBody { - entries = HealthEntry[] from self.attributeAll(graph, diagnostics) - errorCount = number from self.countOf(entries, "error") - warningCount = number from self.countOf(entries, "warning") + entries = HealthEntry[] from attributeAll(graph, diagnostics) + errorCount = number from countOf(entries, "error") + warningCount = number from countOf(entries, "warning") return HealthPage from { entries, errorCount, warningCount } } @@ -638,7 +638,7 @@ component SearchIndex for doc::Doc { /// The index file: every entry sorted by href then FQN, serialised as a JS /// assignment of the entry array. public build(graph: model::Graph, config: doc::DocConfig): doc::SiteFile { - entries = SearchEntry[] from self.entries(graph, config) + entries = SearchEntry[] from entries(graph, config) return SiteFile from { entries } } @@ -658,10 +658,10 @@ component Highlight for doc::Doc { /// Highlight `source` as `lang` into class-based HTML spans: `pds` through /// the syntax lexer's token kinds, anything else escaped verbatim. public code(lang: string, source: string): string { - if (self.isPds(lang)) { - return self.pdsTokens(source) + if (isPds(lang)) { + return pdsTokens(source) } - return self.escaped(source) + return escaped(source) } /// Whether the language word names PseudoScript (`pds`, `pseudoscript`). @@ -690,11 +690,11 @@ component Urls for doc::Doc { /// The module-page-relative `href` to `fqn` (`.html#`), or an error /// when `fqn` resolves to no node. hrefTo(fqn: string): Result { - url = Result from self.get(fqn) + url = Result from get(fqn) if (url.isErr) { return Err(url.error) } - return Ok(self.hrefFromModulePage(url.value)) + return Ok(hrefFromModulePage(url.value)) } /// The page path for a module FQN: `module/.html`. diff --git a/model/dot.pds b/model/dot.pds index 85e53066..b2c6c8cc 100644 --- a/model/dot.pds +++ b/model/dot.pds @@ -131,7 +131,7 @@ component Engine for dot::Dot { placed = NodePos[] from dot::Position.assignCoords(graph, ordered) routed = EdgeRoute[] from dot::Splines.route(graph, ordered, placed) framed = ClusterBox[] from dot::Clusters.boxes(graph, placed) - return self.finish(placed, routed, framed) + return finish(placed, routed, framed) } /// Translate the assembled layout so its minimum corner is the origin and @@ -200,8 +200,8 @@ public component Pipeline for dot::Dot { /// Fold the base state through `passes` in order; the last state's placement /// is the result. run(graph: dot::Graph, passes: dot::Pass[]): dot::Layout { - state = dot::LayoutState from self.base(graph) - folded = dot::LayoutState from self.fold(state, passes) + state = dot::LayoutState from base(graph) + folded = dot::LayoutState from fold(state, passes) return folded.layout } diff --git a/model/emit.pds b/model/emit.pds index a465d59a..4924c6d0 100644 --- a/model/emit.pds +++ b/model/emit.pds @@ -202,21 +202,21 @@ component Projector for emit::Emit { /// `of` node's kind first. public project(graph: model::Graph, view: emit::View): Result { if (view.isContext) { - return Ok(self.projectContext(graph)) + return Ok(projectContext(graph)) } if (view.isContainer) { - return self.projectBoundary(graph, view.of, "system", "container") + return projectBoundary(graph, view.of, "system", "container") } if (view.isComponent) { - return self.projectBoundary(graph, view.of, "container", "component") + return projectBoundary(graph, view.of, "container", "component") } if (view.isData) { - return self.projectData(graph, view.of) + return projectData(graph, view.of) } if (view.isFeature) { - return self.projectFeature(graph, view.of) + return projectFeature(graph, view.of) } - return self.projectSequence(graph, view.entry) + return projectSequence(graph, view.entry) } /// Project the diagram that best explains a single symbol, picking the view @@ -247,7 +247,7 @@ component Projector for emit::Emit { /// The context view: every person and system, with inter-system edges and /// triggers. A cross-container call bubbles to a single system → system edge. projectContext(graph: model::Graph): emit::Scene { - nodes = PlacedNode[] from self.collectKinds(graph, "person", "system") + nodes = PlacedNode[] from collectKinds(graph, "person", "system") edges = RoutedEdge[] from emit::Edges.collect(graph, nodes) return emit::Layout.solveC4(C4Scene from { nodes, edges }) } @@ -269,15 +269,15 @@ component Projector for emit::Emit { /// Edges among the resulting node set are lifted to the contained child. /// Errors when `of` is missing or not of `boundary` kind. projectBoundary(graph: model::Graph, of: string, boundary: string, child: string): Result { - anchor = Result from self.requireKind(graph, of, boundary) + anchor = Result from requireKind(graph, of, boundary) if (anchor.isErr) { return Err(anchor.error) } - outer = Option from self.outerFrameOf(graph, of) - inside = PlacedNode[] from self.childrenOf(graph, of, child, outer) - framed = PlacedNode[] from self.withOuterFrame(graph, inside, outer) - actors = PlacedNode[] from self.externalActors(graph, of, child, inside) - nested = PlacedNode[] from self.nestSiblings(graph, actors, outer) + outer = Option from outerFrameOf(graph, of) + inside = PlacedNode[] from childrenOf(graph, of, child, outer) + framed = PlacedNode[] from withOuterFrame(graph, inside, outer) + actors = PlacedNode[] from externalActors(graph, of, child, inside) + nested = PlacedNode[] from nestSiblings(graph, actors, outer) nodes = PlacedNode[] from { framed, nested } edges = RoutedEdge[] from emit::Edges.collect(graph, nodes) return Ok(emit::Layout.solveC4(C4Scene from { of, nodes, edges })) @@ -289,11 +289,11 @@ component Projector for emit::Emit { /// time, not here. Errors when the entry is not a callable, or has no /// disclosed body to trace. projectSequence(graph: model::Graph, entry: string): Result { - node = Result from self.requireKind(graph, entry, "callable") + node = Result from requireKind(graph, entry, "callable") if (node.isErr) { return Err(node.error) } - disclosed = Option from self.bodyOf(graph, entry) + disclosed = Option from bodyOf(graph, entry) if (disclosed.isNone) { return Ok(emit::Trace.blackBox(graph, entry)) } @@ -303,11 +303,11 @@ component Projector for emit::Emit { /// Looks a node up and asserts its C4 kind, mapping a miss to `UnknownNode` /// and a mismatch to `WrongKind`. Reads the kind off the graph's nodes. requireKind(graph: model::Graph, fqn: string, expected: string): Result { - found = Result from self.kindOf(graph, fqn) + found = Result from kindOf(graph, fqn) if (found.isErr) { return Err(UnknownNode from { fqn }) } - if (self.kindMatches(found.value, expected)) { + if (kindMatches(found.value, expected)) { return Ok(found.value) } return Err(WrongKind from { fqn, expected, found.value }) @@ -370,8 +370,8 @@ component Projector for emit::Emit { component Edges for emit::Emit { /// Collect the in-view routed edges for the nodes already placed in a scene. collect(graph: model::Graph, inView: emit::PlacedNode[]): emit::RoutedEdge[] { - routed = RoutedEdge[] from self.route(graph, inView) - return self.sortDedup(routed) + routed = RoutedEdge[] from route(graph, inView) + return sortDedup(routed) } /// Walk the graph's edges, keeping each whose endpoints both resolve in view. @@ -395,7 +395,8 @@ component Edges for emit::Emit { /// participant on first appearance (LANG.md §9.2, §7). A real trigger's /// initiator leads as the first lifeline with a synthesised inbound call; /// absent a trigger the owner leads. A `call` is a message to the target; a -/// disclosed callee expands inline; a `self.` call is a self-message; a return +/// disclosed callee expands inline; a same-node call is a self-message that +/// expands its disclosed body inline too; a return /// marks `Ok`/`Err`; `if`/`else` nests an `alt` frame, `for`/`while` a `loop` /// frame. component Trace for emit::Emit { @@ -406,16 +407,16 @@ component Trace for emit::Emit { /// land on the initiator. The call stack starts at the entry, guarding /// against re-expanding a callable already in flight. walk(graph: model::Graph, entry: string, body: model::Step[]): emit::Scene { - owner = string from self.ownerOf(graph, entry) - actor = Option from self.triggerActorOf(graph, entry) - stack = string[] from self.entryStack(entry) + owner = string from ownerOf(graph, entry) + actor = Option from triggerActorOf(graph, entry) + stack = string[] from entryStack(entry) if (actor.isNone) { - return self.untriggered(graph, entry, body, owner, stack) + return untriggered(graph, entry, body, owner, stack) } - inbound = SeqItem from self.inboundCall(graph, actor.value, owner, entry) - rest = SeqItem[] from self.traceItems(graph, body, owner, actor.value, stack) - items = SeqItem[] from self.prependItem(inbound, rest) - participants = Lifeline[] from self.participantsOf(graph, actor.value, items) + inbound = SeqItem from inboundCall(graph, actor.value, owner, entry) + rest = SeqItem[] from traceItems(graph, body, owner, actor.value, stack) + items = SeqItem[] from prependItem(inbound, rest) + participants = Lifeline[] from participantsOf(graph, actor.value, items) return SequenceScene from { entry, participants, items } } @@ -425,8 +426,8 @@ component Trace for emit::Emit { /// make sense once a real incoming trigger exists. Inner expanded callees /// still return to their callers. untriggered(graph: model::Graph, entry: string, body: model::Step[], owner: string, stack: string[]): emit::Scene { - items = SeqItem[] from self.traceItems(graph, body, owner, self.noCaller(), stack) - participants = Lifeline[] from self.participantsOf(graph, owner, items) + items = SeqItem[] from traceItems(graph, body, owner, noCaller(), stack) + participants = Lifeline[] from participantsOf(graph, owner, items) return SequenceScene from { entry, participants, items } } @@ -444,13 +445,13 @@ component Trace for emit::Emit { /// mutual recursion), closes with a synthesised return of its declared type, /// so every call has its reply. traceCall(graph: model::Graph, target: string, method: string, active: string, stack: string[]): emit::SeqItem[] { - msg = SeqItem from self.callMessage(graph, active, target, method) - disclosed = Option from self.expandableBody(graph, target, method, stack) + msg = SeqItem from callMessage(graph, active, target, method) + disclosed = Option from expandableBody(graph, target, method, stack) if (disclosed.isNone) { - reply = SeqItem from self.syntheticReturn(graph, target, method, active) + reply = SeqItem from syntheticReturn(graph, target, method, active) return SeqItem[] from { msg, reply } } - inner = SeqItem[] from self.traceItems(graph, disclosed.value, target, active, self.pushCall(stack, target, method)) + inner = SeqItem[] from traceItems(graph, disclosed.value, target, active, pushCall(stack, target, method)) return SeqItem[] from { msg, inner } } @@ -475,12 +476,12 @@ component Trace for emit::Emit { /// exactly one request and its reply — unlike a disclosed trace, which /// suppresses the triggerless caller (§9.2). blackBox(graph: model::Graph, entry: string): emit::Scene { - owner = string from self.ownerOf(graph, entry) - actor = string from self.blackBoxActor(graph, entry) - inbound = SeqItem from self.inboundCall(graph, actor, owner, entry) - reply = SeqItem from self.returnItem(graph, owner, actor, entry) + owner = string from ownerOf(graph, entry) + actor = string from blackBoxActor(graph, entry) + inbound = SeqItem from inboundCall(graph, actor, owner, entry) + reply = SeqItem from returnItem(graph, owner, actor, entry) items = SeqItem[] from { inbound, reply } - participants = Lifeline[] from self.participantsOf(graph, actor, items) + participants = Lifeline[] from participantsOf(graph, actor, items) return SequenceScene from { entry, participants, items } } @@ -510,7 +511,8 @@ component Trace for emit::Emit { prependItem(head: emit::SeqItem, rest: emit::SeqItem[]): emit::SeqItem[]; /// Walk a step list for `active` into sequence items: each cross-node call - /// through `traceCall`, each `self.` call as a self-message, each `return` as + /// through `traceCall`, each same-node call as a self-message whose disclosed + /// body expands inline, each local-value method as a leaf self-message, each `return` as /// a message back to `caller` (suppressed when `caller` is the empty marker), /// and `Alt`/`Loop` frames recursing into their bodies with the same caller /// and stack. @@ -539,7 +541,7 @@ component Layout for emit::Emit { /// Place a C4 scene by the `dot` engine with default tweaks and no pins. layoutC4Default(scene: emit::Scene): emit::C4Layout { - return self.layoutC4(scene, self.defaultTweaks(), self.noPins()) + return layoutC4(scene, defaultTweaks(), noPins()) } /// Place a C4 scene under `tweaks` into a `C4Layout`, with `pins` fixing @@ -549,8 +551,8 @@ component Layout for emit::Emit { /// framed boundary) falls back to the scene's own simple coordinates; the /// engine itself never panics. layoutC4(scene: emit::Scene, tweaks: emit::C4Tweaks, pins: emit::GridPin[]): emit::C4Layout { - g = dot::Graph from self.toDotGraph(scene, tweaks) - return self.readBack(scene, self.place(g, tweaks, pins)) + g = dot::Graph from toDotGraph(scene, tweaks) + return readBack(scene, place(g, tweaks, pins)) } /// Run the placement the tweaks select: the experimental grid placer @@ -558,9 +560,9 @@ component Layout for emit::Emit { /// layered pipeline, folded through the pass list the tweaks select. place(g: dot::Graph, tweaks: emit::C4Tweaks, pins: emit::GridPin[]): dot::Layout { if (tweaks.experimentalGrid) { - return dot::Dot.gridLayout(g, self.gridParams(tweaks), self.dotPins(pins)) + return dot::Dot.gridLayout(g, gridParams(tweaks), dotPins(pins)) } - return dot::Dot.runPipeline(g, self.layoutPasses(tweaks)) + return dot::Dot.runPipeline(g, layoutPasses(tweaks)) } /// The pipeline passes the tweaks select: `ShortenLongEdges` when @@ -590,9 +592,9 @@ component Layout for emit::Emit { /// x-positions in first-appearance order, message rows stacked by evaluation /// order, frames sized to their body. solveSequence(scene: emit::Scene): emit::Scene { - metrics = layout::Metrics from self.sequenceMetrics() - placed = layout::SequenceLayout from layout::Layout.layout(self.toSequenceDiagram(scene), metrics) - return self.fromSequenceLayout(scene, placed) + metrics = layout::Metrics from sequenceMetrics() + placed = layout::SequenceLayout from layout::Layout.layout(toSequenceDiagram(scene), metrics) + return fromSequenceLayout(scene, placed) } /// The sequence engine's default metrics — the one source of truth for the @@ -621,15 +623,15 @@ component SvgRenderer for emit::Emit { /// scene variant. public render(scene: emit::Scene): string { if (scene.isC4Scene) { - return self.renderC4(scene) + return renderC4(scene) } if (scene.isSequenceScene) { - return self.renderSequence(scene) + return renderSequence(scene) } if (scene.isDataScene) { - return self.renderData(scene) + return renderData(scene) } - return self.renderFeature(scene) + return renderFeature(scene) } /// Render any scene to SVG under a chosen colour theme. `light` and `dark` @@ -647,10 +649,10 @@ component SvgRenderer for emit::Emit { /// kind eyebrow, bold title, dimmed wrapped description) and routed edges; /// frames the boundary children when the view has an `of`. renderC4(scene: emit::Scene): string { - if (self.layoutFailed(scene)) { - return self.fallbackSvg(scene) + if (layoutFailed(scene)) { + return fallbackSvg(scene) } - return self.emitSvg(scene) + return emitSvg(scene) } /// Draw the sequence: lifeline-head cards with dropping dashed lifelines, @@ -795,12 +797,12 @@ feature ControlFlowBecomesFrames for emit::Trace { and "a for/while becomes a loop frame over its body" } -/// Body calls become left-to-right messages; self-calls loop on the owner. +/// Body calls become left-to-right messages; same-node calls loop on the owner. feature CallsBecomeMessages for emit::Trace { - given "a callable body with calls to other nodes and self-calls" + given "a callable body with calls to other nodes and same-node calls" when "the body is traced" then "each call to another node is a left-to-right message to that lifeline" - and "each self. call is a self-message on the owner's own lifeline" + and "each same-node call is a self-message that expands its disclosed body on the owner's own lifeline" and "every target is registered as a participant on first appearance" } diff --git a/model/format.pds b/model/format.pds index 2ab8648a..1c5a1d3f 100644 --- a/model/format.pds +++ b/model/format.pds @@ -44,8 +44,8 @@ component Formatter for format::Format { /// tree. public format(text: string): Result { parsed = Parsed from syntax::Syntax.parse(text) - if (self.hasErrors(parsed)) { - errors = string[] from self.collectErrors(parsed) + if (hasErrors(parsed)) { + errors = string[] from collectErrors(parsed) err = FormatError from { errors } return Err(err) } @@ -84,8 +84,8 @@ component Printer for format::Format { /// the `//!` inner docs, a blank separator if items follow, then each item in /// source order. print(ast: syntax::Module): string { - state = PrinterState from self.writeModule(ast) - out = string from self.finish(state) + state = PrinterState from writeModule(ast) + out = string from finish(state) return out } @@ -178,7 +178,8 @@ component Printer for format::Format { /// the segment is a call. writePostfixSeg(state: format::PrinterState, seg: syntax::PostfixSeg): format::PrinterState; - /// Emit a reference primary: the literal `self`, or a path. + /// Emit a reference primary: a path (the only `Reference` form since `self` + /// was dropped, ADR-041; a bare same-node call is the `OwnCall` primary). writeRef(state: format::PrinterState, reference: syntax::Reference): format::PrinterState; /// Emit a literal from its raw source text — strings keep their quotes, diff --git a/model/ide.pds b/model/ide.pds index 9eec708a..3b70b480 100644 --- a/model/ide.pds +++ b/model/ide.pds @@ -183,7 +183,7 @@ public component LanguageSession for ide::IdeSession { /// checked against the dependency externals (§8.3) — what the editor adapter /// marks in the gutter after every edit. public diagnostics(): model::ModuleDiagnostics[] { - return self.checkAgainstExternals(self.heldModules(), self.heldExternals()) + return checkAgainstExternals(heldModules(), heldExternals()) } /// Check every module with the dependency modules bound as externals (§8.3), so /// a `dep::module::Node` reference resolves — the externals-aware counterpart of @@ -262,9 +262,9 @@ public component Docs for ide::IdeSession { /// unchanged — diagnostics never cross the boundary). SSR crosses to the /// host's render callback. render(config: ide::DocConfig): ide::RenderedFile[] { - perModule = model::ModuleDiagnostics[] from model::Model.checkWorkspaceModules(self.heldDocModules(), self.heldExternals()) - prepared = doc::DiagnosticInput[] from doc::Doc.prepareDiagnostics(self.heldDocModules(), perModule) - return self.renderWith(config, prepared) + perModule = model::ModuleDiagnostics[] from model::Model.checkWorkspaceModules(heldDocModules(), heldExternals()) + prepared = doc::DiagnosticInput[] from doc::Doc.prepareDiagnostics(heldDocModules(), perModule) + return renderWith(config, prepared) } /// The held workspace's own modules, the check input. @@ -291,15 +291,15 @@ public component Universe for ide::IdeSession { /// The 3D-view snapshot of the held workspace: structural nodes with /// containment and directed traffic-weighted relationships, no positions. universe(): ide::UniverseSnapshot { - graph = universe::SoftwareGraph from universe::Universe.build(self.heldModules()) - return self.toSnapshot(universe::Universe.snapshot(graph)) + graph = universe::SoftwareGraph from universe::Universe.build(heldModules()) + return toSnapshot(universe::Universe.snapshot(graph)) } /// The entry-point flows of the held workspace, traced and lifted by the /// universe crate — the filaments the ForceGraph animates. Replaces the IDE's /// former client-side sequence-walking, so doc site and IDE share one tracer. universeFlows(): universe::FlowDef[] { - graph = model::Graph from model::Model.graph(self.heldModules()) + graph = model::Graph from model::Model.graph(heldModules()) return universe::Universe.flows(graph) } @@ -346,9 +346,9 @@ public component FsAccessAdapter for ide::WebIde { /// module's FQN from its path, LANG.md §8.1), then resolve externals via the /// session. readDependencies(): model::WorkspaceModule[] { - lock = string from self.readLock() - vendored = ide::VendoredInput[] from self.readVendored() - local = ide::LocalInput[] from self.noLocalSources() + lock = string from readLock() + vendored = ide::VendoredInput[] from readVendored() + local = ide::LocalInput[] from noLocalSources() return ide::Workspace.dependencyModules(lock, vendored, local) } /// The consumer's `pds.lock` text, blank when absent. @@ -375,7 +375,7 @@ public component CodeMirrorAdapter for ide::WebIde { onEdit(fqn: string, text: string): void { ide::IdeSession.setSource(fqn, text) diags = model::ModuleDiagnostics[] from ide::LanguageSession.diagnostics() - self.markProblems(diags) + markProblems(diags) } /// The completion source: ask the session for items at the caret. A `.`/`::` /// boundary opens the popup with no prefix typed. @@ -388,7 +388,7 @@ public component CodeMirrorAdapter for ide::WebIde { if (result.isNone) { return } - self.showTooltip(result.value.contents) + showTooltip(result.value.contents) } /// Mark the problems CodeMirror shows in its gutter. markProblems(diags: model::ModuleDiagnostics[]): void; @@ -414,8 +414,8 @@ component LlmSettingsStore for ide::WebIde { /// Apply a provider preset: its pinned endpoint, wire shape, and default /// model become the active settings in one edit. applyPreset(provider: string): void { - preset = ide::LlmSettings from self.presetFor(provider) - self.save(preset) + preset = ide::LlmSettings from presetFor(provider) + save(preset) } /// The pinned settings for a preset id: `ollama` → the local daemon over /// chat with no key; `openai` → the hosted endpoint over chat, key required; @@ -446,7 +446,7 @@ component FimClient for ide::WebIde { /// Route the prompt by the configured wire shape: a native fill-in-the-middle /// route when the provider has one, else a FIM-shaped chat request. complete(prompt: ide::FimPrompt, settings: ide::LlmSettings): Result { - fim = bool from self.usesNativeFim(settings) + fim = bool from usesNativeFim(settings) if (fim) { return ide::LlmProvider.fimComplete(prompt, settings.model) } @@ -463,12 +463,12 @@ component FimClient for ide::WebIde { /// models, then ask for a one-token completion — the first failure comes /// back classified, with its fix hint, instead of a silent bail. testConnection(settings: ide::LlmSettings): Result { - models = Result from self.listModels(settings) + models = Result from listModels(settings) if (models.isErr) { return Err(models.error) } - probe = ide::FimPrompt from self.probePrompt() - answer = Result from self.complete(probe, settings) + probe = ide::FimPrompt from probePrompt() + answer = Result from complete(probe, settings) if (answer.isErr) { return Err(answer.error) } @@ -496,27 +496,27 @@ component GhostText for ide::WebIde { return } settings = ide::LlmSettings from ide::LlmSettingsStore.current() - prompt = ide::FimPrompt from self.assemble(moduleFqn, offset) + prompt = ide::FimPrompt from assemble(moduleFqn, offset) raw = Result from ide::FimClient.complete(prompt, settings) if (raw.isErr) { ide::LlmSettingsStore.reportFailure(raw.error) return } ide::LlmSettingsStore.clearFailure() - blank = bool from self.isBlank(raw.value) + blank = bool from isBlank(raw.value) if (blank) { ide::LlmSettingsStore.noteDrop("empty") return } ide::LlmSettingsStore.clearDrop() - self.show(raw.value) + show(raw.value) } /// The provider context: the windowed buffer around the caret plus the cached /// grammar primer. assemble(moduleFqn: string, offset: number): ide::FimPrompt { - window = string from self.windowAround(moduleFqn, offset) + window = string from windowAround(moduleFqn, offset) symbols = ide::OutlineNode[] from ide::LanguageSession.outline() - primer = string from self.grammarPrimer() + primer = string from grammarPrimer() return ide::FimPrompt from { window, symbols, primer } } /// The capped prefix/suffix slice of the buffer around the caret. @@ -571,7 +571,7 @@ public component DiagramCanvas for ide::WebIde { public component Bootstrap for ide::WebIde { /// Restore from the hash when it carries a workspace; else show the launcher. start(): void { - restored = bool from self.restoreFromHash() + restored = bool from restoreFromHash() if (!restored) { ide::Launcher.show() } @@ -617,7 +617,7 @@ public component Launcher for ide::WebIde { openFolder(): void { picked = Result from ide::FsAccessAdapter.pickFolder() if (picked.isErr) { - self.showPickError(picked.error) + showPickError(picked.error) return } if (!picked.value) { diff --git a/model/lsp.pds b/model/lsp.pds index c7129891..78c6844c 100644 --- a/model/lsp.pds +++ b/model/lsp.pds @@ -60,14 +60,14 @@ component Server for lsp::Lsp { /// causes it. Driven by the editor (`context::Editor.openDocument`). public onChange(doc: model::WorkspaceModule): void { lsp::Store.change(doc) - self.publishAll() + publishAll() } /// `didClose`: drop the unsaved overlay and re-sync to disk so other files stop /// seeing the closed buffer's edits, then republish. onClose(uri: string): void { lsp::Store.close(uri) - self.publishAll() + publishAll() } /// Recompute diagnostics for the whole workspace and push each module's set to @@ -76,7 +76,7 @@ component Server for lsp::Lsp { payloads = model::ModuleDiagnostics[] from lsp::Store.diagnostics() for (p in payloads) { uri = string from lsp::Store.uriOf(p.fqn) - self.publish(uri, p.diagnostics) + publish(uri, p.diagnostics) } } @@ -103,7 +103,7 @@ component Server for lsp::Lsp { return None } target = lsp::DefTarget from found.value - return Some(self.locate(target)) + return Some(locate(target)) } /// Whole-document formatting edit; none when the buffer is already canonical @@ -119,7 +119,7 @@ component Server for lsp::Lsp { locate(target: lsp::DefTarget): lsp::Occurrence { uri = string from lsp::Store.uriOf(target.fqn) targetText = string from lsp::Store.source(uri) - range = lsp_core::Range from self.rangeIn(targetText, target.span) + range = lsp_core::Range from rangeIn(targetText, target.span) return Occurrence from { uri, range } } @@ -140,9 +140,9 @@ component Store for lsp::Lsp { /// under it; a path with no `pds.toml` on the way to the filesystem root leaves /// the store empty (standalone files are then added as they open). discover(rootUri: string): void { - found = bool from self.findRoot(rootUri) + found = bool from findRoot(rootUri) if (found) { - self.loadRoot() + loadRoot() } } @@ -174,8 +174,8 @@ component Store for lsp::Lsp { /// dependency externals (§8.3): ensure those are loaded, then build the /// resolved workspace from the cached local parses with the externals bound. workspace(): model::Workspace { - external = model::WorkspaceModule[] from self.ensureExternals() - return self.buildWithExternals(self.modules(), external) + external = model::WorkspaceModule[] from ensureExternals() + return buildWithExternals(modules(), external) } /// Ensure the workspace's direct-dependency modules (§8.3) are loaded as @@ -201,8 +201,8 @@ component Store for lsp::Lsp { /// workspace, not a fresh externals-blind re-check, keeps a cross-dependency /// reference resolving rather than falsely diagnosing it. diagnostics(): model::ModuleDiagnostics[] { - ws = model::Workspace from self.workspace() - return self.collectDiagnostics(ws) + ws = model::Workspace from workspace() + return collectDiagnostics(ws) } /// Assemble each module's `ModuleDiagnostics` from its cached parse errors and diff --git a/model/model.pds b/model/model.pds index d1e0c3f3..a02a1242 100644 --- a/model/model.pds +++ b/model/model.pds @@ -276,7 +276,7 @@ public component Resolver for model::Model { /// feature adds nothing to this table — its name lives in a separate /// namespace. public build(ast: syntax::Module): model::SymbolTable { - return self.buildWithPath(ast, "") + return buildWithPath(ast, "") } /// Builds the table with a caller-supplied module FQN — the path the workspace @@ -306,7 +306,7 @@ public component Workspace for model::Model { /// graph-projection path. Resolves each module with its caller-supplied FQN so /// the global index keys on the correct cross-module names. public build(modules: model::WorkspaceModule[]): model::ModuleEntry[] { - return self.buildWithExternals(modules, self.noExternals()) + return buildWithExternals(modules, noExternals()) } /// Builds the table with the workspace's §8.3 dependency modules bound as @@ -315,9 +315,9 @@ public component Workspace for model::Model { /// a private external reports `Private`, not a dangling FQN (§8.3). The checks /// path the CLI, the LSP store, and the IDE all route through. public buildWithExternals(modules: model::WorkspaceModule[], externals: model::WorkspaceModule[]): model::ModuleEntry[] { - local = ModuleEntry[] from self.resolveEach(modules) - external = ModuleEntry[] from self.resolveEach(externals) - return self.index(local, external) + local = ModuleEntry[] from resolveEach(modules) + external = ModuleEntry[] from resolveEach(externals) + return index(local, external) } /// Parse and resolve each module into a `ModuleEntry` — its FQN, its AST, and @@ -343,12 +343,12 @@ public component Workspace for model::Model { /// §8.2 visibility rule: a same-module target resolves regardless of /// visibility; a cross-module target resolves only if it is `public`. public resolveQualified(fromModule: string, fqn: string): model::Resolution { - found = Option from self.lookup(fqn) + found = Option from lookup(fqn) if (found.isNone) { return model::Resolution::Missing } sym = Symbol from found.value - if (self.sameModule(fromModule, fqn)) { + if (sameModule(fromModule, fqn)) { return Public from { sym } } if (sym.isPublic) { @@ -371,7 +371,7 @@ component Checks for model::Model { /// the static-analysis errors. The CLI's `pds check` driver. public check(text: string): syntax::Diagnostic[] { parsed = Parsed from syntax::Syntax.parse(text) - static = syntax::Diagnostic[] from self.analyze(parsed.ast) + static = syntax::Diagnostic[] from analyze(parsed.ast) return syntax::Diagnostic[] from { parsed.diagnostics, static } } @@ -380,7 +380,7 @@ component Checks for model::Model { /// diagnostics; the caller supplies the parse diagnostics. public analyze(ast: syntax::Module): syntax::Diagnostic[] { model = SymbolTable from model::Resolver.build(ast) - return self.run(ast, model) + return run(ast, model) } /// Check a multi-module workspace with its §8.3 dependency modules bound as @@ -390,9 +390,9 @@ component Checks for model::Model { /// parse errors come first because the `ModuleEntry` the workspace pass walks /// keeps only the recovered AST and drops each module's parse diagnostics. public checkWorkspace(modules: model::WorkspaceModule[], externals: model::WorkspaceModule[]): syntax::Diagnostic[] { - parse = syntax::Diagnostic[] from self.parseErrors(modules) + parse = syntax::Diagnostic[] from parseErrors(modules) entries = ModuleEntry[] from model::Workspace.buildWithExternals(modules, externals) - workspace = syntax::Diagnostic[] from self.runWorkspace(entries) + workspace = syntax::Diagnostic[] from runWorkspace(entries) return syntax::Diagnostic[] from { parse, workspace } } @@ -415,10 +415,10 @@ component Checks for model::Model { run(ast: syntax::Module, model: model::SymbolTable): syntax::Diagnostic[] { variants = syntax::Diagnostic[] from model::VariantCollision.check(ast) features = syntax::Diagnostic[] from model::FeatureCheck.check(ast, model) - constants = syntax::Diagnostic[] from self.checkConstantCollisions(ast) + constants = syntax::Diagnostic[] from checkConstantCollisions(ast) reserved = syntax::Diagnostic[] from model::ReservedNames.check(ast) typeRefs = syntax::Diagnostic[] from model::TypeRefs.check(ast, model) - items = syntax::Diagnostic[] from self.checkItems(ast) + items = syntax::Diagnostic[] from checkItems(ast) return syntax::Diagnostic[] from { variants, features, constants, reserved, typeRefs, items } } @@ -430,10 +430,10 @@ component Checks for model::Model { /// macro targeting (§2.4), single-assignment rebinds (§7.1), return coverage /// (ADR-016), `Result`/`Option` accessor narrowing (§6), return-type and `from` /// source/target matching — a `return` or `from` source call whose callee - /// resolves in-module (`self.` or a same-module node) carries the callee's + /// resolves in-module (a same-node call or a same-module node) carries the callee's /// declared return type (§5.1, §7.2 — ADR-035, ADR-040), `for` iterates an array - /// (§7.3 — ADR-014), `self.` calls and bare references resolve (§5.1, §8 — - /// ADR-004), `.field` reads name a declared field (§2.2, §3.4), call arity and + /// (§7.3 — ADR-014), same-node calls and bare references resolve (§5.1, §8 — + /// ADR-041), `.field` reads name a declared field (§2.2, §3.4), call arity and /// argument types match (§5.1 — ADR-022), `.method(args)` calls name a callable /// of the receiver's type (§6 — ADR-022), and `if`/`while` conditions are `bool` /// (§7 — ADR-023). Plus, on each structural declaration, the macro-target check @@ -447,7 +447,7 @@ component Checks for model::Model { /// errors, and the architectural warnings. The graph is projected once from the /// resolved entries and handed to `Architecture`, which never holds source. runWorkspace(entries: model::ModuleEntry[]): syntax::Diagnostic[] { - perModule = syntax::Diagnostic[] from self.runEach(entries) + perModule = syntax::Diagnostic[] from runEach(entries) cross = syntax::Diagnostic[] from model::CrossModule.check(entries) graph = model::Graph from model::Builder.graphOf(entries) arch = syntax::Diagnostic[] from model::Architecture.check(graph) @@ -495,9 +495,9 @@ component Architecture for model::Model { /// bypass (PDS-ARCH-001), cyclic dependency (PDS-ARCH-002), and system-boundary /// bypass (PDS-ARCH-003). public check(graph: model::Graph): syntax::Diagnostic[] { - backdoor = syntax::Diagnostic[] from self.checkBackdoor(graph) - cycles = syntax::Diagnostic[] from self.checkCycles(graph) - boundary = syntax::Diagnostic[] from self.checkSystemBoundary(graph) + backdoor = syntax::Diagnostic[] from checkBackdoor(graph) + cycles = syntax::Diagnostic[] from checkCycles(graph) + boundary = syntax::Diagnostic[] from checkSystemBoundary(graph) return syntax::Diagnostic[] from { backdoor, cycles, boundary } } @@ -567,7 +567,7 @@ public component ResultFlow for model::Model { public component ReturnCoverage for model::Model { /// Whether a block returns on every path: any statement diverges. public blockReturns(body: syntax::Module): bool { - return self.stmtReturns(body) + return stmtReturns(body) } /// Whether one statement guarantees a return: a `return`, or an `if`/`else` @@ -651,7 +651,7 @@ component Builder for model::Model { /// then project. The entry point for callers that hold only source. public graph(modules: model::WorkspaceModule[]): model::Graph { entries = ModuleEntry[] from model::Workspace.build(modules) - return Graph from self.graphOf(entries) + return Graph from graphOf(entries) } /// Projects an already-resolved workspace into the graph: collect every node, @@ -661,7 +661,7 @@ component Builder for model::Model { /// so the architectural lints run without re-resolving. public graphOf(entries: model::ModuleEntry[]): model::Graph { for (e in entries) { - self.collectModule(e) + collectModule(e) } return Graph from { entries } } @@ -719,7 +719,7 @@ public data ResolveHit { clicked: syntax::Span, targetModule: string, targetSpan /// Caret resolution — the engine every cursor feature shares (`lsp_core` hover, /// definition, references, rename; the IDE's symbol actions). Tokenizes the /// active source, finds the identifier under the offset, and resolves it across -/// the workspace and its externals: a node or data FQN, a `self.`/member access, +/// the workspace and its externals: a node or data FQN, a same-node call or member access, /// or a local reference. Token-level, so it survives a partial parse. public component CaretResolve for model::Model { /// Resolve the identifier under `offset` in module `fromFqn`'s source; none diff --git a/model/project.pds b/model/project.pds index 2cbbc595..d6017c9c 100644 --- a/model/project.pds +++ b/model/project.pds @@ -37,11 +37,11 @@ component Loader for project::Project { /// Load every `.pds` module under the workspace root into the `(fqn, source)` /// form the model resolves: find the visible files, then read each one. public load(root: string): Result { - files = Result from self.findModules(root) + files = Result from findModules(root) if (files.isErr) { return Err(files.error) } - modules = Result from self.readEach(root, files.value) + modules = Result from readEach(root, files.value) if (modules.isErr) { return Err(modules.error) } diff --git a/model/site/health.html b/model/site/health.html index 087030e3..6b7534f0 100644 --- a/model/site/health.html +++ b/model/site/health.html @@ -11,7 +11,7 @@ -
PseudoScript
Report

Architecture health

No findings.

Generated by pds doc.
+
PseudoScript
Report

Architecture health

No findings.

Generated by pds doc.
diff --git a/model/site/index.html b/model/site/index.html index 41df25ac..ad35ceb2 100644 --- a/model/site/index.html +++ b/model/site/index.html @@ -11,7 +11,7 @@ -
PseudoScript
Architecture documentation

PseudoScript

A C4 model of the workspace: persons, systems, and their containers and +

PseudoScript
Architecture documentation

PseudoScript

A C4 model of the workspace: persons, systems, and their containers and components, with relationships and sequence flows.

  • 3 systems
  • 15 containers
  • 100 components
  • 24 flows
  • 0 findings
Context System context scroll to zoom · drag to pan
PERSONDeveloperAuthor of architecture models: edits `.pds`files in an IDE and runs the CLI.SYSTEMPseudoscriptThe PseudoScript CLI — loads a `.pds`workspace into one resolved graph and…SYSTEMEditorIDEs that speak the Language Server Protocol(VS Code, Neovim, …). The editor spawns `pd…SYSTEMLlmProviderThe author's configured OpenAI-compatiblecompletion provider — hosted (OpenAI,…PERSONVisitorA prospective user reading the landing page.openDocumentrenderDocsserveLanguagechatCompletefimCompletelistModelsopenIdeshowInstall
cli
14 items
context
3 items
doc
42 items
dot
19 items
emit
42 items
format
4 items
ide
23 items
landing
2 items
layout
21 items
lsp
4 items
lsp_core
8 items
model
52 items
project
2 items
syntax
85 items
universe
10 items
3D Universe
The whole model as an explorable scene
Architecture health
Errors, warnings, and principle lints
Generated by pds doc.
diff --git a/model/site/module/cli.html b/model/site/module/cli.html index c2a1e312..40d583df 100644 --- a/model/site/module/cli.html +++ b/model/site/module/cli.html @@ -11,12 +11,12 @@ -
PseudoScript
Module

cli

data

AddOpts #

public
cli::AddOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDAddOptsurlstringtagstringrevstringbranchstringpathstringnamestring
component

Args #

private
cli::Args

Parses argv with clap-derive into the chosen subcommand and its options, then +

PseudoScript
Module

cli

data

AddOpts #

public
cli::AddOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDAddOptsurlstringtagstringrevstringbranchstringpathstringnamestring
component

Args #

private
cli::Args

Parses argv with clap-derive into the chosen subcommand and its options, then dispatches to the matching command handler.

Parent

Sequence Sequence — dispatch scroll to zoom · drag to pan
SEQUENCEdispatchPERSONcallerCOMPONENTArgsPseudoscript::CliParses argv withclap-derive into the1dispatch(command: cli::Command)↩ return number
component

CheckCmd #

private
cli::CheckCmd

`pds check` — parse and statically check one file, print each diagnostic as `path:line:col: severity: message`, and exit non-zero on any error-severity -diagnostic. Emits no diagram.

Parent

Outbound

Scenarios

CheckExitCode

`pds check` exits non-zero when any error diagnostic is produced.

  • given a .pds source file
  • when the developer runs `pds check` on it
  • then every diagnostic is printed as path:line:col: severity: message
  • but the command exits non-zero if any diagnostic is error-severity
  • and a well-formed model prints nothing and exits zero
Flow Flow — CheckExitCode scroll to zoom · drag to pan
FEATURECheckExitCodefor CheckCmdGIVENa .pds source fileWHENthe developer runs `pds check` on itTHENevery diagnostic is printed aspath:line:col: severity: messageBUTthe command exits non-zero if anydiagnostic is error-severityANDa well-formed model prints nothing andexits zero
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunalt[found.isErr][else]alt[else self.isDirectory()]alt[root.isErr][else]COMPONENTCheckCmdPseudoscript::Cli`pds check` — parseand statically checkCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the projectCONTAINERProjectPseudoscript`crates/pseudoscript-project`.The disk-facingCOMPONENTLoaderPseudoscript::ProjectResolves and reads aworkspace off disk:1findRoot(start: string)2findRoot(start: string)3findRoot(start: string)↩ return Result<string, project::IoError>↩ return Result<string, project::IoError>4ioError↩ Err<cli::IoError>↩ Ok<string>5isDirectory6checkRootless7checkWorkspace
Sequence Sequence — runAll scroll to zoom · drag to pan
SEQUENCErunAllalt[else roots.isErr]COMPONENTCheckCmdPseudoscript::Cli`pds check` — parseand statically checkCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the project1discoverWorkspaces(root: string)↩ return Result<string[], cli::IoError>2checkEach
data

CheckOpts #

public
cli::CheckOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDCheckOptsfilestringallbool
container

Cli #

public
cli::Cli

`crates/pseudoscript` — the binary crate (`pds`). The composition root and +diagnostic. Emits no diagram.

Parent

Outbound

Scenarios

CheckExitCode

`pds check` exits non-zero when any error diagnostic is produced.

  • given a .pds source file
  • when the developer runs `pds check` on it
  • then every diagnostic is printed as path:line:col: severity: message
  • but the command exits non-zero if any diagnostic is error-severity
  • and a well-formed model prints nothing and exits zero
Flow Flow — CheckExitCode scroll to zoom · drag to pan
FEATURECheckExitCodefor CheckCmdGIVENa .pds source fileWHENthe developer runs `pds check` on itTHENevery diagnostic is printed aspath:line:col: severity: messageBUTthe command exits non-zero if anydiagnostic is error-severityANDa well-formed model prints nothing andexits zero
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunalt[found.isErr][else]alt[anyError()][else]alt[text.isErr][else]alt[else isDirectory()]alt[modules.isErr][else]alt[files.isErr][else]alt[loaded.isErr][else]alt[dependencies.isErr][else]alt[modules.isErr][else]alt[config.isErr][else]alt[anyModuleError()][else]alt[project.isErr][else]alt[root.isErr][else]COMPONENTCheckCmdPseudoscript::Cli`pds check` — parseand statically checkCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the projectCONTAINERProjectPseudoscript`crates/pseudoscript-project`.The disk-facingCOMPONENTLoaderPseudoscript::ProjectResolves and reads aworkspace off disk:CONTAINERModelPseudoscript`crates/pseudoscript-model`.AST to one resolvedCOMPONENTChecksPseudoscript::ModelStatic analysis(LANG.md §2.2, §2.3,CONTAINERSyntaxPseudoscript`crates/pseudoscript-syntax`.The foundation crate:COMPONENTParserPseudoscript::SyntaxRecursive-descentparser for the §10COMPONENTLexerPseudoscript::SyntaxHand-written lexer forLANG.md §2. One passCOMPONENTResolverPseudoscript::ModelResolves a module'sdeclarations into aCOMPONENTVariantCollisionPseudoscript::Model§3.5 / ADR-006 — unionvariant collision. ACOMPONENTFeatureCheckPseudoscript::Model§5.2 / §8.1 — featurechecks. A feature'sCOMPONENTReservedNamesPseudoscript::Model§2.3 / ADR-012 —reserved-wordCOMPONENTTypeRefsPseudoscript::Model§3.3 / §8.1 —type-reference1findRoot(start: string)2findRoot(start: string)3findRoot(start: string)↩ return Result<string, project::IoError>↩ return Result<string, project::IoError>4ioError↩ Err<cli::IoError>↩ Ok<string>5isDirectory6checkRootless7readSource↩ Err<cli::IoError>8check(text: string)9check(text: string)10parse(text: string)11parse(text: string)12lex(text: string)↩ return syntax::Lexed13parseModule↩ return syntax::Module14diagnostics↩ return syntax::Parsed↩ return syntax::Parsed15analyze16build(ast: syntax::Module)17buildWithPath↩ return model::SymbolTable↩ return model::SymbolTable18run19check(ast: syntax::Module)↩ return syntax::Diagnostic[]20check(ast: syntax::Module, model: model::SymbolTable)↩ return syntax::Diagnostic[]21checkConstantCollisions22check(ast: syntax::Module)↩ return syntax::Diagnostic[]23check(ast: syntax::Module, model: model::SymbolTable)↩ return syntax::Diagnostic[]24checkItems↩ return syntax::Diagnostic[]↩ return syntax::Diagnostic[]↩ return syntax::Diagnostic[]↩ return syntax::Diagnostic[]25anyError↩ Err<cli::IoError>↩ Ok<void>26checkWorkspace27load(root: string)28loadManifest↩ Err<cli::IoError>29loadModules30load(root: string)31load(root: string)32findModules↩ Err<project::IoError>33readEach↩ Err<project::IoError>↩ Ok<model::WorkspaceModule[]>↩ return Result<model::WorkspaceModule[], project::IoError>34ioError↩ Err<cli::IoError>↩ Ok<model::WorkspaceModule[]>↩ Err<cli::IoError>35loadDependencies↩ Err<cli::IoError>↩ Ok<cli::Workspace>↩ Err<cli::IoError>36checkWorkspaceModules(modules: model::WorkspaceModule[], externals: model::WorkspaceModule[])37checkWorkspaceModules(modules: model::WorkspaceModule[], externals: model::WorkspaceModule[])↩ return model::ModuleDiagnostics[]↩ return model::ModuleDiagnostics[]38reportModules39anyModuleError↩ Err<cli::IoError>↩ Ok<void>
Sequence Sequence — runAll scroll to zoom · drag to pan
SEQUENCErunAllalt[else roots.isErr]COMPONENTCheckCmdPseudoscript::Cli`pds check` — parseand statically checkCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the project1discoverWorkspaces(root: string)↩ return Result<string[], cli::IoError>2checkEach
data

CheckOpts #

public
cli::CheckOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDCheckOptsfilestringallbool
container

Cli #

public
cli::Cli

`crates/pseudoscript` — the binary crate (`pds`). The composition root and only I/O edge; a thin frontend that wires the pure library crates to disk, -HTTP, and a filesystem watcher.

Inbound

Outbound

Scenarios

CompositionRoot

The CLI is the composition root and the only I/O edge.

  • given the library crates that are pure over in-memory modules
  • when the CLI runs any subcommand
  • then all file reading, writing, HTTP serving, and watching happen in the CLI
  • and the pure core receives module text and returns values
  • but I/O and load failures are the only things that fail a command, via the exit code
Flow Flow — CompositionRoot scroll to zoom · drag to pan
FEATURECompositionRootfor CliGIVENthe library crates that are pure overin-memory modulesWHENthe CLI runs any subcommandTHENall file reading, writing, HTTPserving, and watching happen in theCLIANDthe pure core receives module text andreturns valuesBUTI/O and load failures are the onlythings that fail a command, via theexit code
ParseSubcommand

argv is parsed into one subcommand and dispatched.

  • given a command line for one of init, doc, check, eval, fmt, tokens, outline, svg, lsp, lang, skill, add, install, update, remove, list, or upgrade
  • when the CLI starts
  • then clap parses argv into the chosen subcommand and its options
  • and the matching command handler is invoked
  • but an unrecognised command or bad option is rejected with a usage error
Flow Flow — ParseSubcommand scroll to zoom · drag to pan
FEATUREParseSubcommandfor CliGIVENa command line for one of init, doc,check, eval, fmt, tokens, outline,svg, lsp, lang, skill, add, install,update, remove, list, or upgradeWHENthe CLI startsTHENclap parses argv into the chosensubcommand and its optionsANDthe matching command handler isinvokedBUTan unrecognised command or bad optionis rejected with a usage error
Components Component diagram scroll to zoom · drag to pan
PseudoscriptCliCOMPONENTInitCmd`pds init` — bootstrap a workspace: write`pds.toml` and a starter `main.pds`. Refuse…COMPONENTArgsParses argv with clap-derive into the chosensubcommand and its options, then dispatches…COMPONENTLoaderThe CLI loader: resolves the project rootand reads its modules through the `project`…COMPONENTDocCmd`pds doc` — the headline command (ADR-017).Load the workspace, check it per module…COMPONENTCheckCmd`pds check` — parse and statically check onefile, print each diagnostic as…COMPONENTEvalCmd`pds eval` — read a model from stdin andstatically check it, printing each…COMPONENTFmtCmd`pds fmt` — format to canonicalPseudoScript; `--write` overwrites the file…COMPONENTTokensCmd`pds tokens` — print the conformance tokenstream (`KIND@line:col "lexeme"`) to stdout…COMPONENTLspHost`pds lsp` — boot a Tokio runtime and launchthe language server over stdio. Spawned as …COMPONENTReferenceCmd`pds lang` (alias `spec`) and `pds skill` —print the bundled, version-pinned language…COMPONENTOutlineCmd`pds outline` — walk the workspace'smodules, build its graph, and print the…COMPONENTSvgCmd`pds svg` — render a single diagram to aself-contained SVG on stdout. With…COMPONENTDeps`pds add`/`install`/`update`/`remove`/`list`— git workspace dependency management…COMPONENTUpgradeCmd`pds upgrade` — download a release andinstall it over the running binary.COMPONENTHttpServerHTTP host for the generated site(`--serve`/`--watch`): `tiny_http` on…COMPONENTWatcherFilesystem watcher (`--watch`, via`notify`): watches the project root…CONTAINERDoc`crates/pseudoscript-doc`. Turns a resolvedgraph into a Svelte-rendered,…CONTAINERFormat`crates/pseudoscript-format`. The canonicalformatter: parse, then pretty-print the tre…CONTAINERLsp`crates/pseudoscript-lsp`. The stdiolanguage server (tower-lsp): workspace…CONTAINERModel`crates/pseudoscript-model`. AST to oneresolved graph; static checks (resolution,…CONTAINERProject`crates/pseudoscript-project`. Thedisk-facing loader: resolves the workspace…CONTAINERSyntax`crates/pseudoscript-syntax`. The foundationcrate: source text to tokens and a typed…discoverWorkspacesfindRootloadcheckcheckWorkspaceModulesservediscoverWorkspacesfindRootloadwatchprepareDiagnosticsrenderrenderMarkdowncheckWorkspaceModulesgraphcheckformatfindRootloadrunStdiofindRootloadModulesgraphfindRootloadModulesgraphrenderTokensparseparse
data

Command #

public
cli::Command

The subcommand clap parsed from argv, with its resolved options.

Entities Entity diagram scroll to zoom · drag to pan
UNIONCommandInitCmdDocOptsCheckOptsEvalOptsFmtOptsTokensOptsOutlineOptsSvgOptsLspOptsLangOptsSkillOptsAddOptsInstallOptsUpdateOptsRemoveOptsListOptsUpgradeOptsRECORDDocOptspathstringserveboolwatchboolportnumberallboolformatstringRECORDCheckOptsfilestringallboolRECORDFmtOptsfilestringwriteboolRECORDTokensOptsfilestringRECORDOutlineOptspathstringRECORDSvgOptspathstringsymbolstringviewstringtargetstringthemestringRECORDAddOptsurlstringtagstringrevstringbranchstringpathstringnamestringRECORDRemoveOptsnamestringRECORDListOptsrootstringRECORDUpgradeOptsversionstring
component

Deps #

private
cli::Deps

`pds add`/`install`/`update`/`remove`/`list` — git workspace dependency +HTTP, and a filesystem watcher.

Inbound

Outbound

Scenarios

CompositionRoot

The CLI is the composition root and the only I/O edge.

  • given the library crates that are pure over in-memory modules
  • when the CLI runs any subcommand
  • then all file reading, writing, HTTP serving, and watching happen in the CLI
  • and the pure core receives module text and returns values
  • but I/O and load failures are the only things that fail a command, via the exit code
Flow Flow — CompositionRoot scroll to zoom · drag to pan
FEATURECompositionRootfor CliGIVENthe library crates that are pure overin-memory modulesWHENthe CLI runs any subcommandTHENall file reading, writing, HTTPserving, and watching happen in theCLIANDthe pure core receives module text andreturns valuesBUTI/O and load failures are the onlythings that fail a command, via theexit code
ParseSubcommand

argv is parsed into one subcommand and dispatched.

  • given a command line for one of init, doc, check, eval, fmt, tokens, outline, svg, lsp, lang, skill, add, install, update, remove, list, or upgrade
  • when the CLI starts
  • then clap parses argv into the chosen subcommand and its options
  • and the matching command handler is invoked
  • but an unrecognised command or bad option is rejected with a usage error
Flow Flow — ParseSubcommand scroll to zoom · drag to pan
FEATUREParseSubcommandfor CliGIVENa command line for one of init, doc,check, eval, fmt, tokens, outline,svg, lsp, lang, skill, add, install,update, remove, list, or upgradeWHENthe CLI startsTHENclap parses argv into the chosensubcommand and its optionsANDthe matching command handler isinvokedBUTan unrecognised command or bad optionis rejected with a usage error
Components Component diagram scroll to zoom · drag to pan
PseudoscriptCliCOMPONENTInitCmd`pds init` — bootstrap a workspace: write`pds.toml` and a starter `main.pds`. Refuse…COMPONENTArgsParses argv with clap-derive into the chosensubcommand and its options, then dispatches…COMPONENTLoaderThe CLI loader: resolves the project rootand reads its modules through the `project`…COMPONENTDocCmd`pds doc` — the headline command (ADR-017).Load the workspace, check it per module…COMPONENTCheckCmd`pds check` — parse and statically check onefile, print each diagnostic as…COMPONENTEvalCmd`pds eval` — read a model from stdin andstatically check it, printing each…COMPONENTFmtCmd`pds fmt` — format to canonicalPseudoScript; `--write` overwrites the file…COMPONENTTokensCmd`pds tokens` — print the conformance tokenstream (`KIND@line:col "lexeme"`) to stdout…COMPONENTLspHost`pds lsp` — boot a Tokio runtime and launchthe language server over stdio. Spawned as …COMPONENTReferenceCmd`pds lang` (alias `spec`) and `pds skill` —print the bundled, version-pinned language…COMPONENTOutlineCmd`pds outline` — walk the workspace'smodules, build its graph, and print the…COMPONENTSvgCmd`pds svg` — render a single diagram to aself-contained SVG on stdout. With…COMPONENTDeps`pds add`/`install`/`update`/`remove`/`list`— git workspace dependency management…COMPONENTUpgradeCmd`pds upgrade` — download a release andinstall it over the running binary.COMPONENTHttpServerHTTP host for the generated site(`--serve`/`--watch`): `tiny_http` on…COMPONENTWatcherFilesystem watcher (`--watch`, via`notify`): watches the project root…CONTAINERDoc`crates/pseudoscript-doc`. Turns a resolvedgraph into a Svelte-rendered,…CONTAINERFormat`crates/pseudoscript-format`. The canonicalformatter: parse, then pretty-print the tre…CONTAINERLsp`crates/pseudoscript-lsp`. The stdiolanguage server (tower-lsp): workspace…CONTAINERModel`crates/pseudoscript-model`. AST to oneresolved graph; static checks (resolution,…CONTAINERProject`crates/pseudoscript-project`. Thedisk-facing loader: resolves the workspace…CONTAINERSyntax`crates/pseudoscript-syntax`. The foundationcrate: source text to tokens and a typed…discoverWorkspacesfindRootloadcheckcheckWorkspaceModulesservediscoverWorkspacesfindRootloadwatchprepareDiagnosticsrenderrenderMarkdowncheckWorkspaceModulesgraphcheckformatfindRootloadrunStdiofindRootloadModulesgraphfindRootloadModulesgraphrenderTokensparseparse
data

Command #

public
cli::Command

The subcommand clap parsed from argv, with its resolved options.

Entities Entity diagram scroll to zoom · drag to pan
UNIONCommandInitCmdDocOptsCheckOptsEvalOptsFmtOptsTokensOptsOutlineOptsSvgOptsLspOptsLangOptsSkillOptsAddOptsInstallOptsUpdateOptsRemoveOptsListOptsUpgradeOptsRECORDDocOptspathstringserveboolwatchboolportnumberallboolformatstringRECORDCheckOptsfilestringallboolRECORDFmtOptsfilestringwriteboolRECORDTokensOptsfilestringRECORDOutlineOptspathstringRECORDSvgOptspathstringsymbolstringviewstringtargetstringthemestringRECORDAddOptsurlstringtagstringrevstringbranchstringpathstringnamestringRECORDRemoveOptsnamestringRECORDListOptsrootstringRECORDUpgradeOptsversionstring
component

Deps #

private
cli::Deps

`pds add`/`install`/`update`/`remove`/`list` — git workspace dependency management (§8.3). `add` resolves a git URL into `pds.toml` + `pds.lock` and vendors it; `install` restores `pds_modules/` from the lock; `update` re-pins; `remove` drops one; `list` discovers every workspace under a root. @@ -28,34 +28,34 @@ HTML site (every diagram as server SVG, logo copied) or a flat Markdown site (SVG assets, no logo). `--serve` hosts the HTML site over HTTP; `--watch` adds a filesystem watcher and browser live reload; both are skipped for -Markdown output, which only writes.

#headline

Parent

Inbound

Outbound

Scenarios

DocPipeline

`pds doc` runs the headline pipeline end to end.

  • given a project rooted at a pds.toml with one or more .pds modules
  • and no --format override, so the HTML default applies
  • when the developer runs `pds doc`
  • then the workspace is loaded and checked
  • and the resolved graph is built and rendered to the HTML site
  • and every site file is written under the output directory
  • and the configured logo is copied beside the site
Flow Flow — DocPipeline scroll to zoom · drag to pan
FEATUREDocPipelinefor DocCmdGIVENa project rooted at a pds.toml withone or more .pds modulesANDno --format override, so the HTMLdefault appliesWHENthe developer runs `pds doc`THENthe workspace is loaded and checkedANDthe resolved graph is built andrendered to the HTML siteANDevery site file is written under theoutput directoryANDthe configured logo is copied besidethe site
ResolveDocFormat

The output format is resolved by precedence and forks the renderer.

  • given a workspace whose `[doc].format` and the `--format` flag may each set a format
  • when the developer runs `pds doc`
  • then an explicit `--format` wins over the manifest `[doc].format`, else HTML
  • and HTML renders the SSR site and copies the logo
  • but Markdown renders the flat site with its SVG inlined and copies no logo
Flow Flow — ResolveDocFormat scroll to zoom · drag to pan
FEATUREResolveDocFormatfor DocCmdGIVENa workspace whose `[doc].format` andthe `--format` flag may each set aformatWHENthe developer runs `pds doc`THENan explicit `--format` wins over themanifest `[doc].format`, else HTMLANDHTML renders the SSR site and copiesthe logoBUTMarkdown renders the flat site withits SVG inlined and copies no logo
MarkdownSkipsServing

Markdown output writes but never serves or watches.

  • given the resolved format is Markdown
  • when the developer runs `pds doc --serve` or `--watch`
  • then the Markdown site is written to the output directory
  • but serving and watching are skipped, so the command returns after writing
Flow Flow — MarkdownSkipsServing scroll to zoom · drag to pan
FEATUREMarkdownSkipsServingfor DocCmdGIVENthe resolved format is MarkdownWHENthe developer runs `pds doc --serve`or `--watch`THENthe Markdown site is written to theoutput directoryBUTserving and watching are skipped, sothe command returns after writing
CheckIsNonFatal

The workspace check is reported but never aborts generation.

  • given a workspace whose model has warnings or errors
  • when the developer runs `pds doc`
  • then every diagnostic is printed to stderr
  • but generation continues like `cargo doc`, so a partial model still documents
  • and only an I/O or load failure exits non-zero
Flow Flow — CheckIsNonFatal scroll to zoom · drag to pan
FEATURECheckIsNonFatalfor DocCmdGIVENa workspace whose model has warningsor errorsWHENthe developer runs `pds doc`THENevery diagnostic is printed to stderrBUTgeneration continues like `cargo doc`,so a partial model still documentsANDonly an I/O or load failure exitsnon-zero
Sequence Sequence — runAll scroll to zoom · drag to pan
SEQUENCErunAllalt[else roots.isErr]COMPONENTDocCmdPseudoscript::Cli`pds doc` — theheadline commandCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the project1discoverWorkspaces(root: string)↩ return Result<string[], cli::IoError>2buildEach
Sequence Sequence — serve scroll to zoom · drag to pan
SEQUENCEservealt[watch]alt[else !self.shouldServe()]alt[else !self.isHtml()]alt[else built.isErr]COMPONENTDocCmdPseudoscript::Cli`pds doc` — theheadline commandCOMPONENTWatcherPseudoscript::CliFilesystem watcher(`--watch`, viaCOMPONENTHttpServerPseudoscript::CliHTTP host for thegenerated site1run2isHtml3shouldServe4watch(root: string, port: number)↩ return Result<void, cli::IoError>5serve(path: string, port: number, reload: bool)↩ return Result<void, cli::IoError>
data

DocOpts #

public
cli::DocOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDDocOptspathstringserveboolwatchboolportnumberallboolformatstring
component

EvalCmd #

private
cli::EvalCmd

`pds eval` — read a model from stdin and statically check it, printing each +Markdown output, which only writes.

#headline

Parent

Inbound

Outbound

Scenarios

DocPipeline

`pds doc` runs the headline pipeline end to end.

  • given a project rooted at a pds.toml with one or more .pds modules
  • and no --format override, so the HTML default applies
  • when the developer runs `pds doc`
  • then the workspace is loaded and checked
  • and the resolved graph is built and rendered to the HTML site
  • and every site file is written under the output directory
  • and the configured logo is copied beside the site
Flow Flow — DocPipeline scroll to zoom · drag to pan
FEATUREDocPipelinefor DocCmdGIVENa project rooted at a pds.toml withone or more .pds modulesANDno --format override, so the HTMLdefault appliesWHENthe developer runs `pds doc`THENthe workspace is loaded and checkedANDthe resolved graph is built andrendered to the HTML siteANDevery site file is written under theoutput directoryANDthe configured logo is copied besidethe site
ResolveDocFormat

The output format is resolved by precedence and forks the renderer.

  • given a workspace whose `[doc].format` and the `--format` flag may each set a format
  • when the developer runs `pds doc`
  • then an explicit `--format` wins over the manifest `[doc].format`, else HTML
  • and HTML renders the SSR site and copies the logo
  • but Markdown renders the flat site with its SVG inlined and copies no logo
Flow Flow — ResolveDocFormat scroll to zoom · drag to pan
FEATUREResolveDocFormatfor DocCmdGIVENa workspace whose `[doc].format` andthe `--format` flag may each set aformatWHENthe developer runs `pds doc`THENan explicit `--format` wins over themanifest `[doc].format`, else HTMLANDHTML renders the SSR site and copiesthe logoBUTMarkdown renders the flat site withits SVG inlined and copies no logo
MarkdownSkipsServing

Markdown output writes but never serves or watches.

  • given the resolved format is Markdown
  • when the developer runs `pds doc --serve` or `--watch`
  • then the Markdown site is written to the output directory
  • but serving and watching are skipped, so the command returns after writing
Flow Flow — MarkdownSkipsServing scroll to zoom · drag to pan
FEATUREMarkdownSkipsServingfor DocCmdGIVENthe resolved format is MarkdownWHENthe developer runs `pds doc --serve`or `--watch`THENthe Markdown site is written to theoutput directoryBUTserving and watching are skipped, sothe command returns after writing
CheckIsNonFatal

The workspace check is reported but never aborts generation.

  • given a workspace whose model has warnings or errors
  • when the developer runs `pds doc`
  • then every diagnostic is printed to stderr
  • but generation continues like `cargo doc`, so a partial model still documents
  • and only an I/O or load failure exits non-zero
Flow Flow — CheckIsNonFatal scroll to zoom · drag to pan
FEATURECheckIsNonFatalfor DocCmdGIVENa workspace whose model has warningsor errorsWHENthe developer runs `pds doc`THENevery diagnostic is printed to stderrBUTgeneration continues like `cargo doc`,so a partial model still documentsANDonly an I/O or load failure exitsnon-zero
Sequence Sequence — runAll scroll to zoom · drag to pan
SEQUENCErunAllalt[else roots.isErr]COMPONENTDocCmdPseudoscript::Cli`pds doc` — theheadline commandCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the project1discoverWorkspaces(root: string)↩ return Result<string[], cli::IoError>2buildEach
Sequence Sequence — serve scroll to zoom · drag to pan
SEQUENCEservealt[found.isErr][else]alt[modules.isErr][else]alt[files.isErr][else]alt[loaded.isErr][else]alt[dependencies.isErr][else]alt[modules.isErr][else]alt[config.isErr][else]loop[entries]alt[kindMatches()][else]alt[found.isErr][else]alt[anchor.isErr][else]alt[kindMatches()][else]alt[found.isErr][else]alt[anchor.isErr][else]alt[kindMatches()][else]alt[found.isErr][else]alt[actor.isNone][else]alt[disclosed.isNone][else]alt[node.isErr][else]alt[view.isFeature][else]alt[view.isData][else]alt[view.isComponent][else]alt[view.isContainer][else]alt[view.isContext][else]alt[scene.isErr][else]alt[rendered.isErr][else]alt[rendered.isErr][else]alt[rendered.isErr][else]alt[health.isErr][else]alt[universe.isErr][else]alt[modulePages.isErr][else]alt[docPages.isErr][else]alt[index.isErr][else]alt[html.isErr][else]alt[isHtml()][else]alt[isHtml()]alt[written.isErr][else]alt[site.isErr][else]alt[project.isErr][else]alt[root.isErr][else]alt[watch]alt[else !shouldServe()]alt[else !isHtml()]alt[else built.isErr]COMPONENTDocCmdPseudoscript::Cli`pds doc` — theheadline commandCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the projectCONTAINERProjectPseudoscript`crates/pseudoscript-project`.The disk-facingCOMPONENTLoaderPseudoscript::ProjectResolves and reads aworkspace off disk:CONTAINERModelPseudoscript`crates/pseudoscript-model`.AST to one resolvedCOMPONENTChecksPseudoscript::ModelStatic analysis(LANG.md §2.2, §2.3,CONTAINERDocPseudoscript`crates/pseudoscript-doc`.Turns a resolved grap…COMPONENTHealthPseudoscript::DocPositions the host'sdiagnostics in theCOMPONENTBuilderPseudoscript::ModelProjects the parsed,resolved workspaceCOMPONENTWorkspacePseudoscript::ModelThe resolved set ofmodules keyed by FQNCOMPONENTSiteBuilderPseudoscript::DocOrchestrates the wholesite: build the URLCOMPONENTUrlsPseudoscript::DocMaps every node FQN toits page path andCOMPONENTPagesPseudoscript::DocProjects the resolvedgraph into per-pageCOMPONENTDiagramsPseudoscript::DocThe bridge into`emit`: project a vie…CONTAINEREmitPseudoscript`crates/pseudoscript-emit`.Projects a view into …COMPONENTProjectorPseudoscript::EmitProjects a `View` outof the graph (LANG.mdCOMPONENTEdgesPseudoscript::EmitBuilds the routed-edgeset for a C4 sceneCOMPONENTLayoutPseudoscript::EmitAssigns geometry.Projection stamps eac…COMPONENTTracePseudoscript::EmitWalks a callable'sbody trace intoCOMPONENTSsrPseudoscript::DocThe server-side renderboundary: aCOMPONENTShellPseudoscript::DocOwns the documentshell that wraps eachCOMPONENTEscapePseudoscript::DocAll user text — nodenames, `///` docs,CONTAINERUniversePseudoscript`crates/pseudoscript-universe`.Maps the resolved C4COMPONENTAdapterPseudoscript::UniverseAdapts a resolvedmodel into theCOMPONENTFlattenPseudoscript::UniverseFlattens the softwaregraph into theCOMPONENTFlowTracerPseudoscript::UniverseTraces everyentry-point flow: aCOMPONENTSearchIndexPseudoscript::DocBuilds the staticfull-text search inde…COMPONENTAssetsPseudoscript::DocShips the prebuiltpresentation bundles,COMPONENTMarkdownSitePseudoscript::DocThe static Markdownoutput mode: turns th…COMPONENTWatcherPseudoscript::CliFilesystem watcher(`--watch`, viaCOMPONENTHttpServerPseudoscript::CliHTTP host for thegenerated site1run2findRoot(start: string)3findRoot(start: string)4findRoot(start: string)↩ return Result<string, project::IoError>↩ return Result<string, project::IoError>5ioError↩ Err<cli::IoError>↩ Ok<string>↩ Err<cli::IoError>6load(root: string)7loadManifest↩ Err<cli::IoError>8loadModules9load(root: string)10load(root: string)11findModules↩ Err<project::IoError>12readEach↩ Err<project::IoError>↩ Ok<model::WorkspaceModule[]>↩ return Result<model::WorkspaceModule[], project::IoError>13ioError↩ Err<cli::IoError>↩ Ok<model::WorkspaceModule[]>↩ Err<cli::IoError>14loadDependencies↩ Err<cli::IoError>↩ Ok<cli::Workspace>↩ Err<cli::IoError>15resolveFormat16checkWorkspaceModules(modules: model::WorkspaceModule[], externals: model::WorkspaceModule[])17checkWorkspaceModules(modules: model::WorkspaceModule[], externals: model::WorkspaceModule[])↩ return model::ModuleDiagnostics[]↩ return model::ModuleDiagnostics[]18reportModules19prepareDiagnostics(modules: model::WorkspaceModule[], perModule: model::ModuleDiagnostics[])20prepare(modules: model::WorkspaceModule[], perModule: model::ModuleDiagnostics[])↩ return doc::DiagnosticInput[]↩ return doc::DiagnosticInput[]21graph(modules: model::WorkspaceModule[])22graph(modules: model::WorkspaceModule[])23build(modules: model::WorkspaceModule[])24noExternals25buildWithExternals26resolveEach27resolveEach28index↩ return model::ModuleEntry[]↩ return model::ModuleEntry[]29graphOf30collectModule↩ return model::Graph↩ return model::Graph↩ return model::Graph31renderSite32isHtml33render(graph: model::Graph, config: doc::DocConfig, diagnostics: doc::DiagnosticInput[])34render(graph: model::Graph, config: doc::DocConfig, diagnostics: doc::DiagnosticInput[])35build(graph: model::Graph)↩ return36sortedModules(graph: model::Graph)↩ return string[]37index(graph: model::Graph, config: doc::DocConfig, modules: string[], diagnostics: doc::DiagnosticInput[])38docGroups39sidebar40navLinks41crumbsOf42moduleCards43context(graph: model::Graph)44contextView45figure46project(graph: model::Graph, view: emit::View)47project(graph: model::Graph, view: emit::View)48projectContext49collectKinds50collect(graph: model::Graph, inView: emit::PlacedNode[])51route52sortDedup↩ return emit::RoutedEdge[]53solveC4(scene: emit::Scene)↩ return emit::Scene↩ return emit::Scene↩ Ok<emit::Scene>54projectBoundary55requireKind56kindOf↩ Err<emit::EmitError>57kindMatches↩ Ok<string>↩ Err<emit::EmitError>↩ Err<emit::EmitError>58outerFrameOf59childrenOf60withOuterFrame61externalActors62nestSiblings63collect(graph: model::Graph, inView: emit::PlacedNode[])64route65sortDedup↩ return emit::RoutedEdge[]66solveC4(scene: emit::Scene)↩ return emit::Scene↩ Ok<emit::Scene>↩ return Result<emit::Scene, emit::EmitError>67projectBoundary68requireKind69kindOf↩ Err<emit::EmitError>70kindMatches↩ Ok<string>↩ Err<emit::EmitError>↩ Err<emit::EmitError>71outerFrameOf72childrenOf73withOuterFrame74externalActors75nestSiblings76collect(graph: model::Graph, inView: emit::PlacedNode[])77route78sortDedup↩ return emit::RoutedEdge[]79solveC4(scene: emit::Scene)↩ return emit::Scene↩ Ok<emit::Scene>↩ return Result<emit::Scene, emit::EmitError>80projectData↩ return Result<emit::Scene, emit::EmitError>81projectFeature↩ return Result<emit::Scene, emit::EmitError>82projectSequence83requireKind84kindOf↩ Err<emit::EmitError>85kindMatches↩ Ok<string>↩ Err<emit::EmitError>↩ Err<emit::EmitError>86bodyOf87blackBox(graph: model::Graph, entry: string)88ownerOf89blackBoxActor90inboundCall91returnItem92participantsOf↩ return emit::Scene↩ Ok<emit::Scene>93walk(graph: model::Graph, entry: string, body: model::Step[])94ownerOf95triggerActorOf96entryStack97untriggered98noCaller99traceItems100participantsOf↩ return emit::Scene↩ return emit::Scene101inboundCall102traceItems103prependItem104participantsOf↩ return emit::Scene↩ Ok<emit::Scene>↩ return Result<emit::Scene, emit::EmitError>↩ return Result<emit::Scene, emit::EmitError>↩ return doc::Diagram105embed106svgFigure↩ return doc::Diagram↩ return doc::Diagram↩ return doc::Diagram107stats↩ return doc::PageProps108emitPage109renderPage(props: doc::PageProps)↩ return Result<doc::RenderedPage, doc::RenderError>↩ Err<doc::RenderError>110wrap(path: string, props: doc::PageProps, rendered: doc::RenderedPage)111text(text: string)↩ return string112document↩ return string↩ Ok<doc::SiteFile>↩ Err<doc::RenderError>113emitDocPages↩ Err<doc::RenderError>114emitModulePages↩ Err<doc::RenderError>115universePage(graph: model::Graph, config: doc::DocConfig, modules: string[], diagnostics: doc::DiagnosticInput[])116docGroups117sidebar118navLinks119crumbsOf120fromModel(graph: model::Graph)121fromModel(graph: model::Graph)122placeStructural123wireContainment124tallyTraffic↩ return universe::SoftwareGraph↩ return universe::SoftwareGraph125snapshot(graph: universe::SoftwareGraph)126snapshot(graph: universe::SoftwareGraph)127nodesOf128edgesOf↩ return universe::Snapshot↩ return universe::Snapshot129flows(graph: model::Graph)130flows(graph: model::Graph)131entryPoints132traceAll↩ return universe::FlowDef[]↩ return universe::FlowDef[]133nodeHrefs↩ return doc::PageProps134emitPage135renderPage(props: doc::PageProps)↩ return Result<doc::RenderedPage, doc::RenderError>↩ Err<doc::RenderError>136wrap(path: string, props: doc::PageProps, rendered: doc::RenderedPage)137text(text: string)↩ return string138document↩ return string↩ Ok<doc::SiteFile>↩ Err<doc::RenderError>139healthPage(graph: model::Graph, config: doc::DocConfig, modules: string[], diagnostics: doc::DiagnosticInput[])140docGroups141sidebar142navLinks143crumbsOf144page(graph: model::Graph, diagnostics: doc::DiagnosticInput[])145attributeAll146countOf147countOf↩ return doc::PageBody↩ return doc::PageProps148emitPage149renderPage(props: doc::PageProps)↩ return Result<doc::RenderedPage, doc::RenderError>↩ Err<doc::RenderError>150wrap(path: string, props: doc::PageProps, rendered: doc::RenderedPage)151text(text: string)↩ return string152document↩ return string↩ Ok<doc::SiteFile>↩ Err<doc::RenderError>153build(graph: model::Graph, config: doc::DocConfig)154entries↩ return doc::SiteFile155shared()↩ return doc::SiteFile[]↩ Ok<doc::Site>↩ return Result<doc::Site, doc::RenderError>156renderError↩ Err<cli::IoError>↩ Ok<doc::Site>157renderMarkdown158renderMarkdown(graph: model::Graph, config: doc::DocConfig, diagnostics: doc::DiagnosticInput[])159buildAll(graph: model::Graph, config: doc::DocConfig, diagnostics: doc::DiagnosticInput[])↩ return doc::PageProps[]160render(pages: doc::PageProps[], theme: doc::Theme)↩ return doc::Site↩ return doc::Site↩ return doc::Site↩ Ok<doc::Site>↩ Err<cli::IoError>161write↩ Err<cli::IoError>162isHtml163copyLogo↩ Ok<string>164isHtml165shouldServe166watch(root: string, port: number)↩ return Result<void, cli::IoError>167serve(path: string, port: number, reload: bool)↩ return Result<void, cli::IoError>
data

DocOpts #

public
cli::DocOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDDocOptspathstringserveboolwatchboolportnumberallboolformatstring
component

EvalCmd #

private
cli::EvalCmd

`pds eval` — read a model from stdin and statically check it, printing each diagnostic as `<stdin>:line:col: severity: message` and exiting non-zero on any error. The fileless path: an agent pipes a snippet to check it without -writing a file.

Parent

Outbound

Scenarios

EvalExitCode

`pds eval` reads stdin and exits non-zero when any error diagnostic is produced.

  • given a model piped to stdin
  • when the developer or an agent runs `pds eval`
  • then every diagnostic is printed as <stdin>:line:col: severity: message
  • but the command exits non-zero if any diagnostic is error-severity
  • and a well-formed model prints nothing and exits zero
Flow Flow — EvalExitCode scroll to zoom · drag to pan
FEATUREEvalExitCodefor EvalCmdGIVENa model piped to stdinWHENthe developer or an agent runs `pdseval`THENevery diagnostic is printed as<stdin>:line:col: severity: messageBUTthe command exits non-zero if anydiagnostic is error-severityANDa well-formed model prints nothing andexits zero
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunCOMPONENTEvalCmdPseudoscript::Cli`pds eval` — read amodel from stdin andCONTAINERModelPseudoscript`crates/pseudoscript-model`.AST to one resolvedCOMPONENTChecksPseudoscript::ModelStatic analysis(LANG.md §2.2, §2.3,CONTAINERSyntaxPseudoscript`crates/pseudoscript-syntax`.The foundation crate:COMPONENTParserPseudoscript::SyntaxRecursive-descentparser for the §10COMPONENTLexerPseudoscript::SyntaxHand-written lexer forLANG.md §2. One pass1check(text: string)2check(text: string)3parse(text: string)4parse(text: string)5lex(text: string)↩ return syntax::Lexed6parseModule7diagnostics↩ return syntax::Parsed↩ return syntax::Parsed8analyze↩ return syntax::Diagnostic[]↩ return syntax::Diagnostic[]9anyError
component

FmtCmd #

private
cli::FmtCmd

`pds fmt` — format to canonical PseudoScript; `--write` overwrites the file in +writing a file.

Parent

Outbound

Scenarios

EvalExitCode

`pds eval` reads stdin and exits non-zero when any error diagnostic is produced.

  • given a model piped to stdin
  • when the developer or an agent runs `pds eval`
  • then every diagnostic is printed as <stdin>:line:col: severity: message
  • but the command exits non-zero if any diagnostic is error-severity
  • and a well-formed model prints nothing and exits zero
Flow Flow — EvalExitCode scroll to zoom · drag to pan
FEATUREEvalExitCodefor EvalCmdGIVENa model piped to stdinWHENthe developer or an agent runs `pdseval`THENevery diagnostic is printed as<stdin>:line:col: severity: messageBUTthe command exits non-zero if anydiagnostic is error-severityANDa well-formed model prints nothing andexits zero
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunCOMPONENTEvalCmdPseudoscript::Cli`pds eval` — read amodel from stdin andCONTAINERModelPseudoscript`crates/pseudoscript-model`.AST to one resolvedCOMPONENTChecksPseudoscript::ModelStatic analysis(LANG.md §2.2, §2.3,CONTAINERSyntaxPseudoscript`crates/pseudoscript-syntax`.The foundation crate:COMPONENTParserPseudoscript::SyntaxRecursive-descentparser for the §10COMPONENTLexerPseudoscript::SyntaxHand-written lexer forLANG.md §2. One passCOMPONENTResolverPseudoscript::ModelResolves a module'sdeclarations into aCOMPONENTVariantCollisionPseudoscript::Model§3.5 / ADR-006 — unionvariant collision. ACOMPONENTFeatureCheckPseudoscript::Model§5.2 / §8.1 — featurechecks. A feature'sCOMPONENTReservedNamesPseudoscript::Model§2.3 / ADR-012 —reserved-wordCOMPONENTTypeRefsPseudoscript::Model§3.3 / §8.1 —type-reference1check(text: string)2check(text: string)3parse(text: string)4parse(text: string)5lex(text: string)↩ return syntax::Lexed6parseModule↩ return syntax::Module7diagnostics↩ return syntax::Parsed↩ return syntax::Parsed8analyze9build(ast: syntax::Module)10buildWithPath↩ return model::SymbolTable↩ return model::SymbolTable11run12check(ast: syntax::Module)↩ return syntax::Diagnostic[]13check(ast: syntax::Module, model: model::SymbolTable)↩ return syntax::Diagnostic[]14checkConstantCollisions15check(ast: syntax::Module)↩ return syntax::Diagnostic[]16check(ast: syntax::Module, model: model::SymbolTable)↩ return syntax::Diagnostic[]17checkItems↩ return syntax::Diagnostic[]↩ return syntax::Diagnostic[]↩ return syntax::Diagnostic[]↩ return syntax::Diagnostic[]18anyError
component

FmtCmd #

private
cli::FmtCmd

`pds fmt` — format to canonical PseudoScript; `--write` overwrites the file in place, otherwise prints to stdout. A parse error is reported and the file is -left untouched.

Parent

Outbound

Scenarios

FormatInPlaceOrStdout

`pds fmt --write` overwrites in place; otherwise it prints.

  • given a .pds source file
  • when the developer runs `pds fmt`
  • then the source is parsed and pretty-printed to canonical PseudoScript
  • and with `--write` the file is overwritten in place
  • but without `--write` the canonical text is printed to stdout
  • and unparseable source is reported and the file is left untouched
Flow Flow — FormatInPlaceOrStdout scroll to zoom · drag to pan
FEATUREFormatInPlaceOrStdoutfor FmtCmdGIVENa .pds source fileWHENthe developer runs `pds fmt`THENthe source is parsed andpretty-printed to canonicalPseudoScriptANDwith `--write` the file is overwrittenin placeBUTwithout `--write` the canonical textis printed to stdoutANDunparseable source is reported and thefile is left untouched
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunalt[self.hasErrors()][else]alt[write][else]alt[else out.isErr]COMPONENTFmtCmdPseudoscript::Cli`pds fmt` — format tocanonicalCONTAINERFormatPseudoscript`crates/pseudoscript-format`.The canonicalCOMPONENTFormatterPseudoscript::FormatDrives the headlineflow: parse theCONTAINERSyntaxPseudoscript`crates/pseudoscript-syntax`.The foundation crate:COMPONENTParserPseudoscript::SyntaxRecursive-descentparser for the §10COMPONENTLexerPseudoscript::SyntaxHand-written lexer forLANG.md §2. One passCOMPONENTPrinterPseudoscript::FormatThe canonicalpretty-printer: walks1format(text: string)2format(text: string)3parse(text: string)4parse(text: string)5lex(text: string)↩ return syntax::Lexed6parseModule7diagnostics↩ return syntax::Parsed↩ return syntax::Parsed8hasErrors9collectErrors↩ Err<format::FormatError>10print(ast: syntax::Module)11writeModule12finish↩ return string↩ Ok<string>↩ return Result<string, format::FormatError>13overwrite14print
data

FmtOpts #

public
cli::FmtOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDFmtOptsfilestringwritebool
component

HttpServer #

private
cli::HttpServer

HTTP host for the generated site (`--serve`/`--watch`): `tiny_http` on +left untouched.

Parent

Outbound

Scenarios

FormatInPlaceOrStdout

`pds fmt --write` overwrites in place; otherwise it prints.

  • given a .pds source file
  • when the developer runs `pds fmt`
  • then the source is parsed and pretty-printed to canonical PseudoScript
  • and with `--write` the file is overwritten in place
  • but without `--write` the canonical text is printed to stdout
  • and unparseable source is reported and the file is left untouched
Flow Flow — FormatInPlaceOrStdout scroll to zoom · drag to pan
FEATUREFormatInPlaceOrStdoutfor FmtCmdGIVENa .pds source fileWHENthe developer runs `pds fmt`THENthe source is parsed andpretty-printed to canonicalPseudoScriptANDwith `--write` the file is overwrittenin placeBUTwithout `--write` the canonical textis printed to stdoutANDunparseable source is reported and thefile is left untouched
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunalt[hasErrors()][else]alt[write][else]alt[else out.isErr]COMPONENTFmtCmdPseudoscript::Cli`pds fmt` — format tocanonicalCONTAINERFormatPseudoscript`crates/pseudoscript-format`.The canonicalCOMPONENTFormatterPseudoscript::FormatDrives the headlineflow: parse theCONTAINERSyntaxPseudoscript`crates/pseudoscript-syntax`.The foundation crate:COMPONENTParserPseudoscript::SyntaxRecursive-descentparser for the §10COMPONENTLexerPseudoscript::SyntaxHand-written lexer forLANG.md §2. One passCOMPONENTPrinterPseudoscript::FormatThe canonicalpretty-printer: walks1format(text: string)2format(text: string)3parse(text: string)4parse(text: string)5lex(text: string)↩ return syntax::Lexed6parseModule↩ return syntax::Module7diagnostics↩ return syntax::Parsed↩ return syntax::Parsed8hasErrors9collectErrors↩ Err<format::FormatError>10print(ast: syntax::Module)11writeModule12finish↩ return string↩ Ok<string>↩ return Result<string, format::FormatError>13overwrite14print
data

FmtOpts #

public
cli::FmtOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDFmtOptsfilestringwritebool
component

HttpServer #

private
cli::HttpServer

HTTP host for the generated site (`--serve`/`--watch`): `tiny_http` on `127.0.0.1:port`, maps `/` to `index.html`, rejects paths escaping the output dir, and on watch injects a live-reload poll and answers `/__livereload` with the current version. Blocks until the process is stopped.

Parent

Inbound

Scenarios

ServeOverHttp

`pds doc --serve` hosts the generated site over HTTP on localhost.

  • given a generated documentation site
  • when the developer runs `pds doc --serve`
  • then the site is hosted over HTTP on 127.0.0.1 at the chosen port
  • and `/` maps to index.html
  • but a request path that escapes the output directory yields 404
Flow Flow — ServeOverHttp scroll to zoom · drag to pan
FEATUREServeOverHttpfor HttpServerGIVENa generated documentation siteWHENthe developer runs `pds doc --serve`THENthe site is hosted over HTTP on127.0.0.1 at the chosen portAND`/` maps to index.htmlBUTa request path that escapes the outputdirectory yields 404
Sequence Sequence — serve scroll to zoom · drag to pan
SEQUENCEservePERSONclientCOMPONENTHttpServerPseudoscript::CliHTTP host for thegenerated site1serve(path: string, port: number, reload: bool)↩ return Result<void, cli::IoError>
component

InitCmd #

private
cli::InitCmd

`pds init` — bootstrap a workspace: write `pds.toml` and a starter `main.pds`. Refuses to overwrite an existing manifest; the starter is written only when -absent.

Parent

Outbound

  • from cli::Result

Scenarios

InitWorkspace

`pds init` bootstraps a workspace and refuses to clobber an existing one.

  • given a target directory with no pds.toml
  • when the developer runs `pds init`
  • then the directory is created if absent
  • and a pds.toml carrying a [doc] table is written
  • and a starter main.pds is written when none exists
  • but an existing pds.toml aborts the command without overwriting it
Flow Flow — InitWorkspace scroll to zoom · drag to pan
FEATUREInitWorkspacefor InitCmdGIVENa target directory with no pds.tomlWHENthe developer runs `pds init`THENthe directory is created if absentANDa pds.toml carrying a [doc] table iswrittenANDa starter main.pds is written whennone existsBUTan existing pds.toml aborts thecommand without overwriting it
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunalt[else exists.isErr]COMPONENTInitCmdPseudoscript::Cli`pds init` — bootstrapa workspace: write1manifestExists2writeFiles
data

IoError #

public
cli::IoError

A filesystem, parse-config, or serving failure surfaced to the CLI exit code.

Entities Entity diagram scroll to zoom · drag to pan
RECORDIoErrormessagestring
data

ListOpts #

public
cli::ListOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDListOptsrootstring
component

Loader #

private
cli::Loader

The CLI loader: resolves the project root and reads its modules through the +absent.

Parent

Scenarios

InitWorkspace

`pds init` bootstraps a workspace and refuses to clobber an existing one.

  • given a target directory with no pds.toml
  • when the developer runs `pds init`
  • then the directory is created if absent
  • and a pds.toml carrying a [doc] table is written
  • and a starter main.pds is written when none exists
  • but an existing pds.toml aborts the command without overwriting it
Flow Flow — InitWorkspace scroll to zoom · drag to pan
FEATUREInitWorkspacefor InitCmdGIVENa target directory with no pds.tomlWHENthe developer runs `pds init`THENthe directory is created if absentANDa pds.toml carrying a [doc] table iswrittenANDa starter main.pds is written whennone existsBUTan existing pds.toml aborts thecommand without overwriting it
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunalt[else exists.isErr]COMPONENTInitCmdPseudoscript::Cli`pds init` — bootstrapa workspace: write1manifestExists2writeFiles
data

IoError #

public
cli::IoError

A filesystem, parse-config, or serving failure surfaced to the CLI exit code.

Entities Entity diagram scroll to zoom · drag to pan
RECORDIoErrormessagestring
data

ListOpts #

public
cli::ListOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDListOptsrootstring
component

Loader #

private
cli::Loader

The CLI loader: resolves the project root and reads its modules through the `project` crate (the shared filesystem edge), then parses the cli-specific `[doc]` table on top. Root-finding and module-walking are `project`'s job; -the manifest's doc config is the CLI's.

Parent

Inbound

Outbound

  • call project::Project · findRoot
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • call project::Project · load
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result

Scenarios

ResolveProjectRoot

The project root is found by walking up to the nearest pds.toml.

  • given a path somewhere inside a project tree
  • when the CLI loads the workspace
  • then it walks ancestor directories to the nearest pds.toml
  • and the [doc] config and every .pds module beneath it load, sorted by FQN
  • but a tree with no pds.toml is an error
Flow Flow — ResolveProjectRoot scroll to zoom · drag to pan
FEATUREResolveProjectRootfor LoaderGIVENa path somewhere inside a project treeWHENthe CLI loads the workspaceTHENit walks ancestor directories to thenearest pds.tomlANDthe [doc] config and every .pds modulebeneath it load, sorted by FQNBUTa tree with no pds.toml is an error
component

LspHost #

private
cli::LspHost

`pds lsp` — boot a Tokio runtime and launch the language server over stdio. +the manifest's doc config is the CLI's.

Parent

Inbound

Outbound

  • call project::Project · findRoot
  • call project::Project · load
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result
  • from cli::Result

Scenarios

ResolveProjectRoot

The project root is found by walking up to the nearest pds.toml.

  • given a path somewhere inside a project tree
  • when the CLI loads the workspace
  • then it walks ancestor directories to the nearest pds.toml
  • and the [doc] config and every .pds module beneath it load, sorted by FQN
  • but a tree with no pds.toml is an error
Flow Flow — ResolveProjectRoot scroll to zoom · drag to pan
FEATUREResolveProjectRootfor LoaderGIVENa path somewhere inside a project treeWHENthe CLI loads the workspaceTHENit walks ancestor directories to thenearest pds.tomlANDthe [doc] config and every .pds modulebeneath it load, sorted by FQNBUTa tree with no pds.toml is an error
component

LspHost #

private
cli::LspHost

`pds lsp` — boot a Tokio runtime and launch the language server over stdio. Spawned as a child process by an editor (`context::Editor.openDocument`).

Parent

Inbound

Outbound

Scenarios

LaunchLanguageServer

`pds lsp` launches the language server over stdio.

  • given an editor that speaks the Language Server Protocol
  • when the editor spawns `pds lsp` as a child process
  • then a Tokio runtime boots and the server runs over stdio
  • and the editor drives it with document changes and renders its diagnostics
Flow Flow — LaunchLanguageServer scroll to zoom · drag to pan
FEATURELaunchLanguageServerfor LspHostGIVENan editor that speaks the LanguageServer ProtocolWHENthe editor spawns `pds lsp` as a childprocessTHENa Tokio runtime boots and the serverruns over stdioANDthe editor drives it with documentchanges and renders its diagnostics
component

OutlineCmd #

private
cli::OutlineCmd

`pds outline` — walk the workspace's modules, build its graph, and print the symbol outline as JSON: each node's `fqn`, `name`, `kind`, `parent`, whether it is a triggered flow entry, and its declaration site. The structure tree an editor draws. Outlining reads the walked modules straight, skipping the manifest `[doc]` parse and dependency resolution: the structure tree never depends on -presentation config or external modules.

Parent

Outbound

Scenarios

OutlineAsJson

`pds outline` prints the workspace symbol tree as JSON.

  • given a workspace under the path
  • when the developer runs `pds outline`
  • then the workspace's modules are walked and its graph is built
  • and each node's fqn, name, kind, parent, triggered flag, and declaration site is printed as JSON
  • but the manifest [doc] config and dependency modules are not loaded
Flow Flow — OutlineAsJson scroll to zoom · drag to pan
FEATUREOutlineAsJsonfor OutlineCmdGIVENa workspace under the pathWHENthe developer runs `pds outline`THENthe workspace's modules are walked andits graph is builtANDeach node's fqn, name, kind, parent,triggered flag, and declaration siteis printed as JSONBUTthe manifest [doc] config anddependency modules are not loaded
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunalt[found.isErr][else]alt[modules.isErr][else]alt[files.isErr][else]alt[loaded.isErr][else]alt[else modules.isErr]alt[else root.isErr]COMPONENTOutlineCmdPseudoscript::Cli`pds outline` — walkthe workspace'sCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the projectCONTAINERProjectPseudoscript`crates/pseudoscript-project`.The disk-facingCOMPONENTLoaderPseudoscript::ProjectResolves and reads aworkspace off disk:CONTAINERModelPseudoscript`crates/pseudoscript-model`.AST to one resolvedCOMPONENTBuilderPseudoscript::ModelProjects the parsed,resolved workspaceCOMPONENTWorkspacePseudoscript::ModelThe resolved set ofmodules keyed by FQN1findRoot(start: string)2findRoot(start: string)3findRoot(start: string)↩ return Result<string, project::IoError>↩ return Result<string, project::IoError>4ioError↩ Err<cli::IoError>↩ Ok<string>5loadModules(root: string)6load(root: string)7load(root: string)8findModules↩ Err<project::IoError>9readEach↩ Err<project::IoError>↩ Ok<model::WorkspaceModule[]>↩ return Result<model::WorkspaceModule[], project::IoError>10ioError↩ Err<cli::IoError>↩ Ok<model::WorkspaceModule[]>11graph(modules: model::WorkspaceModule[])12graph(modules: model::WorkspaceModule[])13build(modules: model::WorkspaceModule[])14noExternals15buildWithExternals↩ return model::ModuleEntry[]16graphOf↩ return model::Graph↩ return model::Graph17render18print
data

OutlineOpts #

public
cli::OutlineOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDOutlineOptspathstring
component

ReferenceCmd #

private
cli::ReferenceCmd

`pds lang` (alias `spec`) and `pds skill` — print the bundled, version-pinned +presentation config or external modules.

Parent

Outbound

Scenarios

OutlineAsJson

`pds outline` prints the workspace symbol tree as JSON.

  • given a workspace under the path
  • when the developer runs `pds outline`
  • then the workspace's modules are walked and its graph is built
  • and each node's fqn, name, kind, parent, triggered flag, and declaration site is printed as JSON
  • but the manifest [doc] config and dependency modules are not loaded
Flow Flow — OutlineAsJson scroll to zoom · drag to pan
FEATUREOutlineAsJsonfor OutlineCmdGIVENa workspace under the pathWHENthe developer runs `pds outline`THENthe workspace's modules are walked andits graph is builtANDeach node's fqn, name, kind, parent,triggered flag, and declaration siteis printed as JSONBUTthe manifest [doc] config anddependency modules are not loaded
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunalt[found.isErr][else]alt[modules.isErr][else]alt[files.isErr][else]alt[loaded.isErr][else]loop[entries]alt[else modules.isErr]alt[else root.isErr]COMPONENTOutlineCmdPseudoscript::Cli`pds outline` — walkthe workspace'sCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the projectCONTAINERProjectPseudoscript`crates/pseudoscript-project`.The disk-facingCOMPONENTLoaderPseudoscript::ProjectResolves and reads aworkspace off disk:CONTAINERModelPseudoscript`crates/pseudoscript-model`.AST to one resolvedCOMPONENTBuilderPseudoscript::ModelProjects the parsed,resolved workspaceCOMPONENTWorkspacePseudoscript::ModelThe resolved set ofmodules keyed by FQN1findRoot(start: string)2findRoot(start: string)3findRoot(start: string)↩ return Result<string, project::IoError>↩ return Result<string, project::IoError>4ioError↩ Err<cli::IoError>↩ Ok<string>5loadModules(root: string)6load(root: string)7load(root: string)8findModules↩ Err<project::IoError>9readEach↩ Err<project::IoError>↩ Ok<model::WorkspaceModule[]>↩ return Result<model::WorkspaceModule[], project::IoError>10ioError↩ Err<cli::IoError>↩ Ok<model::WorkspaceModule[]>11graph(modules: model::WorkspaceModule[])12graph(modules: model::WorkspaceModule[])13build(modules: model::WorkspaceModule[])14noExternals15buildWithExternals16resolveEach17resolveEach18index↩ return model::ModuleEntry[]↩ return model::ModuleEntry[]19graphOf20collectModule↩ return model::Graph↩ return model::Graph↩ return model::Graph21render22print
data

OutlineOpts #

public
cli::OutlineOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDOutlineOptspathstring
component

ReferenceCmd #

private
cli::ReferenceCmd

`pds lang` (alias `spec`) and `pds skill` — print the bundled, version-pinned language reference and the authoring skill to stdout. Fed to an LLM to author `.pds`; no workspace needed.

Parent

Scenarios

PrintReference

`pds lang`/`pds skill` print the bundled reference and authoring skill.

  • given no workspace is needed
  • when the developer runs `pds lang` or `pds skill`
  • then the version-pinned language reference or the authoring skill is printed to stdout
Flow Flow — PrintReference scroll to zoom · drag to pan
FEATUREPrintReferencefor ReferenceCmdGIVENno workspace is neededWHENthe developer runs `pds lang` or `pdsskill`THENthe version-pinned language referenceor the authoring skill is printed tostdout
Sequence Sequence — lang scroll to zoom · drag to pan
SEQUENCElangPERSONcallerCOMPONENTReferenceCmdPseudoscript::Cli`pds lang` (alias`spec`) and `pds1lang()↩ return
Sequence Sequence — skill scroll to zoom · drag to pan
SEQUENCEskillPERSONcallerCOMPONENTReferenceCmdPseudoscript::Cli`pds lang` (alias`spec`) and `pds1skill()↩ return
data

RemoveOpts #

public
cli::RemoveOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDRemoveOptsnamestring
component

SvgCmd #

private
cli::SvgCmd

`pds svg` — render a single diagram to a self-contained SVG on stdout. With `--symbol`, draws that symbol's fitting view; otherwise draws `--view` (paired with `--target`) over the whole workspace. The static counterpart of the IDE canvas. Like `pds outline`, it reads the walked modules straight, skipping the manifest `[doc]` parse and dependency resolution: a diagram never depends on -presentation config or external modules.

Parent

Outbound

Scenarios

RenderOneSvg

`pds svg` renders one diagram to a self-contained SVG.

  • given a workspace and either a --symbol or a --view/--target selection
  • when the developer runs `pds svg`
  • then the chosen view is projected and rendered to a standalone SVG on stdout
  • but a missing or wrong-kind target fails non-zero with the projection error
Flow Flow — RenderOneSvg scroll to zoom · drag to pan
FEATURERenderOneSvgfor SvgCmdGIVENa workspace and either a --symbol or a--view/--target selectionWHENthe developer runs `pds svg`THENthe chosen view is projected andrendered to a standalone SVG on stdoutBUTa missing or wrong-kind target failsnon-zero with the projection error
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunalt[found.isErr][else]alt[modules.isErr][else]alt[files.isErr][else]alt[loaded.isErr][else]alt[scene.isErr][else]alt[else modules.isErr]alt[else root.isErr]alt[!self.isKnownTheme()][else]COMPONENTSvgCmdPseudoscript::Cli`pds svg` — render asingle diagram to aCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the projectCONTAINERProjectPseudoscript`crates/pseudoscript-project`.The disk-facingCOMPONENTLoaderPseudoscript::ProjectResolves and reads aworkspace off disk:CONTAINERModelPseudoscript`crates/pseudoscript-model`.AST to one resolvedCOMPONENTBuilderPseudoscript::ModelProjects the parsed,resolved workspaceCOMPONENTWorkspacePseudoscript::ModelThe resolved set ofmodules keyed by FQN1isKnownTheme2unknownTheme3findRoot(start: string)4findRoot(start: string)5findRoot(start: string)↩ return Result<string, project::IoError>↩ return Result<string, project::IoError>6ioError↩ Err<cli::IoError>↩ Ok<string>7loadModules(root: string)8load(root: string)9load(root: string)10findModules↩ Err<project::IoError>11readEach↩ Err<project::IoError>↩ Ok<model::WorkspaceModule[]>↩ return Result<model::WorkspaceModule[], project::IoError>12ioError↩ Err<cli::IoError>↩ Ok<model::WorkspaceModule[]>13graph(modules: model::WorkspaceModule[])14graph(modules: model::WorkspaceModule[])15build(modules: model::WorkspaceModule[])16noExternals17buildWithExternals↩ return model::ModuleEntry[]18graphOf↩ return model::Graph↩ return model::Graph19projectChosen20emitError21renderThemed22print
data

SvgOpts #

public
cli::SvgOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDSvgOptspathstringsymbolstringviewstringtargetstringthemestring
component

TokensCmd #

private
cli::TokensCmd

`pds tokens` — print the conformance token stream (`KIND@line:col "lexeme"`) -to stdout, for debugging the lexer.

Parent

Outbound

Scenarios

PrintTokenStream

`pds tokens` prints the conformance token stream.

  • given a .pds source file
  • when the developer runs `pds tokens`
  • then the lexical token stream is printed as KIND@line:col \"lexeme\"
Flow Flow — PrintTokenStream scroll to zoom · drag to pan
FEATUREPrintTokenStreamfor TokensCmdGIVENa .pds source fileWHENthe developer runs `pds tokens`THENthe lexical token stream is printed asKIND@line:col \"lexeme\"
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunCOMPONENTTokensCmdPseudoscript::Cli`pds tokens` — printthe conformance tokenCONTAINERSyntaxPseudoscript`crates/pseudoscript-syntax`.The foundation crate:COMPONENTLexerPseudoscript::SyntaxHand-written lexer forLANG.md §2. One passCOMPONENTLineIndexPseudoscript::SyntaxPrecomputed newlineoffsets turning a byt…1renderTokens(text: string)2renderTokens(text: string)3tokenize4build(src: string)↩ return syntax::LineIndex5renderStream↩ return string↩ return string6print
data

TokensOpts #

public
cli::TokensOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDTokensOptsfilestring
component

UpgradeCmd #

private
cli::UpgradeCmd

`pds upgrade` — download a release and install it over the running binary.

Parent

Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunPERSONcallerCOMPONENTUpgradeCmdPseudoscript::Cli`pds upgrade` —download a release an…1run(version: string)↩ return Result<void, cli::IoError>
data

UpgradeOpts #

public
cli::UpgradeOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDUpgradeOptsversionstring
component

Watcher #

private
cli::Watcher

Filesystem watcher (`--watch`, via `notify`): watches the project root +presentation config or external modules.

Parent

Outbound

Scenarios

RenderOneSvg

`pds svg` renders one diagram to a self-contained SVG.

  • given a workspace and either a --symbol or a --view/--target selection
  • when the developer runs `pds svg`
  • then the chosen view is projected and rendered to a standalone SVG on stdout
  • but a missing or wrong-kind target fails non-zero with the projection error
Flow Flow — RenderOneSvg scroll to zoom · drag to pan
FEATURERenderOneSvgfor SvgCmdGIVENa workspace and either a --symbol or a--view/--target selectionWHENthe developer runs `pds svg`THENthe chosen view is projected andrendered to a standalone SVG on stdoutBUTa missing or wrong-kind target failsnon-zero with the projection error
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunalt[found.isErr][else]alt[modules.isErr][else]alt[files.isErr][else]alt[loaded.isErr][else]loop[entries]alt[scene.isErr][else]alt[else modules.isErr]alt[else root.isErr]alt[!isKnownTheme()][else]COMPONENTSvgCmdPseudoscript::Cli`pds svg` — render asingle diagram to aCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the projectCONTAINERProjectPseudoscript`crates/pseudoscript-project`.The disk-facingCOMPONENTLoaderPseudoscript::ProjectResolves and reads aworkspace off disk:CONTAINERModelPseudoscript`crates/pseudoscript-model`.AST to one resolvedCOMPONENTBuilderPseudoscript::ModelProjects the parsed,resolved workspaceCOMPONENTWorkspacePseudoscript::ModelThe resolved set ofmodules keyed by FQN1isKnownTheme2unknownTheme3findRoot(start: string)4findRoot(start: string)5findRoot(start: string)↩ return Result<string, project::IoError>↩ return Result<string, project::IoError>6ioError↩ Err<cli::IoError>↩ Ok<string>7loadModules(root: string)8load(root: string)9load(root: string)10findModules↩ Err<project::IoError>11readEach↩ Err<project::IoError>↩ Ok<model::WorkspaceModule[]>↩ return Result<model::WorkspaceModule[], project::IoError>12ioError↩ Err<cli::IoError>↩ Ok<model::WorkspaceModule[]>13graph(modules: model::WorkspaceModule[])14graph(modules: model::WorkspaceModule[])15build(modules: model::WorkspaceModule[])16noExternals17buildWithExternals18resolveEach19resolveEach20index↩ return model::ModuleEntry[]↩ return model::ModuleEntry[]21graphOf22collectModule↩ return model::Graph↩ return model::Graph↩ return model::Graph23projectChosen24emitError25renderThemed26print
data

SvgOpts #

public
cli::SvgOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDSvgOptspathstringsymbolstringviewstringtargetstringthemestring
component

TokensCmd #

private
cli::TokensCmd

`pds tokens` — print the conformance token stream (`KIND@line:col "lexeme"`) +to stdout, for debugging the lexer.

Parent

Outbound

Scenarios

PrintTokenStream

`pds tokens` prints the conformance token stream.

  • given a .pds source file
  • when the developer runs `pds tokens`
  • then the lexical token stream is printed as KIND@line:col \"lexeme\"
Flow Flow — PrintTokenStream scroll to zoom · drag to pan
FEATUREPrintTokenStreamfor TokensCmdGIVENa .pds source fileWHENthe developer runs `pds tokens`THENthe lexical token stream is printed asKIND@line:col \"lexeme\"
Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunCOMPONENTTokensCmdPseudoscript::Cli`pds tokens` — printthe conformance tokenCONTAINERSyntaxPseudoscript`crates/pseudoscript-syntax`.The foundation crate:COMPONENTLexerPseudoscript::SyntaxHand-written lexer forLANG.md §2. One passCOMPONENTLineIndexPseudoscript::SyntaxPrecomputed newlineoffsets turning a byt…1renderTokens(text: string)2renderTokens(text: string)3tokenize4lex↩ return syntax::Token[]5build(src: string)↩ return syntax::LineIndex6renderStream↩ return string↩ return string7print
data

TokensOpts #

public
cli::TokensOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDTokensOptsfilestring
component

UpgradeCmd #

private
cli::UpgradeCmd

`pds upgrade` — download a release and install it over the running binary.

Parent

Sequence Sequence — run scroll to zoom · drag to pan
SEQUENCErunPERSONcallerCOMPONENTUpgradeCmdPseudoscript::Cli`pds upgrade` —download a release an…1run(version: string)↩ return Result<void, cli::IoError>
data

UpgradeOpts #

public
cli::UpgradeOpts
Entities Entity diagram scroll to zoom · drag to pan
RECORDUpgradeOptsversionstring
component

Watcher #

private
cli::Watcher

Filesystem watcher (`--watch`, via `notify`): watches the project root recursively, debounces each save burst, and on a relevant change rebuilds the site and bumps the version the browser's live-reload poll reads. Events under the output dir are ignored so writing the site never re-triggers a build.

Parent

Inbound

Scenarios

WatchAndLiveReload

`pds doc --watch` rebuilds on change and live-reloads the browser.

  • given a site served with `--watch`
  • when a .pds file or pds.toml under the root changes
  • then the watcher debounces the save burst and rebuilds the site
  • and the live-reload version is bumped
  • and the browser, polling /__livereload, reloads to the new version
  • but writes under the output directory are ignored so the build does not loop
Flow Flow — WatchAndLiveReload scroll to zoom · drag to pan
FEATUREWatchAndLiveReloadfor WatcherGIVENa site served with `--watch`WHENa .pds file or pds.toml under the rootchangesTHENthe watcher debounces the save burstand rebuilds the siteANDthe live-reload version is bumpedANDthe browser, polling /__livereload,reloads to the new versionBUTwrites under the output directory areignored so the build does not loop
data

Workspace #

public
cli::Workspace

The loaded project: the `[doc]` config, the resolved output directory diff --git a/model/site/module/context.html b/model/site/module/context.html index f0e5880c..1cea4af1 100644 --- a/model/site/module/context.html +++ b/model/site/module/context.html @@ -11,12 +11,12 @@ -

PseudoScript
Module

context

person

Developer #

public
context::Developer

Author of architecture models: edits `.pds` files in an IDE and runs the CLI.

Outbound

Sequence Sequence — editModel scroll to zoom · drag to pan
SEQUENCEeditModelPERSONDeveloperAuthor of architecturemodels: edits `.pds`SYSTEMEditorIDEs that speak theLanguage ServerSYSTEMPseudoscriptThe PseudoScript CLI —loads a `.pds`CONTAINERCliPseudoscript`crates/pseudoscript`— the binary crateCOMPONENTLspHostPseudoscript::Cli`pds lsp` — boot aTokio runtime andCONTAINERLspPseudoscript`crates/pseudoscript-lsp`.The stdio languageCOMPONENTServerPseudoscript::LspThe tower-lsp`Backend`: owns theCOMPONENTStorePseudoscript::LspThe workspace documentstore: the server's1openDocument(doc: model::WorkspaceModule)2serveLanguage(doc: model::WorkspaceModule)3runLsp()4run()5runStdio()6runStdio()↩ return↩ Ok<void>↩ return Result<void, cli::IoError>7onChange(doc: model::WorkspaceModule)8onChange(doc: model::WorkspaceModule)9change(doc: model::WorkspaceModule)↩ return10publishAll
Sequence Sequence — renderDocs scroll to zoom · drag to pan
SEQUENCErenderDocsalt[found.isErr][else]alt[dependencies.isErr][else]alt[modules.isErr][else]alt[config.isErr][else]alt[self.isHtml()]alt[written.isErr][else]alt[site.isErr][else]alt[project.isErr][else]alt[root.isErr][else]alt[built.isErr][else]PERSONDeveloperAuthor of architecturemodels: edits `.pds`SYSTEMPseudoscriptThe PseudoScript CLI —loads a `.pds`CONTAINERCliPseudoscript`crates/pseudoscript`— the binary crateCOMPONENTDocCmdPseudoscript::Cli`pds doc` — theheadline commandCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the projectCONTAINERProjectPseudoscript`crates/pseudoscript-project`.The disk-facingCOMPONENTLoaderPseudoscript::ProjectResolves and reads aworkspace off disk:CONTAINERModelPseudoscript`crates/pseudoscript-model`.AST to one resolvedCOMPONENTChecksPseudoscript::ModelStatic analysis(LANG.md §2.2, §2.3,CONTAINERDocPseudoscript`crates/pseudoscript-doc`.Turns a resolved grap…COMPONENTHealthPseudoscript::DocPositions the host'sdiagnostics in theCOMPONENTBuilderPseudoscript::ModelProjects the parsed,resolved workspaceCOMPONENTWorkspacePseudoscript::ModelThe resolved set ofmodules keyed by FQN1renderDocs(path: string)2runDoc(path: string)3noFormatOverride4run(path: string, cliFormat: string)5findRoot(start: string)6findRoot(start: string)7findRoot(start: string)↩ return Result<string, project::IoError>↩ return Result<string, project::IoError>8ioError↩ Err<cli::IoError>↩ Ok<string>↩ Err<cli::IoError>9load(root: string)10loadManifest↩ Err<cli::IoError>11loadModules↩ Err<cli::IoError>12loadDependencies↩ Err<cli::IoError>↩ Ok<cli::Workspace>↩ Err<cli::IoError>13resolveFormat14checkWorkspaceModules(modules: model::WorkspaceModule[], externals: model::WorkspaceModule[])15checkWorkspaceModules(modules: model::WorkspaceModule[], externals: model::WorkspaceModule[])↩ return model::ModuleDiagnostics[]↩ return model::ModuleDiagnostics[]16reportModules17prepareDiagnostics(modules: model::WorkspaceModule[], perModule: model::ModuleDiagnostics[])18prepare(modules: model::WorkspaceModule[], perModule: model::ModuleDiagnostics[])↩ return doc::DiagnosticInput[]↩ return doc::DiagnosticInput[]19graph(modules: model::WorkspaceModule[])20graph(modules: model::WorkspaceModule[])21build(modules: model::WorkspaceModule[])22noExternals23buildWithExternals↩ return model::ModuleEntry[]24graphOf↩ return model::Graph↩ return model::Graph25renderSite↩ Err<cli::IoError>26write↩ Err<cli::IoError>27isHtml28copyLogo↩ Ok<string>↩ Err<cli::IoError>↩ Ok<void>
system

Editor #

public
context::Editor

IDEs that speak the Language Server Protocol (VS Code, Neovim, …). The editor +

PseudoScript
Module

context

person

Developer #

public
context::Developer

Author of architecture models: edits `.pds` files in an IDE and runs the CLI.

Outbound

Sequence Sequence — editModel scroll to zoom · drag to pan
SEQUENCEeditModelloop[payloads]PERSONDeveloperAuthor of architecturemodels: edits `.pds`SYSTEMEditorIDEs that speak theLanguage ServerSYSTEMPseudoscriptThe PseudoScript CLI —loads a `.pds`CONTAINERCliPseudoscript`crates/pseudoscript`— the binary crateCOMPONENTLspHostPseudoscript::Cli`pds lsp` — boot aTokio runtime andCONTAINERLspPseudoscript`crates/pseudoscript-lsp`.The stdio languageCOMPONENTServerPseudoscript::LspThe tower-lsp`Backend`: owns theCOMPONENTStorePseudoscript::LspThe workspace documentstore: the server's1openDocument(doc: model::WorkspaceModule)2serveLanguage(doc: model::WorkspaceModule)3runLsp()4run()5runStdio()6runStdio()↩ return↩ Ok<void>↩ return Result<void, cli::IoError>7onChange(doc: model::WorkspaceModule)8onChange(doc: model::WorkspaceModule)9change(doc: model::WorkspaceModule)↩ return10publishAll11diagnostics()12workspace13ensureExternals14modules15buildWithExternals↩ return model::Workspace16collectDiagnostics↩ return model::ModuleDiagnostics[]17uriOf(fqn: string)↩ return string18publish
Sequence Sequence — renderDocs scroll to zoom · drag to pan
SEQUENCErenderDocsalt[found.isErr][else]alt[modules.isErr][else]alt[files.isErr][else]alt[loaded.isErr][else]alt[dependencies.isErr][else]alt[modules.isErr][else]alt[config.isErr][else]loop[entries]alt[kindMatches()][else]alt[found.isErr][else]alt[anchor.isErr][else]alt[kindMatches()][else]alt[found.isErr][else]alt[anchor.isErr][else]alt[kindMatches()][else]alt[found.isErr][else]alt[actor.isNone][else]alt[disclosed.isNone][else]alt[node.isErr][else]alt[view.isFeature][else]alt[view.isData][else]alt[view.isComponent][else]alt[view.isContainer][else]alt[view.isContext][else]alt[scene.isErr][else]alt[rendered.isErr][else]alt[rendered.isErr][else]alt[rendered.isErr][else]alt[health.isErr][else]alt[universe.isErr][else]alt[modulePages.isErr][else]alt[docPages.isErr][else]alt[index.isErr][else]alt[html.isErr][else]alt[isHtml()][else]alt[isHtml()]alt[written.isErr][else]alt[site.isErr][else]alt[project.isErr][else]alt[root.isErr][else]alt[built.isErr][else]PERSONDeveloperAuthor of architecturemodels: edits `.pds`SYSTEMPseudoscriptThe PseudoScript CLI —loads a `.pds`CONTAINERCliPseudoscript`crates/pseudoscript`— the binary crateCOMPONENTDocCmdPseudoscript::Cli`pds doc` — theheadline commandCOMPONENTLoaderPseudoscript::CliThe CLI loader:resolves the projectCONTAINERProjectPseudoscript`crates/pseudoscript-project`.The disk-facingCOMPONENTLoaderPseudoscript::ProjectResolves and reads aworkspace off disk:CONTAINERModelPseudoscript`crates/pseudoscript-model`.AST to one resolvedCOMPONENTChecksPseudoscript::ModelStatic analysis(LANG.md §2.2, §2.3,CONTAINERDocPseudoscript`crates/pseudoscript-doc`.Turns a resolved grap…COMPONENTHealthPseudoscript::DocPositions the host'sdiagnostics in theCOMPONENTBuilderPseudoscript::ModelProjects the parsed,resolved workspaceCOMPONENTWorkspacePseudoscript::ModelThe resolved set ofmodules keyed by FQNCOMPONENTSiteBuilderPseudoscript::DocOrchestrates the wholesite: build the URLCOMPONENTUrlsPseudoscript::DocMaps every node FQN toits page path andCOMPONENTPagesPseudoscript::DocProjects the resolvedgraph into per-pageCOMPONENTDiagramsPseudoscript::DocThe bridge into`emit`: project a vie…CONTAINEREmitPseudoscript`crates/pseudoscript-emit`.Projects a view into …COMPONENTProjectorPseudoscript::EmitProjects a `View` outof the graph (LANG.mdCOMPONENTEdgesPseudoscript::EmitBuilds the routed-edgeset for a C4 sceneCOMPONENTLayoutPseudoscript::EmitAssigns geometry.Projection stamps eac…COMPONENTTracePseudoscript::EmitWalks a callable'sbody trace intoCOMPONENTSsrPseudoscript::DocThe server-side renderboundary: aCOMPONENTShellPseudoscript::DocOwns the documentshell that wraps eachCOMPONENTEscapePseudoscript::DocAll user text — nodenames, `///` docs,CONTAINERUniversePseudoscript`crates/pseudoscript-universe`.Maps the resolved C4COMPONENTAdapterPseudoscript::UniverseAdapts a resolvedmodel into theCOMPONENTFlattenPseudoscript::UniverseFlattens the softwaregraph into theCOMPONENTFlowTracerPseudoscript::UniverseTraces everyentry-point flow: aCOMPONENTSearchIndexPseudoscript::DocBuilds the staticfull-text search inde…COMPONENTAssetsPseudoscript::DocShips the prebuiltpresentation bundles,COMPONENTMarkdownSitePseudoscript::DocThe static Markdownoutput mode: turns th…1renderDocs(path: string)2runDoc(path: string)3noFormatOverride4run(path: string, cliFormat: string)5findRoot(start: string)6findRoot(start: string)7findRoot(start: string)↩ return Result<string, project::IoError>↩ return Result<string, project::IoError>8ioError↩ Err<cli::IoError>↩ Ok<string>↩ Err<cli::IoError>9load(root: string)10loadManifest↩ Err<cli::IoError>11loadModules12load(root: string)13load(root: string)14findModules↩ Err<project::IoError>15readEach↩ Err<project::IoError>↩ Ok<model::WorkspaceModule[]>↩ return Result<model::WorkspaceModule[], project::IoError>16ioError↩ Err<cli::IoError>↩ Ok<model::WorkspaceModule[]>↩ Err<cli::IoError>17loadDependencies↩ Err<cli::IoError>↩ Ok<cli::Workspace>↩ Err<cli::IoError>18resolveFormat19checkWorkspaceModules(modules: model::WorkspaceModule[], externals: model::WorkspaceModule[])20checkWorkspaceModules(modules: model::WorkspaceModule[], externals: model::WorkspaceModule[])↩ return model::ModuleDiagnostics[]↩ return model::ModuleDiagnostics[]21reportModules22prepareDiagnostics(modules: model::WorkspaceModule[], perModule: model::ModuleDiagnostics[])23prepare(modules: model::WorkspaceModule[], perModule: model::ModuleDiagnostics[])↩ return doc::DiagnosticInput[]↩ return doc::DiagnosticInput[]24graph(modules: model::WorkspaceModule[])25graph(modules: model::WorkspaceModule[])26build(modules: model::WorkspaceModule[])27noExternals28buildWithExternals29resolveEach30resolveEach31index↩ return model::ModuleEntry[]↩ return model::ModuleEntry[]32graphOf33collectModule↩ return model::Graph↩ return model::Graph↩ return model::Graph34renderSite35isHtml36render(graph: model::Graph, config: doc::DocConfig, diagnostics: doc::DiagnosticInput[])37render(graph: model::Graph, config: doc::DocConfig, diagnostics: doc::DiagnosticInput[])38build(graph: model::Graph)↩ return39sortedModules(graph: model::Graph)↩ return string[]40index(graph: model::Graph, config: doc::DocConfig, modules: string[], diagnostics: doc::DiagnosticInput[])41docGroups42sidebar43navLinks44crumbsOf45moduleCards46context(graph: model::Graph)47contextView48figure49project(graph: model::Graph, view: emit::View)50project(graph: model::Graph, view: emit::View)51projectContext52collectKinds53collect(graph: model::Graph, inView: emit::PlacedNode[])54route55sortDedup↩ return emit::RoutedEdge[]56solveC4(scene: emit::Scene)↩ return emit::Scene↩ return emit::Scene↩ Ok<emit::Scene>57projectBoundary58requireKind59kindOf↩ Err<emit::EmitError>60kindMatches↩ Ok<string>↩ Err<emit::EmitError>↩ Err<emit::EmitError>61outerFrameOf62childrenOf63withOuterFrame64externalActors65nestSiblings66collect(graph: model::Graph, inView: emit::PlacedNode[])67route68sortDedup↩ return emit::RoutedEdge[]69solveC4(scene: emit::Scene)↩ return emit::Scene↩ Ok<emit::Scene>↩ return Result<emit::Scene, emit::EmitError>70projectBoundary71requireKind72kindOf↩ Err<emit::EmitError>73kindMatches↩ Ok<string>↩ Err<emit::EmitError>↩ Err<emit::EmitError>74outerFrameOf75childrenOf76withOuterFrame77externalActors78nestSiblings79collect(graph: model::Graph, inView: emit::PlacedNode[])80route81sortDedup↩ return emit::RoutedEdge[]82solveC4(scene: emit::Scene)↩ return emit::Scene↩ Ok<emit::Scene>↩ return Result<emit::Scene, emit::EmitError>83projectData↩ return Result<emit::Scene, emit::EmitError>84projectFeature↩ return Result<emit::Scene, emit::EmitError>85projectSequence86requireKind87kindOf↩ Err<emit::EmitError>88kindMatches↩ Ok<string>↩ Err<emit::EmitError>↩ Err<emit::EmitError>89bodyOf90blackBox(graph: model::Graph, entry: string)91ownerOf92blackBoxActor93inboundCall94returnItem95participantsOf↩ return emit::Scene↩ Ok<emit::Scene>96walk(graph: model::Graph, entry: string, body: model::Step[])97ownerOf98triggerActorOf99entryStack100untriggered101noCaller102traceItems103participantsOf↩ return emit::Scene↩ return emit::Scene104inboundCall105traceItems106prependItem107participantsOf↩ return emit::Scene↩ Ok<emit::Scene>↩ return Result<emit::Scene, emit::EmitError>↩ return Result<emit::Scene, emit::EmitError>↩ return doc::Diagram108embed109svgFigure↩ return doc::Diagram↩ return doc::Diagram↩ return doc::Diagram110stats↩ return doc::PageProps111emitPage112renderPage(props: doc::PageProps)↩ return Result<doc::RenderedPage, doc::RenderError>↩ Err<doc::RenderError>113wrap(path: string, props: doc::PageProps, rendered: doc::RenderedPage)114text(text: string)↩ return string115document↩ return string↩ Ok<doc::SiteFile>↩ Err<doc::RenderError>116emitDocPages↩ Err<doc::RenderError>117emitModulePages↩ Err<doc::RenderError>118universePage(graph: model::Graph, config: doc::DocConfig, modules: string[], diagnostics: doc::DiagnosticInput[])119docGroups120sidebar121navLinks122crumbsOf123fromModel(graph: model::Graph)124fromModel(graph: model::Graph)125placeStructural126wireContainment127tallyTraffic↩ return universe::SoftwareGraph↩ return universe::SoftwareGraph128snapshot(graph: universe::SoftwareGraph)129snapshot(graph: universe::SoftwareGraph)130nodesOf131edgesOf↩ return universe::Snapshot↩ return universe::Snapshot132flows(graph: model::Graph)133flows(graph: model::Graph)134entryPoints135traceAll↩ return universe::FlowDef[]↩ return universe::FlowDef[]136nodeHrefs↩ return doc::PageProps137emitPage138renderPage(props: doc::PageProps)↩ return Result<doc::RenderedPage, doc::RenderError>↩ Err<doc::RenderError>139wrap(path: string, props: doc::PageProps, rendered: doc::RenderedPage)140text(text: string)↩ return string141document↩ return string↩ Ok<doc::SiteFile>↩ Err<doc::RenderError>142healthPage(graph: model::Graph, config: doc::DocConfig, modules: string[], diagnostics: doc::DiagnosticInput[])143docGroups144sidebar145navLinks146crumbsOf147page(graph: model::Graph, diagnostics: doc::DiagnosticInput[])148attributeAll149countOf150countOf↩ return doc::PageBody↩ return doc::PageProps151emitPage152renderPage(props: doc::PageProps)↩ return Result<doc::RenderedPage, doc::RenderError>↩ Err<doc::RenderError>153wrap(path: string, props: doc::PageProps, rendered: doc::RenderedPage)154text(text: string)↩ return string155document↩ return string↩ Ok<doc::SiteFile>↩ Err<doc::RenderError>156build(graph: model::Graph, config: doc::DocConfig)157entries↩ return doc::SiteFile158shared()↩ return doc::SiteFile[]↩ Ok<doc::Site>↩ return Result<doc::Site, doc::RenderError>159renderError↩ Err<cli::IoError>↩ Ok<doc::Site>160renderMarkdown161renderMarkdown(graph: model::Graph, config: doc::DocConfig, diagnostics: doc::DiagnosticInput[])162buildAll(graph: model::Graph, config: doc::DocConfig, diagnostics: doc::DiagnosticInput[])↩ return doc::PageProps[]163render(pages: doc::PageProps[], theme: doc::Theme)↩ return doc::Site↩ return doc::Site↩ return doc::Site↩ Ok<doc::Site>↩ Err<cli::IoError>164write↩ Err<cli::IoError>165isHtml166copyLogo↩ Ok<string>↩ Err<cli::IoError>↩ Ok<void>
system

Editor #

public
context::Editor

IDEs that speak the Language Server Protocol (VS Code, Neovim, …). The editor spawns `pds lsp` as a child process and drives it over stdio: it streams document changes to the server and renders the diagnostics it publishes.

Inbound

Outbound

Containers Container diagram scroll to zoom · drag to pan
PERSONDeveloperAuthor of architecture models: edits `.pds`files in an IDE and runs the CLI.SYSTEMPseudoscriptThe PseudoScript CLI — loads a `.pds`workspace into one resolved graph and…renderDocs
system

Pseudoscript #

public
context::Pseudoscript

The PseudoScript CLI — loads a `.pds` workspace into one resolved graph and documents it as a static site, or serves the language to editors. Cross-system callers couple to these published faces; the containers stay behind them.

#headline

Inbound

Outbound

Scenarios

DocumentAWorkspace

Render a `.pds` workspace to a browsable documentation site — the headline -flow, aggregating load, check, graph, project, and write.

#headline
  • given a directory holding a pds.toml and one or more .pds modules
  • and the modules form a well-formed C4 model
  • when the developer runs `pds doc` against the directory
  • then every module is loaded into one resolved graph
  • and C4 and sequence diagrams are projected as scene geometry the site's client islands draw
  • and a static site is written under the configured output directory
Flow Flow — DocumentAWorkspace scroll to zoom · drag to pan
FEATUREDocumentAWorkspacefor PseudoscriptGIVENa directory holding a pds.toml and oneor more .pds modulesANDthe modules form a well-formed C4modelWHENthe developer runs `pds doc` againstthe directoryTHENevery module is loaded into oneresolved graphANDC4 and sequence diagrams are projectedas scene geometry the site's clientislands drawANDa static site is written under theconfigured output directory
CheckAModel

Statically validate a model and report what is wrong.

  • given a .pds source file
  • when the developer runs `pds check` on it
  • then the source is parsed with error recovery
  • and resolution, visibility, Result-flow, and return-coverage checks run
  • and every violation is reported as a diagnostic
  • but a well-formed model reports nothing and exits zero
Flow Flow — CheckAModel scroll to zoom · drag to pan
FEATURECheckAModelfor PseudoscriptGIVENa .pds source fileWHENthe developer runs `pds check` on itTHENthe source is parsed with errorrecoveryANDresolution, visibility, Result-flow,and return-coverage checks runANDevery violation is reported as adiagnosticBUTa well-formed model reports nothingand exits zero
FormatAModel

Reformat a model to one canonical form.

  • given a .pds source file with non-canonical spacing
  • when the developer runs `pds fmt`
  • then the source is parsed and pretty-printed to canonical PseudoScript
  • and comments and blank-line runs are preserved
  • but unparseable source is rejected without rewriting the file
Flow Flow — FormatAModel scroll to zoom · drag to pan
FEATUREFormatAModelfor PseudoscriptGIVENa .pds source file with non-canonicalspacingWHENthe developer runs `pds fmt`THENthe source is parsed andpretty-printed to canonicalPseudoScriptANDcomments and blank-line runs arepreservedBUTunparseable source is rejected withoutrewriting the file
EditModelInIde

Author a model in an editor with live diagnostics.

  • given an editor that speaks the Language Server Protocol
  • and `pds lsp` running as a child process over stdio
  • when the author changes a document
  • then the server re-checks the document from its current text
  • and diagnostics are published back to the editor
Flow Flow — EditModelInIde scroll to zoom · drag to pan
FEATUREEditModelInIdefor PseudoscriptGIVENan editor that speaks the LanguageServer ProtocolAND`pds lsp` running as a child processover stdioWHENthe author changes a documentTHENthe server re-checks the document fromits current textANDdiagnostics are published back to theeditor
ServeLiveDocs

Preview docs that rebuild as the model changes.

  • given a generated documentation site
  • when the developer runs `pds doc --serve --watch`
  • then the site is hosted over HTTP on localhost
  • and a change to any .pds or pds.toml rebuilds the site
  • and the browser live-reloads to the new version
Flow Flow — ServeLiveDocs scroll to zoom · drag to pan
FEATUREServeLiveDocsfor PseudoscriptGIVENa generated documentation siteWHENthe developer runs `pds doc --serve--watch`THENthe site is hosted over HTTP onlocalhostANDa change to any .pds or pds.tomlrebuilds the siteANDthe browser live-reloads to the newversion
Containers Container diagram scroll to zoom · drag to pan
PseudoscriptCONTAINERCli`crates/pseudoscript` — the binary crate(`pds`). The composition root and only I/O…CONTAINERDoc`crates/pseudoscript-doc`. Turns a resolvedgraph into a Svelte-rendered,…CONTAINERDot`crates/pseudoscript-dot`. Runs thefour-pass layered pipeline over a `Graph` t…CONTAINEREmit`crates/pseudoscript-emit`. Projects a viewinto a `Scene` and renders it to SVG; C4…CONTAINERFormat`crates/pseudoscript-format`. The canonicalformatter: parse, then pretty-print the tre…CONTAINERIdeSession`crates/pseudoscript-ide` — the IDEapplication, Rust compiled to a single wasm…CONTAINERWebIde`web-ide` — the IDE at `ide.pdscript.dev`: aSvelteKit shell on Cloudflare Workers stati…CONTAINERLanding`web-landing` — the marketing site at`pdscript.dev`. A static Svelte build on…CONTAINERLayout`crates/pseudoscript-layout`. Thegeometry/text `Core` and the sequence…CONTAINERLsp`crates/pseudoscript-lsp`. The stdiolanguage server (tower-lsp): workspace…CONTAINERLspCore`crates/pseudoscript-lsp-core`. The singlehome for language intelligence as…CONTAINERModel`crates/pseudoscript-model`. AST to oneresolved graph; static checks (resolution,…CONTAINERProject`crates/pseudoscript-project`. Thedisk-facing loader: resolves the workspace…CONTAINERSyntax`crates/pseudoscript-syntax`. The foundationcrate: source text to tokens and a typed…CONTAINERUniverse`crates/pseudoscript-universe`. Maps theresolved C4 model into the software graph…PERSONDeveloperAuthor of architecture models: edits `.pds`files in an IDE and runs the CLI.SYSTEMEditorIDEs that speak the Language Server Protocol(VS Code, Neovim, …). The editor spawns `pd…SYSTEMLlmProviderThe author's configured OpenAI-compatiblecompletion provider — hosted (OpenAI,…PERSONVisitorA prospective user reading the landing page.prepareDiagnosticsrenderrenderMarkdownformatrunStdiocheckcheckWorkspaceModulesgraphfindRootloadrenderTokensopenDocumentprojectflowsfromModelsnapshotgridLayoutrunPipelinelayoutparseprepareDiagnosticscheckcheckWorkspaceModulesgraphbuildflowssnapshotcompletedependencyModulesdiagnosticshovermountoutlinesetSourcechatCompletefimCompletelistModelsopenopenIdeshowInstalldefinitionformatEdithovercheckparseprojectgraph
Generated by pds doc.
+flow, aggregating load, check, graph, project, and write.

#headline
  • given a directory holding a pds.toml and one or more .pds modules
  • and the modules form a well-formed C4 model
  • when the developer runs `pds doc` against the directory
  • then every module is loaded into one resolved graph
  • and C4 and sequence diagrams are projected as scene geometry the site's client islands draw
  • and a static site is written under the configured output directory
Flow Flow — DocumentAWorkspace scroll to zoom · drag to pan
FEATUREDocumentAWorkspacefor PseudoscriptGIVENa directory holding a pds.toml and oneor more .pds modulesANDthe modules form a well-formed C4modelWHENthe developer runs `pds doc` againstthe directoryTHENevery module is loaded into oneresolved graphANDC4 and sequence diagrams are projectedas scene geometry the site's clientislands drawANDa static site is written under theconfigured output directory
CheckAModel

Statically validate a model and report what is wrong.

  • given a .pds source file
  • when the developer runs `pds check` on it
  • then the source is parsed with error recovery
  • and resolution, visibility, Result-flow, and return-coverage checks run
  • and every violation is reported as a diagnostic
  • but a well-formed model reports nothing and exits zero
Flow Flow — CheckAModel scroll to zoom · drag to pan
FEATURECheckAModelfor PseudoscriptGIVENa .pds source fileWHENthe developer runs `pds check` on itTHENthe source is parsed with errorrecoveryANDresolution, visibility, Result-flow,and return-coverage checks runANDevery violation is reported as adiagnosticBUTa well-formed model reports nothingand exits zero
FormatAModel

Reformat a model to one canonical form.

  • given a .pds source file with non-canonical spacing
  • when the developer runs `pds fmt`
  • then the source is parsed and pretty-printed to canonical PseudoScript
  • and comments and blank-line runs are preserved
  • but unparseable source is rejected without rewriting the file
Flow Flow — FormatAModel scroll to zoom · drag to pan
FEATUREFormatAModelfor PseudoscriptGIVENa .pds source file with non-canonicalspacingWHENthe developer runs `pds fmt`THENthe source is parsed andpretty-printed to canonicalPseudoScriptANDcomments and blank-line runs arepreservedBUTunparseable source is rejected withoutrewriting the file
EditModelInIde

Author a model in an editor with live diagnostics.

  • given an editor that speaks the Language Server Protocol
  • and `pds lsp` running as a child process over stdio
  • when the author changes a document
  • then the server re-checks the document from its current text
  • and diagnostics are published back to the editor
Flow Flow — EditModelInIde scroll to zoom · drag to pan
FEATUREEditModelInIdefor PseudoscriptGIVENan editor that speaks the LanguageServer ProtocolAND`pds lsp` running as a child processover stdioWHENthe author changes a documentTHENthe server re-checks the document fromits current textANDdiagnostics are published back to theeditor
ServeLiveDocs

Preview docs that rebuild as the model changes.

  • given a generated documentation site
  • when the developer runs `pds doc --serve --watch`
  • then the site is hosted over HTTP on localhost
  • and a change to any .pds or pds.toml rebuilds the site
  • and the browser live-reloads to the new version
Flow Flow — ServeLiveDocs scroll to zoom · drag to pan
FEATUREServeLiveDocsfor PseudoscriptGIVENa generated documentation siteWHENthe developer runs `pds doc --serve--watch`THENthe site is hosted over HTTP onlocalhostANDa change to any .pds or pds.tomlrebuilds the siteANDthe browser live-reloads to the newversion
Containers Container diagram scroll to zoom · drag to pan
PseudoscriptCONTAINERCli`crates/pseudoscript` — the binary crate(`pds`). The composition root and only I/O…CONTAINERDoc`crates/pseudoscript-doc`. Turns a resolvedgraph into a Svelte-rendered,…CONTAINERDot`crates/pseudoscript-dot`. Runs thefour-pass layered pipeline over a `Graph` t…CONTAINEREmit`crates/pseudoscript-emit`. Projects a viewinto a `Scene` and renders it to SVG; C4…CONTAINERFormat`crates/pseudoscript-format`. The canonicalformatter: parse, then pretty-print the tre…CONTAINERIdeSession`crates/pseudoscript-ide` — the IDEapplication, Rust compiled to a single wasm…CONTAINERWebIde`web-ide` — the IDE at `ide.pdscript.dev`: aSvelteKit shell on Cloudflare Workers stati…CONTAINERLanding`web-landing` — the marketing site at`pdscript.dev`. A static Svelte build on…CONTAINERLayout`crates/pseudoscript-layout`. Thegeometry/text `Core` and the sequence…CONTAINERLsp`crates/pseudoscript-lsp`. The stdiolanguage server (tower-lsp): workspace…CONTAINERLspCore`crates/pseudoscript-lsp-core`. The singlehome for language intelligence as…CONTAINERModel`crates/pseudoscript-model`. AST to oneresolved graph; static checks (resolution,…CONTAINERProject`crates/pseudoscript-project`. Thedisk-facing loader: resolves the workspace…CONTAINERSyntax`crates/pseudoscript-syntax`. The foundationcrate: source text to tokens and a typed…CONTAINERUniverse`crates/pseudoscript-universe`. Maps theresolved C4 model into the software graph…PERSONDeveloperAuthor of architecture models: edits `.pds`files in an IDE and runs the CLI.SYSTEMEditorIDEs that speak the Language Server Protocol(VS Code, Neovim, …). The editor spawns `pd…SYSTEMLlmProviderThe author's configured OpenAI-compatiblecompletion provider — hosted (OpenAI,…PERSONVisitorA prospective user reading the landing page.prepareDiagnosticsrenderrenderMarkdownformatrunStdiocheckcheckWorkspaceModulesgraphfindRootloadrenderTokensopenDocumentprojectflowsfromModelsnapshotgridLayoutrunPipelinelayoutparseprepareDiagnosticscheckcheckWorkspaceModulesgraphbuildflowssnapshotcompletedependencyModulesdiagnosticshovermountoutlinesetSourcechatCompletefimCompletelistModelsopenopenIdeshowInstalldefinitionformatEdithovercheckparseprojectgraph
Generated by pds doc.
diff --git a/model/site/module/doc.html b/model/site/module/doc.html index 79cc23da..ae87bcb1 100644 --- a/model/site/module/doc.html +++ b/model/site/module/doc.html @@ -11,13 +11,13 @@ -
PseudoScript
Module

doc

component

Assets #

private
doc::Assets

Ships the prebuilt presentation bundles, authored in the `web/` Svelte package and +

PseudoScript
Module

doc

component

Assets #

private
doc::Assets

Ships the prebuilt presentation bundles, authored in the `web/` Svelte package and embedded at build time (LANG.md §9.3). `style.css`, `client.js`, and `universe.js` ship once at the site root; `ssr.js` is build-time only — the engine (`Doc::Ssr`) evaluates it and it is never written to the site. The universe bundle is the only one that carries WebGL; the SSR bundle never does.

Parent

Inbound

Outbound

Scenarios

SharedAssetsShipOnce

Shared assets ship once and every page links them.

  • given a multi-page site
  • when it is generated
  • then style.css and client.js are emitted once at the site root
  • and every page links them by a depth-relative path rather than inlining them
Flow Flow — SharedAssetsShipOnce scroll to zoom · drag to pan
FEATURESharedAssetsShipOncefor AssetsGIVENa multi-page siteWHENit is generatedTHENstyle.css and client.js are emittedonce at the site rootANDevery page links them by adepth-relative path rather thaninlining them
data

Call #

public
doc::Call
Entities Entity diagram scroll to zoom · drag to pan
RECORDCallmessagestring
data

Codec #

public
doc::Codec
Entities Entity diagram scroll to zoom · drag to pan
RECORDCodecmessagestring
data

Crumb #

public
doc::Crumb

One breadcrumb hop: its label and href; the trailing crumb (the current -page) carries no href.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDCrumblabelstringhrefstring
data

DiagnosticInput #

public
doc::DiagnosticInput

One host-prepared diagnostic, positioned in its module's source: severity +page) carries no href.

Entities Entity diagram scroll to zoom · drag to pan
RECORDCrumblabelstringhrefstring
data

DiagnosticInput #

public
doc::DiagnosticInput

One host-prepared diagnostic, positioned in its module's source: severity word (`error`/`warning`), the stable rule code and its principle-article URL (empty for plain diagnostics), the message, and the 1-based line/column with the byte span. The host computes line/column once — this crate never sees @@ -47,10 +47,10 @@ whose section it belongs on, and builds the health page and the per-section badges. Attribution is positional: the node whose declaration starts closest before the diagnostic's span owns it, a callable lifting to its -owner; a span no node encloses falls back to the module page.

Parent

Inbound

Outbound

Scenarios

HealthPageListsDiagnostics

The health page lists every finding; affected sections carry badges.

  • given host-prepared diagnostics including an architecture lint
  • when the site is rendered
  • then the health page lists each finding with severity, code, location, and its principle-article link
  • and each entry deep-links to the owning node's documentation section
  • and the affected section shows an inline badge
  • but a clean model renders an empty health page, not a missing one
Flow Flow — HealthPageListsDiagnostics scroll to zoom · drag to pan
FEATUREHealthPageListsDiagnosticsfor HealthGIVENhost-prepared diagnostics including anarchitecture lintWHENthe site is renderedTHENthe health page lists each findingwith severity, code, location, and itsprinciple-article linkANDeach entry deep-links to the owningnode's documentation sectionANDthe affected section shows an inlinebadgeBUTa clean model renders an empty healthpage, not a missing one
data

HealthEntry #

public
doc::HealthEntry

One architecture-health finding, attributed to the node whose section it +owner; a span no node encloses falls back to the module page.

Parent

Inbound

Outbound

Scenarios

HealthPageListsDiagnostics

The health page lists every finding; affected sections carry badges.

  • given host-prepared diagnostics including an architecture lint
  • when the site is rendered
  • then the health page lists each finding with severity, code, location, and its principle-article link
  • and each entry deep-links to the owning node's documentation section
  • and the affected section shows an inline badge
  • but a clean model renders an empty health page, not a missing one
Flow Flow — HealthPageListsDiagnostics scroll to zoom · drag to pan
FEATUREHealthPageListsDiagnosticsfor HealthGIVENhost-prepared diagnostics including anarchitecture lintWHENthe site is renderedTHENthe health page lists each findingwith severity, code, location, and itsprinciple-article linkANDeach entry deep-links to the owningnode's documentation sectionANDthe affected section shows an inlinebadgeBUTa clean model renders an empty healthpage, not a missing one
data

HealthEntry #

public
doc::HealthEntry

One architecture-health finding, attributed to the node whose section it belongs on: the diagnostic fields plus the owning node's FQN and the (prefixed) href to its section — the module page when no node encloses the -span.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDHealthEntrymodulestringseveritystringcodestringcodeUrlstringmessagestringlinenumbercolumnnumbernodeFqnstringhrefstring
data

HealthPage #

public
doc::HealthPage
Entities Entity diagram scroll to zoom · drag to pan
RECORDHealthPageentriesdoc::HealthEntry[]errorCountnumberwarningCountnumberRECORDHealthEntrymodulestringseveritystringcodestringcodeUrlstringmessagestringlinenumbercolumnnumbernodeFqnstringhrefstring
component

Highlight #

private
doc::Highlight

Highlights fenced code blocks at build time, so the shipped site needs no +span.

Entities Entity diagram scroll to zoom · drag to pan
RECORDHealthEntrymodulestringseveritystringcodestringcodeUrlstringmessagestringlinenumbercolumnnumbernodeFqnstringhrefstring
data

HealthPage #

public
doc::HealthPage
Entities Entity diagram scroll to zoom · drag to pan
RECORDHealthPageentriesdoc::HealthEntry[]errorCountnumberwarningCountnumberRECORDHealthEntrymodulestringseveritystringcodestringcodeUrlstringmessagestringlinenumbercolumnnumbernodeFqnstringhrefstring
component

Highlight #

private
doc::Highlight

Highlights fenced code blocks at build time, so the shipped site needs no client highlighter and one deterministic output serves every theme: PseudoScript through the toolchain's own lexer, emitting class-based spans coloured by the site stylesheet. Any other language passes through escaped @@ -65,11 +65,11 @@ (nodes by level, edges with traffic, each flow's legs); the health page as a table with article links. No SSR engine, no shared assets, no logo — the engine-free site for wikis and code hosts.

Parent

Inbound

Scenarios

MarkdownSiteMirrorsHtml

The Markdown site is the engine-free twin of the HTML site.

  • given the same resolved graph and configuration
  • when the Markdown site is rendered
  • then one .md per page mirrors the HTML site's paths
  • and each diagram is written as a standalone themed .svg asset referenced as an image
  • but no SSR engine runs and no shared assets or logo are emitted
Flow Flow — MarkdownSiteMirrorsHtml scroll to zoom · drag to pan
FEATUREMarkdownSiteMirrorsHtmlfor MarkdownSiteGIVENthe same resolved graph andconfigurationWHENthe Markdown site is renderedTHENone .md per page mirrors the HTMLsite's pathsANDeach diagram is written as astandalone themed .svg assetreferenced as an imageBUTno SSR engine runs and no sharedassets or logo are emitted
data

ModuleCard #

public
doc::ModuleCard

A module card on the index: its FQN, the (prefixed) href to its page, and an -"N item(s)" line.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDModuleCardnamestringhrefstringmetastring
data

ModulePage #

public
doc::ModulePage
Entities Entity diagram scroll to zoom · drag to pan
RECORDModulePagenamestringsectionsdoc::NodeSection[]RECORDNodeSectionidstringkindstringnamestringfqnstringvisibilitystringsummarystringextendedstringtagsstring[]relationshipsdoc::RelGroup[]scenariosdoc::ScenarioCard[]diagramsdoc::Diagram[]diagnosticsdoc::SectionDiagnostic[]
data

NodeHref #

public
doc::NodeHref

A universe node's documentation link: its id and the (prefixed) href to its -section, so clicking a sphere can navigate to the docs.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeHrefidstringhrefstring
data

NodeSection #

public
doc::NodeSection

One node's documentation section: head fields, `///` docs, tags, relationship +"N item(s)" line.

Entities Entity diagram scroll to zoom · drag to pan
RECORDModuleCardnamestringhrefstringmetastring
data

ModulePage #

public
doc::ModulePage
Entities Entity diagram scroll to zoom · drag to pan
RECORDModulePagenamestringsectionsdoc::NodeSection[]RECORDNodeSectionidstringkindstringnamestringfqnstringvisibilitystringsummarystringextendedstringtagsstring[]relationshipsdoc::RelGroup[]scenariosdoc::ScenarioCard[]diagramsdoc::Diagram[]diagnosticsdoc::SectionDiagnostic[]
data

NodeHref #

public
doc::NodeHref

A universe node's documentation link: its id and the (prefixed) href to its +section, so clicking a sphere can navigate to the docs.

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeHrefidstringhrefstring
data

NodeSection #

public
doc::NodeSection

One node's documentation section: head fields, `///` docs, tags, relationship groups, scenario cards, embedded diagrams, and the health findings attributed -to the node (rendered as inline badges).

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeSectionidstringkindstringnamestringfqnstringvisibilitystringsummarystringextendedstringtagsstring[]relationshipsdoc::RelGroup[]scenariosdoc::ScenarioCard[]diagramsdoc::Diagram[]diagnosticsdoc::SectionDiagnostic[]RECORDRelGrouptitlestringitemsdoc::RelItem[]RECORDScenarioCardnamestringsummarystringextendedstringtagsstring[]stepsdoc::Step[]flowdoc::DiagramUNIONDiagramSvgEmptyRECORDSectionDiagnosticseveritystringcodestringcodeUrlstringmessagestringlinenumber
data

NodeUrl #

public
doc::NodeUrl

A resolved cross-reference target: the module page it lives on and its in-page +to the node (rendered as inline badges).

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeSectionidstringkindstringnamestringfqnstringvisibilitystringsummarystringextendedstringtagsstring[]relationshipsdoc::RelGroup[]scenariosdoc::ScenarioCard[]diagramsdoc::Diagram[]diagnosticsdoc::SectionDiagnostic[]RECORDRelGrouptitlestringitemsdoc::RelItem[]RECORDScenarioCardnamestringsummarystringextendedstringtagsstring[]stepsdoc::Step[]flowdoc::DiagramUNIONDiagramSvgEmptyRECORDSectionDiagnosticseveritystringcodestringcodeUrlstringmessagestringlinenumber
data

NodeUrl #

public
doc::NodeUrl

A resolved cross-reference target: the module page it lives on and its in-page anchor id. A cross-link uses these to build an `href`; an unresolved FQN has no entry and renders as plain text.

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeUrlpagestringanchorstring
data

PageBody #

public
doc::PageBody

The page body: the landing index, one authored doc page, one module's page, the 3D universe, or the architecture-health report.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
UNIONPageBodyIndexPageDocBodyModulePageUniversePageHealthPageRECORDIndexPagetitlestringcontextDiagramdoc::Diagramcardsdoc::ModuleCard[]statsdoc::SiteStatsRECORDDocBodytitlestringhtmlstringRECORDModulePagenamestringsectionsdoc::NodeSection[]RECORDUniversePagenodesuniverse::NodeOut[]edgesuniverse::EdgeOut[]flowsuniverse::FlowDef[]hrefsdoc::NodeHref[]RECORDHealthPageentriesdoc::HealthEntry[]errorCountnumberwarningCountnumber
data

PageProps #

public
doc::PageProps

The serialisable model for one page, handed to the Svelte server renderer. @@ -82,23 +82,23 @@ also embedded in the document, for its 3D island — every other page ships no page data.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDPagePropssitedoc::SiteInfodocGroupsdoc::SidebarDocGroup[]sidebardoc::SidebarModule[]navdoc::NavLink[]crumbsdoc::Crumb[]pagedoc::PageBodyRECORDSiteInfonamestringthemestringlogoFilenamestringprefixstringRECORDSidebarDocGrouptitlestringitemsdoc::SidebarDocItem[]RECORDSidebarModulelabelstringhrefstringnodesdoc::SidebarNode[]RECORDNavLinklabelstringhrefstringbadgestringRECORDCrumblabelstringhrefstringUNIONPageBodyIndexPageDocBodyModulePageUniversePageHealthPage
component

Pages #

private
doc::Pages

Projects the resolved graph into per-page `PageProps` — pure data, no markup. Builds the index props (title, context diagram, module cards), each module page's -node sections, and the depth-prefixed sidebar tree mirroring the C4 hierarchy.

Parent

Inbound

Outbound

Scenarios

UniversePageShipsStructureAndFlows

The universe page ships the 3D structure and its flows.

  • given a resolved model with entry-point flows
  • when the universe page is built
  • then the flat snapshot, the traced flows, and each node's documentation href are embedded
  • and the 3D island lays the graph out in the browser and links spheres to their docs
  • but a browser without WebGL keeps the server-rendered fallback listing
Flow Flow — UniversePageShipsStructureAndFlows scroll to zoom · drag to pan
FEATUREUniversePageShipsStructureAndFlowsfor PagesGIVENa resolved model with entry-pointflowsWHENthe universe page is builtTHENthe flat snapshot, the traced flows,and each node's documentation href areembeddedANDthe 3D island lays the graph out inthe browser and links spheres to theirdocsBUTa browser without WebGL keeps theserver-rendered fallback listing
SidebarMirrorsHierarchy

The sidebar tree mirrors the C4 hierarchy.

  • given modules whose nodes nest containers, components, and declarations
  • when the sidebar is rendered
  • then each module holds its top-level nodes and every node nests its same-module children
Flow Flow — SidebarMirrorsHierarchy scroll to zoom · drag to pan
FEATURESidebarMirrorsHierarchyfor PagesGIVENmodules whose nodes nest containers,components, and declarationsWHENthe sidebar is renderedTHENeach module holds its top-level nodesand every node nests its same-modulechildren
data

RelGroup #

public
doc::RelGroup

One relationship group ("Parent", "Inbound", or "Outbound") and its endpoints.

Entities Entity diagram scroll to zoom · drag to pan
RECORDRelGrouptitlestringitemsdoc::RelItem[]RECORDRelItemedgeKindstringarrowboolfqnstringhrefstringlabelstring
data

RelItem #

public
doc::RelItem

One relationship endpoint: the edge-kind word, whether to draw the outbound +node sections, and the depth-prefixed sidebar tree mirroring the C4 hierarchy.

Parent

Inbound

Outbound

Scenarios

UniversePageShipsStructureAndFlows

The universe page ships the 3D structure and its flows.

  • given a resolved model with entry-point flows
  • when the universe page is built
  • then the flat snapshot, the traced flows, and each node's documentation href are embedded
  • and the 3D island lays the graph out in the browser and links spheres to their docs
  • but a browser without WebGL keeps the server-rendered fallback listing
Flow Flow — UniversePageShipsStructureAndFlows scroll to zoom · drag to pan
FEATUREUniversePageShipsStructureAndFlowsfor PagesGIVENa resolved model with entry-pointflowsWHENthe universe page is builtTHENthe flat snapshot, the traced flows,and each node's documentation href areembeddedANDthe 3D island lays the graph out inthe browser and links spheres to theirdocsBUTa browser without WebGL keeps theserver-rendered fallback listing
SidebarMirrorsHierarchy

The sidebar tree mirrors the C4 hierarchy.

  • given modules whose nodes nest containers, components, and declarations
  • when the sidebar is rendered
  • then each module holds its top-level nodes and every node nests its same-module children
Flow Flow — SidebarMirrorsHierarchy scroll to zoom · drag to pan
FEATURESidebarMirrorsHierarchyfor PagesGIVENmodules whose nodes nest containers,components, and declarationsWHENthe sidebar is renderedTHENeach module holds its top-level nodesand every node nests its same-modulechildren
data

RelGroup #

public
doc::RelGroup

One relationship group ("Parent", "Inbound", or "Outbound") and its endpoints.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDRelGrouptitlestringitemsdoc::RelItem[]RECORDRelItemedgeKindstringarrowboolfqnstringhrefstringlabelstring
data

RelItem #

public
doc::RelItem

One relationship endpoint: the edge-kind word, whether to draw the outbound arrow, the endpoint FQN, its resolved href (absent → plain text), and any label.

Entities Entity diagram scroll to zoom · drag to pan
RECORDRelItemedgeKindstringarrowboolfqnstringhrefstringlabelstring
component

Relationships #

private
doc::Relationships

Renders a node's relationships (LANG.md §9.3): its `for`/owner parent, inbound edges, and outbound edges, skipping the structural `for`-parent edge kind. Each -endpoint is a cross-link when its FQN resolves, plain text otherwise.

Parent

Inbound

Outbound

data

RenderError #

public
doc::RenderError

Why server-rendering a page failed. Every variant is a defect in the bundle, the +endpoint is a cross-link when its FQN resolves, plain text otherwise.

Parent

Inbound

Outbound

data

RenderError #

public
doc::RenderError

Why server-rendering a page failed. Every variant is a defect in the bundle, the engine, or the props codec — never user model data; well-formed props always render.

Entities Entity diagram scroll to zoom · drag to pan
UNIONRenderErrorEngineCallCodecRECORDEnginemessagestringRECORDCallmessagestringRECORDCodecmessagestring
data

RenderedPage #

public
doc::RenderedPage

The server-render result for one page: the `<svelte:head>` contents and the rendered body markup, dropped into the document shell.

Entities Entity diagram scroll to zoom · drag to pan
RECORDRenderedPageheadstringbodystring
data

ScenarioCard #

public
doc::ScenarioCard

One BDD scenario card: name, `///` docs, tags, the ordered steps, and the feature's flow figure (LANG.md §9.5).

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDScenarioCardnamestringsummarystringextendedstringtagsstring[]stepsdoc::Step[]flowdoc::DiagramRECORDStepkeywordstringtextstringUNIONDiagramSvgEmpty
component

Scenarios #

private
doc::Scenarios

Renders the BDD scenario cards on a node's page: each `feature` targeting the node becomes its given/when/then steps and its flow figure as a card (LANG.md §5.2, -§9.3, §9.5). Empty when the node has no features.

Parent

Inbound

Outbound

Scenarios

RenderScenarioCards

Feature scenarios render on their target node's page (§9.3).

  • given a model whose nodes carry `feature` scenarios
  • when the site is generated
  • then each node's page lists its scenarios as given/when/then cards
  • but a node with no features renders no scenarios section
Flow Flow — RenderScenarioCards scroll to zoom · drag to pan
FEATURERenderScenarioCardsfor ScenariosGIVENa model whose nodes carry `feature`scenariosWHENthe site is generatedTHENeach node's page lists its scenariosas given/when/then cardsBUTa node with no features renders noscenarios section
data

SearchEntry #

public
doc::SearchEntry

One record in the static full-text search index: the symbol or page's FQN, +§9.3, §9.5). Empty when the node has no features.

Parent

Inbound

Outbound

Scenarios

RenderScenarioCards

Feature scenarios render on their target node's page (§9.3).

  • given a model whose nodes carry `feature` scenarios
  • when the site is generated
  • then each node's page lists its scenarios as given/when/then cards
  • but a node with no features renders no scenarios section
Flow Flow — RenderScenarioCards scroll to zoom · drag to pan
FEATURERenderScenarioCardsfor ScenariosGIVENa model whose nodes carry `feature`scenariosWHENthe site is generatedTHENeach node's page lists its scenariosas given/when/then cardsBUTa node with no features renders noscenarios section
data

SearchEntry #

public
doc::SearchEntry

One record in the static full-text search index: the symbol or page's FQN, display name, kind word, one-line summary, plain-text body, declaring module, and root-relative href. Sorted by href then FQN so the index is -deterministic.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDSearchEntryfqnstringnamestringkindstringsummarystringtextstringmodulestringhrefstring
component

SearchIndex #

private
doc::SearchIndex

Builds the static full-text search index the client palette queries: one +deterministic.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSearchEntryfqnstringnamestringkindstringsummarystringtextstringmodulestringhrefstring
component

SearchIndex #

private
doc::SearchIndex

Builds the static full-text search index the client palette queries: one record per linkable node, module page, and authored doc page, plus the universe and health pages. Shipped as a classic-script JS assignment (`search-index.js` setting a window global) so it loads under `file://` — -never fetched.

Parent

Inbound

Outbound

Scenarios

SearchIndexIsStatic

The search index is static and file://-safe.

  • given a generated site
  • when the search palette opens
  • then the index loads as a classic script setting a window global, never fetched
  • and entries cover nodes, modules, authored docs, and the universe and health pages
  • and the index is sorted and deterministic
Flow Flow — SearchIndexIsStatic scroll to zoom · drag to pan
FEATURESearchIndexIsStaticfor SearchIndexGIVENa generated siteWHENthe search palette opensTHENthe index loads as a classic scriptsetting a window global, never fetchedANDentries cover nodes, modules, authoreddocs, and the universe and healthpagesANDthe index is sorted and deterministic
data

SectionDiagnostic #

public
doc::SectionDiagnostic

One inline badge on a node's section: the severity, rule code, article URL, +never fetched.

Parent

Inbound

Outbound

Scenarios

SearchIndexIsStatic

The search index is static and file://-safe.

  • given a generated site
  • when the search palette opens
  • then the index loads as a classic script setting a window global, never fetched
  • and entries cover nodes, modules, authored docs, and the universe and health pages
  • and the index is sorted and deterministic
Flow Flow — SearchIndexIsStatic scroll to zoom · drag to pan
FEATURESearchIndexIsStaticfor SearchIndexGIVENa generated siteWHENthe search palette opensTHENthe index loads as a classic scriptsetting a window global, never fetchedANDentries cover nodes, modules, authoreddocs, and the universe and healthpagesANDthe index is sorted and deterministic
data

SectionDiagnostic #

public
doc::SectionDiagnostic

One inline badge on a node's section: the severity, rule code, article URL, message, and source line of a finding attributed to that node.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDSectionDiagnosticseveritystringcodestringcodeUrlstringmessagestringlinenumber
component

Shell #

private
doc::Shell

Owns the document shell that wraps each server-rendered body — the part Rust renders, not Svelte: doctype, `<html data-theme>`, font + stylesheet links, the theme pre-paint script, and the classic deferred scripts. Scripts are @@ -106,19 +106,19 @@ the site works opened from disk (`file://`). Only the universe page embeds its props (`window.__DATA__`) — for its 3D island; every other page ships no page data and the client only progressively enhances.

#app

Parent

Inbound

Outbound

Scenarios

OnlyUniverseEmbedsData

Only the universe page carries page data; everything else is pure SSR.

  • given a generated site
  • when the document shells are assembled
  • then the universe page embeds its props as window.__DATA__ for the 3D island
  • and every script is a classic deferred script so the site works from file://
  • but no other page embeds page data — the client only progressively enhances
Flow Flow — OnlyUniverseEmbedsData scroll to zoom · drag to pan
FEATUREOnlyUniverseEmbedsDatafor ShellGIVENa generated siteWHENthe document shells are assembledTHENthe universe page embeds its props aswindow.__DATA__ for the 3D islandANDevery script is a classic deferredscript so the site works from file://BUTno other page embeds page data — theclient only progressively enhances
SystemThemeFollowsOs

The theme follows the OS by default, overridable in-page and in config.

  • given a DocConfig carrying the system theme
  • when the document shell is rendered
  • then a pre-paint script applies the stored preference, else the OS scheme, else the configured default
  • and diagram SVGs follow the active scheme through the --pds-* variables
  • but a light or dark config pins the default without removing the toggle
Flow Flow — SystemThemeFollowsOs scroll to zoom · drag to pan
FEATURESystemThemeFollowsOsfor ShellGIVENa DocConfig carrying the system themeWHENthe document shell is renderedTHENa pre-paint script applies the storedpreference, else the OS scheme, elsethe configured defaultANDdiagram SVGs follow the active schemethrough the --pds-* variablesBUTa light or dark config pins thedefault without removing the toggle
data

SidebarDocGroup #

public
doc::SidebarDocGroup

One `[[doc.sidebar]]` group in the authored-doc navigation: a heading and its -page links, shown above the module tree.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDSidebarDocGrouptitlestringitemsdoc::SidebarDocItem[]RECORDSidebarDocItemtitlestringhrefstring
data

SidebarDocItem #

public
doc::SidebarDocItem

One authored doc-page link in the sidebar: its title and the page href, already +page links, shown above the module tree.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSidebarDocGrouptitlestringitemsdoc::SidebarDocItem[]RECORDSidebarDocItemtitlestringhrefstring
data

SidebarDocItem #

public
doc::SidebarDocItem

One authored doc-page link in the sidebar: its title and the page href, already prefixed for the page's depth.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSidebarDocItemtitlestringhrefstring
data

SidebarModule #

public
doc::SidebarModule

One sidebar entry: a module-page link and its node tree, hrefs already prefixed -for the page's depth.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDSidebarModulelabelstringhrefstringnodesdoc::SidebarNode[]RECORDSidebarNodenamestringfqnstringkindstringhrefstringchildrendoc::SidebarNode[]
data

SidebarNode #

public
doc::SidebarNode

One node in the sidebar tree, recursing into its same-module children.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSidebarNodenamestringfqnstringkindstringhrefstringchildrendoc::SidebarNode[]
data

Site #

public
doc::Site

The whole generated site as in-memory files, in deterministic order: the index, +for the page's depth.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSidebarModulelabelstringhrefstringnodesdoc::SidebarNode[]RECORDSidebarNodenamestringfqnstringkindstringhrefstringchildrendoc::SidebarNode[]
data

SidebarNode #

public
doc::SidebarNode

One node in the sidebar tree, recursing into its same-module children.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSidebarNodenamestringfqnstringkindstringhrefstringchildrendoc::SidebarNode[]
data

Site #

public
doc::Site

The whole generated site as in-memory files, in deterministic order: the index, one page per authored doc (`[[doc.sidebar]]`, in declaration order), then one page per module sorted by FQN, plus the shared assets. The doc crate does no I/O; the CLI writes each file under the configured output directory.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSitefilesdoc::SiteFile[]RECORDSiteFilepathstringcontentsstring
component

SiteBuilder #

private
doc::SiteBuilder

Orchestrates the whole site: build the URL map, project each page's props, server render it through the SSR engine, wrap it in the document shell, and ship the shared assets plus the search index. Pure: returns in-memory `Site` files, -writes none.

Parent

Inbound

Outbound

Scenarios

DeterministicGeneration

Generation is deterministic and pure.

  • given the same resolved graph and configuration rendered twice
  • when the two sites are compared
  • then modules and nodes are ordered by FQN with no clock or randomness consulted
  • and the file lists are byte-for-byte identical
Flow Flow — DeterministicGeneration scroll to zoom · drag to pan
FEATUREDeterministicGenerationfor SiteBuilderGIVENthe same resolved graph andconfiguration rendered twiceWHENthe two sites are comparedTHENmodules and nodes are ordered by FQNwith no clock or randomness consultedANDthe file lists are byte-for-byteidentical
data

SiteFile #

public
doc::SiteFile

One generated file: a site-root-relative `/`-separated path (`index.html`, +writes none.

Parent

Inbound

Outbound

Scenarios

DeterministicGeneration

Generation is deterministic and pure.

  • given the same resolved graph and configuration rendered twice
  • when the two sites are compared
  • then modules and nodes are ordered by FQN with no clock or randomness consulted
  • and the file lists are byte-for-byte identical
Flow Flow — DeterministicGeneration scroll to zoom · drag to pan
FEATUREDeterministicGenerationfor SiteBuilderGIVENthe same resolved graph andconfiguration rendered twiceWHENthe two sites are comparedTHENmodules and nodes are ordered by FQNwith no clock or randomness consultedANDthe file lists are byte-for-byteidentical
data

SiteFile #

public
doc::SiteFile

One generated file: a site-root-relative `/`-separated path (`index.html`, `module/banking.core.html`, `style.css`, `client.js`) and its complete contents.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDSiteFilepathstringcontentsstring
data

SiteInfo #

public
doc::SiteInfo

Site-wide presentation, identical across pages: the title, the `light`/`dark`/`system` theme word, the optional logo filename, and the `../` prefix to the site root for this page's depth.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSiteInfonamestringthemestringlogoFilenamestringprefixstring
data

SiteStats #

public
doc::SiteStats

The index page's stats strip: how many systems, containers, components, -flows, and health findings the model carries.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDSiteStatssystemsnumbercontainersnumbercomponentsnumberflowsnumberfindingsnumber
component

Ssr #

private
doc::Ssr

The server-side render boundary: a JSON-string-in / JSON-string-out seam to the +flows, and health findings the model carries.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSiteStatssystemsnumbercontainersnumbercomponentsnumberflowsnumberfindingsnumber
component

Ssr #

private
doc::Ssr

The server-side render boundary: a JSON-string-in / JSON-string-out seam to the JavaScript renderer. The native build embeds a `QuickJS` engine that evaluates the prebuilt `ssr.js` bundle (`Doc::Assets`) in-process; a wasm host implements the same seam against its own JS engine. A defect here is a build-asset fault, never @@ -128,7 +128,7 @@ `Dark` pin the scheme.

Entities Entity diagram scroll to zoom · drag to pan
UNIONThemeLightDarkSystem
data

UniversePage #

public
doc::UniversePage
Entities Entity diagram scroll to zoom · drag to pan
RECORDUniversePagenodesuniverse::NodeOut[]edgesuniverse::EdgeOut[]flowsuniverse::FlowDef[]hrefsdoc::NodeHref[]RECORDNodeOutidstringlevelstringparentstringRECORDEdgeOutsourcestringtostringtrafficnumberRECORDFlowDeffqnstringnamestringcolorstringhopsuniverse::FlowHop[]RECORDNodeHrefidstringhrefstring
component

Urls #

private
doc::Urls

Maps every node FQN to its page path and anchor, and resolves cross-references to module-page-relative `href`s. A module page is `module/<dotted-fqn>.html` (`::` → `.`); a node is the `#<slug>` anchor within it. Only an FQN naming a real -node produces a link (LANG.md §9.3).

Parent

Inbound

Outbound

  • from doc::Result
  • from doc::Result

Scenarios

CrossLinksResolve

Cross-links resolve to anchors; unresolved references stay plain text.

  • given a relationship endpoint named by FQN
  • when the link is rendered
  • then an FQN naming a real node becomes an anchor link to its section
  • but an unresolved or synthesised endpoint renders as plain text
Flow Flow — CrossLinksResolve scroll to zoom · drag to pan
FEATURECrossLinksResolvefor UrlsGIVENa relationship endpoint named by FQNWHENthe link is renderedTHENan FQN naming a real node becomes ananchor link to its sectionBUTan unresolved or synthesised endpointrenders as plain text
Generated by pds doc.
+node produces a link (LANG.md §9.3).

Parent

Inbound

Outbound

  • from doc::Result

Scenarios

CrossLinksResolve

Cross-links resolve to anchors; unresolved references stay plain text.

  • given a relationship endpoint named by FQN
  • when the link is rendered
  • then an FQN naming a real node becomes an anchor link to its section
  • but an unresolved or synthesised endpoint renders as plain text
Flow Flow — CrossLinksResolve scroll to zoom · drag to pan
FEATURECrossLinksResolvefor UrlsGIVENa relationship endpoint named by FQNWHENthe link is renderedTHENan FQN naming a real node becomes ananchor link to its sectionBUTan unresolved or synthesised endpointrenders as plain text
Generated by pds doc.
diff --git a/model/site/module/dot.html b/model/site/module/dot.html index 024bc476..47951380 100644 --- a/model/site/module/dot.html +++ b/model/site/module/dot.html @@ -11,7 +11,7 @@ -
PseudoScript
Module

dot

data

Box2 #

private
dot::Box2

An axis-aligned box: top-left corner and extent.

Entities Entity diagram scroll to zoom · drag to pan
RECORDBox2xnumberynumberwnumberhnumber
data

Cluster #

private
dot::Cluster

A cluster (a `subgraph cluster_*`): its id, optional parent cluster, member +

PseudoScript
Module

dot

data

Box2 #

private
dot::Box2

An axis-aligned box: top-left corner and extent.

Entities Entity diagram scroll to zoom · drag to pan
RECORDBox2xnumberynumberwnumberhnumber
data

Cluster #

private
dot::Cluster

A cluster (a `subgraph cluster_*`): its id, optional parent cluster, member node ids, the margin kept outside its members, and the header band reserved on the title side.

Entities Entity diagram scroll to zoom · drag to pan
RECORDClusteridstringparentstringmembersstring[]marginnumberheadernumber
data

ClusterBox #

private
dot::ClusterBox

A laid-out cluster: its id and bounding box.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDClusterBoxidstringbboxdot::Box2RECORDBox2xnumberynumberwnumberhnumber
component

Clusters #

public
dot::Clusters

Cluster framing: compute each cluster's bounding box bottom-up so a nested cluster's box encloses its children (grown by their margins) and its direct @@ -23,7 +23,7 @@ `tail` and `head`, plus where its label sits.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDEdgeRoutetailstringheadstringsplinedot::Pt[]polylinedot::Pt[]labelPosdot::PtRECORDPtxnumberynumber
component

Engine #

private
dot::Engine

The layered engine: runs the faithful four-pass `dot` pipeline — rank, order, position, splines — then frames the clusters and shifts the whole layout to the origin. Deterministic and panic-free.

Parent

Inbound

Outbound

Scenarios

FaithfulFourPassPipeline

The pipeline is a faithful four-pass port, run in order.

  • given an input graph of sized nodes, edges, and clusters
  • when the graph is laid out
  • then ranks are assigned, nodes ordered within ranks, x-coordinates placed, and edges routed as splines
  • and the passes run in dot's order: rank, then order, then position, then splines
Flow Flow — FaithfulFourPassPipeline scroll to zoom · drag to pan
FEATUREFaithfulFourPassPipelinefor EngineGIVENan input graph of sized nodes, edges,and clustersWHENthe graph is laid outTHENranks are assigned, nodes orderedwithin ranks, x-coordinates placed,and edges routed as splinesANDthe passes run in dot's order: rank,then order, then position, thensplines
DeterministicAndPanicFree

Layout is deterministic and never panics.

  • given the same input graph laid out twice
  • when the layouts are compared
  • then the placements are identical
  • and an empty graph yields an empty layout
  • but no input — however extreme its rank spans — panics or hangs
Flow Flow — DeterministicAndPanicFree scroll to zoom · drag to pan
FEATUREDeterministicAndPanicFreefor EngineGIVENthe same input graph laid out twiceWHENthe layouts are comparedTHENthe placements are identicalANDan empty graph yields an empty layoutBUTno input — however extreme its rankspans — panics or hangs
data

Graph #

public
dot::Graph

The input graph the engine lays out: nodes, edges, clusters, the rank and node -separations, the rank direction, and any same-rank constraints.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDGraphnodesdot::Node[]edgesdot::Edge[]clustersdot::Cluster[]ranksepnumbernodesepnumberrankdirdot::RankDirsameRankstring[]RECORDNodeidstringwidthnumberheightnumberRECORDEdgetailstringheadstringminlennumberweightnumberlabeldot::SizeRECORDClusteridstringparentstringmembersstring[]marginnumberheadernumberUNIONRankDirTopBottomLeftRight
component

Grid #

public
dot::Grid

The experimental grid placer: an alternative to the layered pipeline that +separations, the rank direction, and any same-rank constraints.

Entities Entity diagram scroll to zoom · drag to pan
RECORDGraphnodesdot::Node[]edgesdot::Edge[]clustersdot::Cluster[]ranksepnumbernodesepnumberrankdirdot::RankDirsameRankstring[]RECORDNodeidstringwidthnumberheightnumberRECORDEdgetailstringheadstringminlennumberweightnumberlabeldot::SizeRECORDClusteridstringparentstringmembersstring[]marginnumberheadernumberUNIONRankDirTopBottomLeftRight
component

Grid #

public
dot::Grid

The experimental grid placer: an alternative to the layered pipeline that snaps nodes to a grid, honouring drag-to-pin cells and searching for a placement that minimises crossings, distance, and against-flow edges.

Parent

Inbound

Scenarios

GridPlacerHonoursPins

The grid placer honours pinned cells while it searches.

  • given a graph and a set of drag-to-pin cells
  • when the experimental grid placer runs
  • then each pinned node stays in its cell
  • and the remaining nodes are placed to minimise crossings, distance, and against-flow edges
Flow Flow — GridPlacerHonoursPins scroll to zoom · drag to pan
FEATUREGridPlacerHonoursPinsfor GridGIVENa graph and a set of drag-to-pin cellsWHENthe experimental grid placer runsTHENeach pinned node stays in its cellANDthe remaining nodes are placed tominimise crossings, distance, andagainst-flow edges
data

GridMeta #

private
dot::GridMeta

Grid metadata carried back when the experimental grid placer ran: the grid dimensions, the cell pitch (centre-to-centre), the pixel centre of cell (0,0), @@ -36,7 +36,7 @@ metadata.

Entities Entity diagram scroll to zoom · drag to pan
RECORDLayoutbboxdot::Box2nodesdot::NodePos[]edgesdot::EdgeRoute[]clustersdot::ClusterBox[]gridOption<dot::GridMeta>RECORDBox2xnumberynumberwnumberhnumberRECORDNodePosidstringcenterdot::PtwidthnumberheightnumberRECORDEdgeRoutetailstringheadstringsplinedot::Pt[]polylinedot::Pt[]labelPosdot::PtRECORDClusterBoxidstringbboxdot::Box2
data

LayoutState #

public
dot::LayoutState

The structure that flows through a pipeline: the (possibly pass-mutated) input graph and its current placement. A pass receives one, returns one, and the framework hands it to the next pass; a re-ranking pass may edit `graph` -and re-run the base layout.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDLayoutStategraphdot::Graphlayoutdot::LayoutRECORDGraphnodesdot::Node[]edgesdot::Edge[]clustersdot::Cluster[]ranksepnumbernodesepnumberrankdirdot::RankDirsameRankstring[]RECORDLayoutbboxdot::Box2nodesdot::NodePos[]edgesdot::EdgeRoute[]clustersdot::ClusterBox[]gridOption<dot::GridMeta>
data

Node #

private
dot::Node

An input node: its id and its rendered size (the placer reserves this box).

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeidstringwidthnumberheightnumber
data

NodePos #

private
dot::NodePos

A placed node: its id, centre point, and reserved size.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodePosidstringcenterdot::PtwidthnumberheightnumberRECORDPtxnumberynumber
component

Optimize #

public
dot::Optimize

The `ShortenLongEdges` pass: a bounded, deterministic post-layout refinement. +and re-run the base layout.

Entities Entity diagram scroll to zoom · drag to pan
RECORDLayoutStategraphdot::Graphlayoutdot::LayoutRECORDGraphnodesdot::Node[]edgesdot::Edge[]clustersdot::Cluster[]ranksepnumbernodesepnumberrankdirdot::RankDirsameRankstring[]RECORDLayoutbboxdot::Box2nodesdot::NodePos[]edgesdot::EdgeRoute[]clustersdot::ClusterBox[]gridOption<dot::GridMeta>
data

Node #

private
dot::Node

An input node: its id and its rendered size (the placer reserves this box).

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeidstringwidthnumberheightnumber
data

NodePos #

private
dot::NodePos

A placed node: its id, centre point, and reserved size.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodePosidstringcenterdot::PtwidthnumberheightnumberRECORDPtxnumberynumber
component

Optimize #

public
dot::Optimize

The `ShortenLongEdges` pass: a bounded, deterministic post-layout refinement. Greedily search for same-rank moves that reduce the sum of squared edge lengths (never merging across a cluster boundary), then re-lay-out with the improving moves applied.

Parent

component

Oracle #

private
dot::Oracle

The test oracle: lays the same graph out with the real `dot` binary and @@ -50,7 +50,7 @@ caller resolves the FQN) at a row and column. The IDE's drag-to-pin.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPinnodenumberrownumbercolnumber
component

Pipeline #

public
dot::Pipeline

The composable post-layout pipeline: the base layout produces the starting `LayoutState`; each `Pass` folds it into the next. A pass adjusts geometry directly or edits the input graph (e.g. adds same-rank hints) and re-runs the -base layout.

Parent

Inbound

Outbound

component

Position #

public
dot::Position

Pass 3 — positioning: assign x-coordinates by network simplex on an auxiliary +base layout.

Parent

Inbound

component

Position #

public
dot::Position

Pass 3 — positioning: assign x-coordinates by network simplex on an auxiliary graph (aligning nodes and straightening long edges), and y-coordinates per rank stacked with `ranksep` plus any label and cluster-header room.

Parent

Inbound

Outbound

data

Pt #

private
dot::Pt

A point in layout coordinates.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPtxnumberynumber
component

Rank #

public
dot::Rank

Pass 1 — rank assignment: give each node an integer rank by network simplex, banding each cluster onto contiguous ranks and honouring same-rank constraints.

Parent

Inbound

Outbound

  • from dot::string
data

RankDir #

private
dot::RankDir

The rank direction: top-to-bottom (the C4 default) or left-to-right.

Entities Entity diagram scroll to zoom · drag to pan
UNIONRankDirTopBottomLeftRight
data

SearchMode #

private
dot::SearchMode

How the experimental grid placer searches for a placement: pick automatically diff --git a/model/site/module/emit.html b/model/site/module/emit.html index 300401eb..ca74cbf3 100644 --- a/model/site/module/emit.html +++ b/model/site/module/emit.html @@ -11,7 +11,7 @@ -

PseudoScript
Module

emit

data

BoundaryFrame #

private
emit::BoundaryFrame

An enclosing frame of a boundary view: the boundary node's FQN (so the canvas +

PseudoScript
Module

emit

data

BoundaryFrame #

private
emit::BoundaryFrame

An enclosing frame of a boundary view: the boundary node's FQN (so the canvas can drill into it), title, C4 kind (frame accent), and the cluster rectangle.

Entities Entity diagram scroll to zoom · drag to pan
RECORDBoundaryFramefqnstringtitlestringkindstringrectemit::RectRECORDRectxnumberynumberwnumberhnumber
data

C4EdgeKind #

private
emit::C4EdgeKind

The relationship a routed edge expresses: a body `call`, a synthesised `trigger` from an initiator, or a `from` `provenance` link. Maps from `model::EdgeKind`, dropping the structural `for`-parent link.

Entities Entity diagram scroll to zoom · drag to pan
UNIONC4EdgeKindCallTriggerProvenanceRECORDCallmessagestringUNIONTriggerOnEventScheduleHttpManual
data

C4Layout #

public
emit::C4Layout

A dot-placed C4 view for the interactive canvas and the static SVG: canvas @@ -25,7 +25,7 @@ referencing and referenced FQNs and the field driving the reference.

Entities Entity diagram scroll to zoom · drag to pan
RECORDDataLinksourcestringtostringfieldstring
data

DataScene #

public
emit::DataScene
Entities Entity diagram scroll to zoom · drag to pan
RECORDDataSceneofstringentitiesemit::DataEntity[]linksemit::DataLink[]widthnumberheightnumberRECORDDataEntityfqnstringlabelstringformemit::EntityFormrowsemit::EntityRow[]focalboolrectemit::RectRECORDDataLinksourcestringtostringfieldstring
component

Edges #

private
emit::Edges

Builds the routed-edge set for a C4 scene from the graph's edges: maps each `model::EdgeKind` to a `C4EdgeKind` (dropping `for`-parent links), lifts each endpoint to the in-view node it belongs to, drops self-loops and -out-of-view edges, then sorts and de-duplicates.

Parent

Inbound

Outbound

container

Emit #

public
emit::Emit

`crates/pseudoscript-emit`. Projects a view into a `Scene` and renders it to +out-of-view edges, then sorts and de-duplicates.

Parent

Inbound

Outbound

container

Emit #

public
emit::Emit

`crates/pseudoscript-emit`. Projects a view into a `Scene` and renders it to SVG; C4 views are placed by the `dot` layered engine, the sequence/data/feature views by the matching `layout` engine.

#headline

Inbound

Outbound

Scenarios

ProjectAView

A view projects to a notation-neutral scene, then to SVG.

  • given a resolved graph and a chosen view
  • when the view is projected
  • then the relevant nodes and edges are selected and laid out into a Scene
  • and the Scene renders to standalone SVG
  • but a missing or wrong-kind target fails with an EmitError
Flow Flow — ProjectAView scroll to zoom · drag to pan
FEATUREProjectAViewfor EmitGIVENa resolved graph and a chosen viewWHENthe view is projectedTHENthe relevant nodes and edges areselected and laid out into a SceneANDthe Scene renders to standalone SVGBUTa missing or wrong-kind target failswith an EmitError
SceneIsDeterministic

The Scene IR is the conformance surface: notation-neutral and deterministic.

  • given the same resolved graph and view projected twice
  • when the scenes are compared
  • then node order is source-declaration order
  • and parallel edges merge into one multi-label edge, sorted by from, to, then kind
  • but renderer coordinates never appear in the golden
Flow Flow — SceneIsDeterministic scroll to zoom · drag to pan
FEATURESceneIsDeterministicfor EmitGIVENthe same resolved graph and viewprojected twiceWHENthe scenes are comparedTHENnode order is source-declaration orderANDparallel edges merge into onemulti-label edge, sorted by from, to,then kindBUTrenderer coordinates never appear inthe golden
Components Component diagram scroll to zoom · drag to pan
PseudoscriptEmitCOMPONENTProjectorProjects a `View` out of the graph (LANG.md§9): dispatches on the view variant,…COMPONENTEdgesBuilds the routed-edge set for a C4 scenefrom the graph's edges: maps each…COMPONENTTraceWalks a callable's body trace into orderedsequence items, registering each participan…COMPONENTLayoutAssigns geometry. Projection stamps each C4scene with simple row-flow coordinates…COMPONENTSvgRendererRenders a laid-out `Scene` to standalone SVGmarkup — string-building, no template engin…CONTAINERDoc`crates/pseudoscript-doc`. Turns a resolvedgraph into a Svelte-rendered,…CONTAINERDot`crates/pseudoscript-dot`. Runs thefour-pass layered pipeline over a `Graph` t…CONTAINERLayout`crates/pseudoscript-layout`. Thegeometry/text `Core` and the sequence…CONTAINERUniverse`crates/pseudoscript-universe`. Maps theresolved C4 model into the software graph…flowsfromModelsnapshotgridLayoutrunPipelinelayoutcollectsolveC4blackBoxwalk
data

EmitError #

public
emit::EmitError

Why projecting a view failed: the target FQN names no node, or names a node of the wrong kind for the requested view.

Entities Entity diagram scroll to zoom · drag to pan
UNIONEmitErrorUnknownNodeWrongKindRECORDUnknownNodefqnstringRECORDWrongKindfqnstringexpectedstringfoundstring
data

EntityForm #

private
emit::EntityForm

The disclosed form of a data entity, driving the card's eyebrow and rows: a @@ -50,35 +50,36 @@ carries. The real placement runs at render/canvas time: `layoutC4` drives the `dot` layered engine (rank/order/position/splines), framing boundary children as a cluster, tuned by per-diagram `C4Tweaks` and drag-to-pin cells. The -sequence/data/feature scenes are placed by the matching `layout` engine.

Parent

Inbound

Outbound

Scenarios

LayoutDelegatesToDot

C4 layout is delegated to the `dot` layered engine.

  • given a projected C4 scene
  • when the scene is laid out
  • then it is translated into a dot input graph and placed by the dot layered engine
  • and a boundary view's children are framed as a dot cluster
  • but the engine tolerates cycles and never panics, so the placement stays deterministic
Flow Flow — LayoutDelegatesToDot scroll to zoom · drag to pan
FEATURELayoutDelegatesToDotfor LayoutGIVENa projected C4 sceneWHENthe scene is laid outTHENit is translated into a dot inputgraph and placed by the dot layeredengineANDa boundary view's children are framedas a dot clusterBUTthe engine tolerates cycles and neverpanics, so the placement staysdeterministic
TweaksAndPinsTuneC4Layout

Per-diagram tweaks and pins tune the C4 placement.

  • given a C4 scene and the UI's layout tweaks
  • when the scene is laid out with those tweaks
  • then the long-edge optimiser, reading direction, and spacing follow the tweaks
  • and under experimental grid placement, drag-to-pin cells fix their nodes
Flow Flow — TweaksAndPinsTuneC4Layout scroll to zoom · drag to pan
FEATURETweaksAndPinsTuneC4Layoutfor LayoutGIVENa C4 scene and the UI's layout tweaksWHENthe scene is laid out with thosetweaksTHENthe long-edge optimiser, readingdirection, and spacing follow thetweaksANDunder experimental grid placement,drag-to-pin cells fix their nodes
data

Lifeline #

private
emit::Lifeline

A sequence lifeline (a participant): its FQN, C4 kind for the head card, the +sequence/data/feature scenes are placed by the matching `layout` engine.

Parent

Inbound

Outbound

Scenarios

LayoutDelegatesToDot

C4 layout is delegated to the `dot` layered engine.

  • given a projected C4 scene
  • when the scene is laid out
  • then it is translated into a dot input graph and placed by the dot layered engine
  • and a boundary view's children are framed as a dot cluster
  • but the engine tolerates cycles and never panics, so the placement stays deterministic
Flow Flow — LayoutDelegatesToDot scroll to zoom · drag to pan
FEATURELayoutDelegatesToDotfor LayoutGIVENa projected C4 sceneWHENthe scene is laid outTHENit is translated into a dot inputgraph and placed by the dot layeredengineANDa boundary view's children are framedas a dot clusterBUTthe engine tolerates cycles and neverpanics, so the placement staysdeterministic
TweaksAndPinsTuneC4Layout

Per-diagram tweaks and pins tune the C4 placement.

  • given a C4 scene and the UI's layout tweaks
  • when the scene is laid out with those tweaks
  • then the long-edge optimiser, reading direction, and spacing follow the tweaks
  • and under experimental grid placement, drag-to-pin cells fix their nodes
Flow Flow — TweaksAndPinsTuneC4Layout scroll to zoom · drag to pan
FEATURETweaksAndPinsTuneC4Layoutfor LayoutGIVENa C4 scene and the UI's layout tweaksWHENthe scene is laid out with thosetweaksTHENthe long-edge optimiser, readingdirection, and spacing follow thetweaksANDunder experimental grid placement,drag-to-pin cells fix their nodes
data

Lifeline #

private
emit::Lifeline

A sequence lifeline (a participant): its FQN, C4 kind for the head card, the node's `///` summary shown dimmed under the name, and the structural ancestry path under a container/component name. No coordinate — the sequence layout -assigns the lifeline x.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDLifelinefqnstringkindstringsummarystringparentPathstring
data

Message #

private
emit::Message

A message between two lifelines (or a self-message). `label` is the method +assigns the lifeline x.

Entities Entity diagram scroll to zoom · drag to pan
RECORDLifelinefqnstringkindstringsummarystringparentPathstring
data

Message #

private
emit::Message

A message between two lifelines (or a self-message). `label` is the method name, or `Ok`/`Err`/empty for a return; `detail` is the type detail shown after it — a call's `(params): ret` signature or a return's concrete type.

Entities Entity diagram scroll to zoom · drag to pan
RECORDMessagesourcestringtostringkindemit::MessageKindlabelstringdetailstringUNIONMessageKindCallMsgReturnMsgSelfMsg
data

MessageItem #

private
emit::MessageItem
Entities Entity diagram scroll to zoom · drag to pan
RECORDMessageItemmessageemit::MessageRECORDMessagesourcestringtostringkindemit::MessageKindlabelstringdetailstring
data

MessageKind #

private
emit::MessageKind

The kind of a sequence message: a `call` to another lifeline, a `return` to the caller, or a `self`-message on the owner's own lifeline.

Entities Entity diagram scroll to zoom · drag to pan
UNIONMessageKindCallMsgReturnMsgSelfMsg
data

PlacedNode #

private
emit::PlacedNode

A node placed in a C4 view: its FQN, C4 kind, display label, optional `///` summary (the card's dimmed description), the boundary it sits inside, and the -layout rectangle assigned by the placer.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPlacedNodefqnstringkindstringlabelstringsummarystringboundarystringrectemit::RectRECORDRectxnumberynumberwnumberhnumber
data

PointI #

private
emit::PointI

An integer point on the canvas (a rounded layout coordinate).

Entities Entity diagram scroll to zoom · drag to pan
RECORDPointIxnumberynumber
component

Projector #

private
emit::Projector

Projects a `View` out of the graph (LANG.md §9): dispatches on the view +layout rectangle assigned by the placer.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPlacedNodefqnstringkindstringlabelstringsummarystringboundarystringrectemit::RectRECORDRectxnumberynumberwnumberhnumber
data

PointI #

private
emit::PointI

An integer point on the canvas (a rounded layout coordinate).

Entities Entity diagram scroll to zoom · drag to pan
RECORDPointIxnumberynumber
component

Projector #

private
emit::Projector

Projects a `View` out of the graph (LANG.md §9): dispatches on the view variant, collecting nodes and edges for a C4 scene or tracing a callable body for a sequence scene, then hands off to the placer. Fails with `EmitError` -when the view's target is missing or the wrong kind.

Parent

Inbound

Outbound

Scenarios

DispatchOnViewKind

Each view kind dispatches to its projection.

  • given a graph and a View variant
  • when project runs
  • then Context and the boundary views build a C4Scene
  • and Sequence traces the entry callable's body into a SequenceScene
  • and Data builds an entity (ER) scene and Feature builds a flow scene
Flow Flow — DispatchOnViewKind scroll to zoom · drag to pan
FEATUREDispatchOnViewKindfor ProjectorGIVENa graph and a View variantWHENproject runsTHENContext and the boundary views build aC4SceneANDSequence traces the entry callable'sbody into a SequenceSceneANDData builds an entity (ER) scene andFeature builds a flow scene
ProjectSymbolPicksAView

Project-symbol picks the view that best explains one symbol.

  • given a single symbol FQN — a callable, system, container, component, or data type
  • when the symbol is projected
  • then a callable traces as a sequence, a system or container with children shows them, a data type shows its entity view
  • and a childless boundary falls back to the view that frames it as a peer
  • and an FQN naming no graph node projects its feature flow view
  • but an FQN naming neither a node nor a feature fails with EmitError::UnknownNode
Flow Flow — ProjectSymbolPicksAView scroll to zoom · drag to pan
FEATUREProjectSymbolPicksAViewfor ProjectorGIVENa single symbol FQN — a callable,system, container, component, or datatypeWHENthe symbol is projectedTHENa callable traces as a sequence, asystem or container with childrenshows them, a data type shows itsentity viewANDa childless boundary falls back to theview that frames it as a peerANDan FQN naming no graph node projectsits feature flow viewBUTan FQN naming neither a node nor afeature fails withEmitError::UnknownNode
DataAndFeatureViews

The data and feature views project their own scene families.

  • given a Data view of a data type, or a Feature view of a scenario
  • when the view is projected
  • then the Data view collects the focal entity, its referenced types, and the links between them
  • and the Feature view lays the scenario's given/when/then steps out as a flow
Flow Flow — DataAndFeatureViews scroll to zoom · drag to pan
FEATUREDataAndFeatureViewsfor ProjectorGIVENa Data view of a data type, or aFeature view of a scenarioWHENthe view is projectedTHENthe Data view collects the focalentity, its referenced types, and thelinks between themANDthe Feature view lays the scenario'sgiven/when/then steps out as a flow
UnknownNodeFails

A missing node fails the projection.

  • given a boundary or sequence view whose target FQN names no node
  • when the view is projected
  • then projection fails with EmitError::UnknownNode
Flow Flow — UnknownNodeFails scroll to zoom · drag to pan
FEATUREUnknownNodeFailsfor ProjectorGIVENa boundary or sequence view whosetarget FQN names no nodeWHENthe view is projectedTHENprojection fails withEmitError::UnknownNode
WrongKindFails

A target of the wrong kind fails the projection.

  • given a container view whose of node is not a system
  • when the view is projected
  • then projection fails with EmitError::WrongKind carrying expected and found
Flow Flow — WrongKindFails scroll to zoom · drag to pan
FEATUREWrongKindFailsfor ProjectorGIVENa container view whose of node is nota systemWHENthe view is projectedTHENprojection fails withEmitError::WrongKind carrying expectedand found
BlackBoxEntryTracesSignature

A black-box sequence entry still traces — its signature is the trace.

  • given a sequence view whose entry callable is black-boxed (no disclosed body)
  • when the view is projected
  • then the trace is the single inbound call and its return the signature describes
  • but a non-callable entry still fails with EmitError::WrongKind
Flow Flow — BlackBoxEntryTracesSignature scroll to zoom · drag to pan
FEATUREBlackBoxEntryTracesSignaturefor ProjectorGIVENa sequence view whose entry callableis black-boxed (no disclosed body)WHENthe view is projectedTHENthe trace is the single inbound calland its return the signature describesBUTa non-callable entry still fails withEmitError::WrongKind
data

Rect #

private
emit::Rect

An axis-aligned layout rectangle in renderer coordinates.

Entities Entity diagram scroll to zoom · drag to pan
RECORDRectxnumberynumberwnumberhnumber
data

RoutedEdge #

private
emit::RoutedEdge

An edge routed between two C4 nodes: its endpoints, kind, and the merged call +when the view's target is missing or the wrong kind.

Parent

Inbound

Outbound

Scenarios

DispatchOnViewKind

Each view kind dispatches to its projection.

  • given a graph and a View variant
  • when project runs
  • then Context and the boundary views build a C4Scene
  • and Sequence traces the entry callable's body into a SequenceScene
  • and Data builds an entity (ER) scene and Feature builds a flow scene
Flow Flow — DispatchOnViewKind scroll to zoom · drag to pan
FEATUREDispatchOnViewKindfor ProjectorGIVENa graph and a View variantWHENproject runsTHENContext and the boundary views build aC4SceneANDSequence traces the entry callable'sbody into a SequenceSceneANDData builds an entity (ER) scene andFeature builds a flow scene
ProjectSymbolPicksAView

Project-symbol picks the view that best explains one symbol.

  • given a single symbol FQN — a callable, system, container, component, or data type
  • when the symbol is projected
  • then a callable traces as a sequence, a system or container with children shows them, a data type shows its entity view
  • and a childless boundary falls back to the view that frames it as a peer
  • and an FQN naming no graph node projects its feature flow view
  • but an FQN naming neither a node nor a feature fails with EmitError::UnknownNode
Flow Flow — ProjectSymbolPicksAView scroll to zoom · drag to pan
FEATUREProjectSymbolPicksAViewfor ProjectorGIVENa single symbol FQN — a callable,system, container, component, or datatypeWHENthe symbol is projectedTHENa callable traces as a sequence, asystem or container with childrenshows them, a data type shows itsentity viewANDa childless boundary falls back to theview that frames it as a peerANDan FQN naming no graph node projectsits feature flow viewBUTan FQN naming neither a node nor afeature fails withEmitError::UnknownNode
DataAndFeatureViews

The data and feature views project their own scene families.

  • given a Data view of a data type, or a Feature view of a scenario
  • when the view is projected
  • then the Data view collects the focal entity, its referenced types, and the links between them
  • and the Feature view lays the scenario's given/when/then steps out as a flow
Flow Flow — DataAndFeatureViews scroll to zoom · drag to pan
FEATUREDataAndFeatureViewsfor ProjectorGIVENa Data view of a data type, or aFeature view of a scenarioWHENthe view is projectedTHENthe Data view collects the focalentity, its referenced types, and thelinks between themANDthe Feature view lays the scenario'sgiven/when/then steps out as a flow
UnknownNodeFails

A missing node fails the projection.

  • given a boundary or sequence view whose target FQN names no node
  • when the view is projected
  • then projection fails with EmitError::UnknownNode
Flow Flow — UnknownNodeFails scroll to zoom · drag to pan
FEATUREUnknownNodeFailsfor ProjectorGIVENa boundary or sequence view whosetarget FQN names no nodeWHENthe view is projectedTHENprojection fails withEmitError::UnknownNode
WrongKindFails

A target of the wrong kind fails the projection.

  • given a container view whose of node is not a system
  • when the view is projected
  • then projection fails with EmitError::WrongKind carrying expected and found
Flow Flow — WrongKindFails scroll to zoom · drag to pan
FEATUREWrongKindFailsfor ProjectorGIVENa container view whose of node is nota systemWHENthe view is projectedTHENprojection fails withEmitError::WrongKind carrying expectedand found
BlackBoxEntryTracesSignature

A black-box sequence entry still traces — its signature is the trace.

  • given a sequence view whose entry callable is black-boxed (no disclosed body)
  • when the view is projected
  • then the trace is the single inbound call and its return the signature describes
  • but a non-callable entry still fails with EmitError::WrongKind
Flow Flow — BlackBoxEntryTracesSignature scroll to zoom · drag to pan
FEATUREBlackBoxEntryTracesSignaturefor ProjectorGIVENa sequence view whose entry callableis black-boxed (no disclosed body)WHENthe view is projectedTHENthe trace is the single inbound calland its return the signature describesBUTa non-callable entry still fails withEmitError::WrongKind
data

Rect #

private
emit::Rect

An axis-aligned layout rectangle in renderer coordinates.

Entities Entity diagram scroll to zoom · drag to pan
RECORDRectxnumberynumberwnumberhnumber
data

RoutedEdge #

private
emit::RoutedEdge

An edge routed between two C4 nodes: its endpoints, kind, and the merged call labels (sorted and de-duplicated; empty for a trigger or provenance edge). Parallel calls between the same pair collapse into one multi-label edge. `source` is the wire field `from`, renamed because `from` is a PseudoScript -keyword.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDRoutedEdgesourcestringtostringkindemit::C4EdgeKindlabelsstring[]UNIONC4EdgeKindCallTriggerProvenance
data

Scene #

public
emit::Scene

A laid-out diagram: the notation-neutral IR the SVG renderer consumes. A C4 +keyword.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDRoutedEdgesourcestringtostringkindemit::C4EdgeKindlabelsstring[]UNIONC4EdgeKindCallTriggerProvenance
data

Scene #

public
emit::Scene

A laid-out diagram: the notation-neutral IR the SVG renderer consumes. A C4 scene carries placed nodes and routed edges; a sequence scene carries lifelines and ordered messages with nested frames. This IR is the generation conformance surface (ADR-017): coordinates are carried for the renderer only, -never serialised to the golden.

Entities Entity diagram scroll to zoom · drag to pan
UNIONSceneC4SceneSequenceSceneDataSceneFeatureSceneRECORDC4Sceneviewemit::C4ViewofOption<string>nodesemit::PlacedNode[]edgesemit::RoutedEdge[]RECORDSequenceSceneentrystringparticipantsemit::Lifeline[]itemsemit::SeqItem[]RECORDDataSceneofstringentitiesemit::DataEntity[]linksemit::DataLink[]widthnumberheightnumberRECORDFeatureSceneentrystringtargetFqnstringtargetLabelstringnamestringstepsemit::FeatureStepNode[]widthnumberheightnumber
data

SeqItem #

private
emit::SeqItem

One ordered item in a sequence trace: a message or a nested frame.

Entities Entity diagram scroll to zoom · drag to pan
UNIONSeqItemMessageItemFrameItemRECORDMessageItemmessageemit::MessageRECORDFrameItemframeemit::Frame
data

Sequence #

public
emit::Sequence
Entities Entity diagram scroll to zoom · drag to pan
RECORDSequenceentrystring
data

SequenceScene #

public
emit::SequenceScene
Entities Entity diagram scroll to zoom · drag to pan
RECORDSequenceSceneentrystringparticipantsemit::Lifeline[]itemsemit::SeqItem[]RECORDLifelinefqnstringkindstringsummarystringparentPathstringUNIONSeqItemMessageItemFrameItem
component

SvgRenderer #

private
emit::SvgRenderer

Renders a laid-out `Scene` to standalone SVG markup — string-building, no +never serialised to the golden.

Entities Entity diagram scroll to zoom · drag to pan
UNIONSceneC4SceneSequenceSceneDataSceneFeatureSceneRECORDC4Sceneviewemit::C4ViewofOption<string>nodesemit::PlacedNode[]edgesemit::RoutedEdge[]RECORDSequenceSceneentrystringparticipantsemit::Lifeline[]itemsemit::SeqItem[]RECORDDataSceneofstringentitiesemit::DataEntity[]linksemit::DataLink[]widthnumberheightnumberRECORDFeatureSceneentrystringtargetFqnstringtargetLabelstringnamestringstepsemit::FeatureStepNode[]widthnumberheightnumber
data

SeqItem #

private
emit::SeqItem

One ordered item in a sequence trace: a message or a nested frame.

Entities Entity diagram scroll to zoom · drag to pan
UNIONSeqItemMessageItemFrameItemRECORDMessageItemmessageemit::MessageRECORDFrameItemframeemit::Frame
data

Sequence #

public
emit::Sequence
Entities Entity diagram scroll to zoom · drag to pan
RECORDSequenceentrystring
data

SequenceScene #

public
emit::SequenceScene
Entities Entity diagram scroll to zoom · drag to pan
RECORDSequenceSceneentrystringparticipantsemit::Lifeline[]itemsemit::SeqItem[]RECORDLifelinefqnstringkindstringsummarystringparentPathstringUNIONSeqItemMessageItemFrameItem
component

SvgRenderer #

private
emit::SvgRenderer

Renders a laid-out `Scene` to standalone SVG markup — string-building, no template engine and no headless browser (LANG.md §9.3, ADR-017). Dispatches on the scene variant; the layout engine is wrapped so no input panics the render, falling back to the scene's own simple coordinates.

Parent

Inbound

Scenarios

RenderSvgAsString

SVG is built as a string — no headless browser.

  • given a laid-out Scene
  • when render runs
  • then a self-contained SVG document is built by string concatenation
  • but no template engine or headless browser is invoked
Flow Flow — RenderSvgAsString scroll to zoom · drag to pan
FEATURERenderSvgAsStringfor SvgRendererGIVENa laid-out SceneWHENrender runsTHENa self-contained SVG document is builtby string concatenationBUTno template engine or headless browseris invoked
AdaptiveSvgUsesCssVariables

The adaptive theme defers colour choice to the host stylesheet.

  • given a scene rendered under the adaptive theme
  • when the SVG is emitted
  • then every palette role paints as a --pds-* CSS variable with the light value as fallback
  • and the light and dark outputs stay byte-identical to their literal palettes
Flow Flow — AdaptiveSvgUsesCssVariables scroll to zoom · drag to pan
FEATUREAdaptiveSvgUsesCssVariablesfor SvgRendererGIVENa scene rendered under the adaptivethemeWHENthe SVG is emittedTHENevery palette role paints as a --pds-*CSS variable with the light value asfallbackANDthe light and dark outputs staybyte-identical to their literalpalettes
LayoutFallbackIsPanicSafe

A failed layout degrades to simple coordinates rather than crashing.

  • given a scene the layout engine cannot place, or an empty view
  • when the C4 scene is rendered
  • then rendering falls back to the scene's own simple coordinates
  • but the render never panics
Flow Flow — LayoutFallbackIsPanicSafe scroll to zoom · drag to pan
FEATURELayoutFallbackIsPanicSafefor SvgRendererGIVENa scene the layout engine cannotplace, or an empty viewWHENthe C4 scene is renderedTHENrendering falls back to the scene'sown simple coordinatesBUTthe render never panics
component

Trace #

private
emit::Trace

Walks a callable's body trace into ordered sequence items, registering each participant on first appearance (LANG.md §9.2, §7). A real trigger's initiator leads as the first lifeline with a synthesised inbound call; absent a trigger the owner leads. A `call` is a message to the target; a -disclosed callee expands inline; a `self.` call is a self-message; a return +disclosed callee expands inline; a same-node call is a self-message that +expands its disclosed body inline too; a return marks `Ok`/`Err`; `if`/`else` nests an `alt` frame, `for`/`while` a `loop` -frame.

Parent

Inbound

Outbound

Scenarios

ControlFlowBecomesFrames

Control flow becomes nested frames in the trace.

  • given a callable body with if/else and for/while
  • when the body is traced
  • then an if/else becomes an alt frame over its branches
  • and a for/while becomes a loop frame over its body
Flow Flow — ControlFlowBecomesFrames scroll to zoom · drag to pan
FEATUREControlFlowBecomesFramesfor TraceGIVENa callable body with if/else andfor/whileWHENthe body is tracedTHENan if/else becomes an alt frame overits branchesANDa for/while becomes a loop frame overits body
CallsBecomeMessages

Body calls become left-to-right messages; self-calls loop on the owner.

  • given a callable body with calls to other nodes and self-calls
  • when the body is traced
  • then each call to another node is a left-to-right message to that lifeline
  • and each self. call is a self-message on the owner's own lifeline
  • and every target is registered as a participant on first appearance
Flow Flow — CallsBecomeMessages scroll to zoom · drag to pan
FEATURECallsBecomeMessagesfor TraceGIVENa callable body with calls to othernodes and self-callsWHENthe body is tracedTHENeach call to another node is aleft-to-right message to that lifelineANDeach self. call is a self-message onthe owner's own lifelineANDevery target is registered as aparticipant on first appearance
DisclosedCalleesExpandInline

Disclosed callees expand inline; black boxes and recursion stay single calls.

  • given a traced body whose calls reach disclosed, black-box, and recursive callees
  • when each call is traced
  • then a disclosed callee's body expands inline, the active lifeline switching to it
  • and its own returns flow back to its caller
  • but a black-box callee, or one already in flight on the call stack, is one message closed by a synthesised return of its declared type
Flow Flow — DisclosedCalleesExpandInline scroll to zoom · drag to pan
FEATUREDisclosedCalleesExpandInlinefor TraceGIVENa traced body whose calls reachdisclosed, black-box, and recursivecalleesWHENeach call is tracedTHENa disclosed callee's body expandsinline, the active lifeline switchingto itANDits own returns flow back to itscallerBUTa black-box callee, or one already inflight on the call stack, is onemessage closed by a synthesised returnof its declared type
TriggerlessEntrySuppressesReturns

A triggerless entry suppresses its own returns.

  • given a sequence entry that bears no trigger macro
  • when its body is traced
  • then the owner leads with no inbound call and the entry's returns are suppressed
  • but inner expanded callees still return to their callers
Flow Flow — TriggerlessEntrySuppressesReturns scroll to zoom · drag to pan
FEATURETriggerlessEntrySuppressesReturnsfor TraceGIVENa sequence entry that bears no triggermacroWHENits body is tracedTHENthe owner leads with no inbound calland the entry's returns are suppressedBUTinner expanded callees still return totheir callers
data

UnknownNode #

public
emit::UnknownNode
Entities Entity diagram scroll to zoom · drag to pan
RECORDUnknownNodefqnstring
data

View #

public
emit::View

Which view to project from the graph (LANG.md §9). Context is the C1 whole; +frame.

Parent

Inbound

Scenarios

ControlFlowBecomesFrames

Control flow becomes nested frames in the trace.

  • given a callable body with if/else and for/while
  • when the body is traced
  • then an if/else becomes an alt frame over its branches
  • and a for/while becomes a loop frame over its body
Flow Flow — ControlFlowBecomesFrames scroll to zoom · drag to pan
FEATUREControlFlowBecomesFramesfor TraceGIVENa callable body with if/else andfor/whileWHENthe body is tracedTHENan if/else becomes an alt frame overits branchesANDa for/while becomes a loop frame overits body
CallsBecomeMessages

Body calls become left-to-right messages; same-node calls loop on the owner.

  • given a callable body with calls to other nodes and same-node calls
  • when the body is traced
  • then each call to another node is a left-to-right message to that lifeline
  • and each same-node call is a self-message that expands its disclosed body on the owner's own lifeline
  • and every target is registered as a participant on first appearance
Flow Flow — CallsBecomeMessages scroll to zoom · drag to pan
FEATURECallsBecomeMessagesfor TraceGIVENa callable body with calls to othernodes and same-node callsWHENthe body is tracedTHENeach call to another node is aleft-to-right message to that lifelineANDeach same-node call is a self-messagethat expands its disclosed body on theowner's own lifelineANDevery target is registered as aparticipant on first appearance
DisclosedCalleesExpandInline

Disclosed callees expand inline; black boxes and recursion stay single calls.

  • given a traced body whose calls reach disclosed, black-box, and recursive callees
  • when each call is traced
  • then a disclosed callee's body expands inline, the active lifeline switching to it
  • and its own returns flow back to its caller
  • but a black-box callee, or one already in flight on the call stack, is one message closed by a synthesised return of its declared type
Flow Flow — DisclosedCalleesExpandInline scroll to zoom · drag to pan
FEATUREDisclosedCalleesExpandInlinefor TraceGIVENa traced body whose calls reachdisclosed, black-box, and recursivecalleesWHENeach call is tracedTHENa disclosed callee's body expandsinline, the active lifeline switchingto itANDits own returns flow back to itscallerBUTa black-box callee, or one already inflight on the call stack, is onemessage closed by a synthesised returnof its declared type
TriggerlessEntrySuppressesReturns

A triggerless entry suppresses its own returns.

  • given a sequence entry that bears no trigger macro
  • when its body is traced
  • then the owner leads with no inbound call and the entry's returns are suppressed
  • but inner expanded callees still return to their callers
Flow Flow — TriggerlessEntrySuppressesReturns scroll to zoom · drag to pan
FEATURETriggerlessEntrySuppressesReturnsfor TraceGIVENa sequence entry that bears no triggermacroWHENits body is tracedTHENthe owner leads with no inbound calland the entry's returns are suppressedBUTinner expanded callees still return totheir callers
data

UnknownNode #

public
emit::UnknownNode
Entities Entity diagram scroll to zoom · drag to pan
RECORDUnknownNodefqnstring
data

View #

public
emit::View

Which view to project from the graph (LANG.md §9). Context is the C1 whole; Container/Component zoom into one boundary; Sequence traces one entry point; Data draws a `data` type's entity (ER) view (§9.4); Feature draws a `feature` scenario's flow (§9.5).

Entities Entity diagram scroll to zoom · drag to pan
UNIONViewContextContainerComponentSequenceDataFeatureRECORDContainerofstringRECORDComponentofstringRECORDSequenceentrystringRECORDDataofstringRECORDFeatureofstring
data

WrongKind #

public
emit::WrongKind
Entities Entity diagram scroll to zoom · drag to pan
RECORDWrongKindfqnstringexpectedstringfoundstring
Generated by pds doc.
diff --git a/model/site/module/format.html b/model/site/module/format.html index e32890be..a244236a 100644 --- a/model/site/module/format.html +++ b/model/site/module/format.html @@ -11,20 +11,20 @@ -
PseudoScript
Module

format

container

Format #

public
format::Format

`crates/pseudoscript-format`. The canonical formatter: parse, then +

PseudoScript
Module

format

container

Format #

public
format::Format

`crates/pseudoscript-format`. The canonical formatter: parse, then pretty-print the tree to one canonical form. The string-to-string entry the CLI and LSP depend on.

Inbound

Outbound

Scenarios

FormatIsIdempotent

Formatting the output again changes nothing.

  • given a parseable .pds source
  • when it is formatted and the result is formatted a second time
  • then the first pass yields canonical text
  • and the second pass returns that text unchanged
Flow Flow — FormatIsIdempotent scroll to zoom · drag to pan
FEATUREFormatIsIdempotentfor FormatGIVENa parseable .pds sourceWHENit is formatted and the result isformatted a second timeTHENthe first pass yields canonical textANDthe second pass returns that textunchanged
FormatPreservesSemantics

Reformatting only touches layout, never meaning.

  • given a parseable .pds source
  • when it is formatted
  • then the result re-parses to a structurally equivalent tree
  • but only whitespace and comment layout differ from the source
Flow Flow — FormatPreservesSemantics scroll to zoom · drag to pan
FEATUREFormatPreservesSemanticsfor FormatGIVENa parseable .pds sourceWHENit is formattedTHENthe result re-parses to a structurallyequivalent treeBUTonly whitespace and comment layoutdiffer from the source
Components Component diagram scroll to zoom · drag to pan
PseudoscriptFormatCOMPONENTFormatterDrives the headline flow: parse the source,short-circuit to `FormatError::Parse` if an…COMPONENTPrinterThe canonical pretty-printer: walks the ASTtop-down, appending to a buffer, and emits…CONTAINERCli`crates/pseudoscript` — the binary crate(`pds`). The composition root and only I/O…CONTAINERSyntax`crates/pseudoscript-syntax`. The foundationcrate: source text to tokens and a typed…renderTokensprintparse
data

FormatError #

public
format::FormatError

Why formatting failed. The only failure is unparseable input: `Parse` carries the rendered error messages (one per diagnostic) for context, and the caller keeps its original text.

Entities Entity diagram scroll to zoom · drag to pan
UNIONFormatErrorParseRECORDParsemessagesstring[]
component

Formatter #

private
format::Formatter

Drives the headline flow: parse the source, short-circuit to `FormatError::Parse` if any error diagnostic surfaced, else hand the tree to -the printer for canonical text.

Parent

Inbound

Outbound

Scenarios

ParseErrorsShortCircuit

Unparseable source short-circuits and is left untouched.

  • given source with at least one error-severity parse diagnostic
  • when it is formatted
  • then formatting returns FormatError::Parse carrying the rendered messages
  • but the source is not rewritten and the caller keeps the original text
Flow Flow — ParseErrorsShortCircuit scroll to zoom · drag to pan
FEATUREParseErrorsShortCircuitfor FormatterGIVENsource with at least oneerror-severity parse diagnosticWHENit is formattedTHENformatting returns FormatError::Parsecarrying the rendered messagesBUTthe source is not rewritten and thecaller keeps the original text
OnlyErrorsBlockFormatting

Warnings and info never block formatting.

  • given source that parses with only warning or info diagnostics
  • when it is formatted
  • then the tree is pretty-printed to canonical text
  • but the non-error diagnostics do not short-circuit the formatter
Flow Flow — OnlyErrorsBlockFormatting scroll to zoom · drag to pan
FEATUREOnlyErrorsBlockFormattingfor FormatterGIVENsource that parses with only warningor info diagnosticsWHENit is formattedTHENthe tree is pretty-printed tocanonical textBUTthe non-error diagnostics do notshort-circuit the formatter
data

Parse #

public
format::Parse
Entities Entity diagram scroll to zoom · drag to pan
RECORDParsemessagesstring[]
component

Printer #

private
format::Printer

The canonical pretty-printer: walks the AST top-down, appending to a buffer, +the printer for canonical text.

Parent

Inbound

Outbound

Scenarios

ParseErrorsShortCircuit

Unparseable source short-circuits and is left untouched.

  • given source with at least one error-severity parse diagnostic
  • when it is formatted
  • then formatting returns FormatError::Parse carrying the rendered messages
  • but the source is not rewritten and the caller keeps the original text
Flow Flow — ParseErrorsShortCircuit scroll to zoom · drag to pan
FEATUREParseErrorsShortCircuitfor FormatterGIVENsource with at least oneerror-severity parse diagnosticWHENit is formattedTHENformatting returns FormatError::Parsecarrying the rendered messagesBUTthe source is not rewritten and thecaller keeps the original text
OnlyErrorsBlockFormatting

Warnings and info never block formatting.

  • given source that parses with only warning or info diagnostics
  • when it is formatted
  • then the tree is pretty-printed to canonical text
  • but the non-error diagnostics do not short-circuit the formatter
Flow Flow — OnlyErrorsBlockFormatting scroll to zoom · drag to pan
FEATUREOnlyErrorsBlockFormattingfor FormatterGIVENsource that parses with only warningor info diagnosticsWHENit is formattedTHENthe tree is pretty-printed tocanonical textBUTthe non-error diagnostics do notshort-circuit the formatter
data

Parse #

public
format::Parse
Entities Entity diagram scroll to zoom · drag to pan
RECORDParsemessagesstring[]
component

Printer #

private
format::Printer

The canonical pretty-printer: walks the AST top-down, appending to a buffer, and emits one canonical form. Indentation is a level counter (two spaces each); leading trivia is normalised — blank-line runs collapse to at most one separator and `//` / `/* */` comments reproduce at the current indent in source order. Internals are black-boxed per construct; the top-level `print` -flow is disclosed.

Parent

Inbound

Outbound

Scenarios

CanonicalRecord

Records render on one line.

  • given a data record declaration
  • when it is printed
  • then the fields render inline as { a: T, b: U }
  • and an empty record renders as { }
Flow Flow — CanonicalRecord scroll to zoom · drag to pan
FEATURECanonicalRecordfor PrinterGIVENa data record declarationWHENit is printedTHENthe fields render inline as { a: T, b:U }ANDan empty record renders as { }
CanonicalUnion

Unions stack one variant per line.

  • given a data union declaration
  • when it is printed
  • then the body opens with ` =` on the declaration line
  • and each variant prints as `| Name` on its own line, indented one level
Flow Flow — CanonicalUnion scroll to zoom · drag to pan
FEATURECanonicalUnionfor PrinterGIVENa data union declarationWHENit is printedTHENthe body opens with ` =` on thedeclaration lineANDeach variant prints as `| Name` on itsown line, indented one level
CanonicalFeature

Features render one step per line.

  • given a feature declaration with given/when/then steps
  • when it is printed
  • then each step prints on its own line, keyword then prose
  • but the keywords are not column-aligned, keeping the form idempotent
Flow Flow — CanonicalFeature scroll to zoom · drag to pan
FEATURECanonicalFeaturefor PrinterGIVENa feature declaration withgiven/when/then stepsWHENit is printedTHENeach step prints on its own line,keyword then proseBUTthe keywords are not column-aligned,keeping the form idempotent
TwoSpaceIndent

Indentation is two spaces per nesting level.

  • given a disclosed node, block, union, or feature body
  • when its members are printed
  • then each nesting level adds two spaces of indentation
Flow Flow — TwoSpaceIndent scroll to zoom · drag to pan
FEATURETwoSpaceIndentfor PrinterGIVENa disclosed node, block, union, orfeature bodyWHENits members are printedTHENeach nesting level adds two spaces ofindentation
TriviaPreserved

Comments and blank-line runs survive; runs collapse to one.

  • given source interleaving declarations with comments and runs of blank lines
  • when it is printed
  • then each comment reproduces on its own line at the current indent
  • and a run of blank lines collapses to a single blank separator
  • but the first member in a block or file gets no leading blank line
Flow Flow — TriviaPreserved scroll to zoom · drag to pan
FEATURETriviaPreservedfor PrinterGIVENsource interleaving declarations withcomments and runs of blank linesWHENit is printedTHENeach comment reproduces on its ownline at the current indentANDa run of blank lines collapses to asingle blank separatorBUTthe first member in a block or filegets no leading blank line
BodyFormsPreserved

Black-box and disclosed forms are each kept, never converted.

  • given a mix of black-box `;` and disclosed `{ }` nodes and callables
  • when they are printed
  • then a black-box body stays `;`
  • and a disclosed body keeps its `{ }`, one member per line
  • but an empty disclosed body renders ` { }`
Flow Flow — BodyFormsPreserved scroll to zoom · drag to pan
FEATUREBodyFormsPreservedfor PrinterGIVENa mix of black-box `;` and disclosed`{ }` nodes and callablesWHENthey are printedTHENa black-box body stays `;`ANDa disclosed body keeps its `{ }`, onemember per lineBUTan empty disclosed body renders ` { }`
data

PrinterState #

private
format::PrinterState

The running pretty-printer state: the text buffer being built, the current +flow is disclosed.

Parent

Inbound

Outbound

  • from format::string

Scenarios

CanonicalRecord

Records render on one line.

  • given a data record declaration
  • when it is printed
  • then the fields render inline as { a: T, b: U }
  • and an empty record renders as { }
Flow Flow — CanonicalRecord scroll to zoom · drag to pan
FEATURECanonicalRecordfor PrinterGIVENa data record declarationWHENit is printedTHENthe fields render inline as { a: T, b:U }ANDan empty record renders as { }
CanonicalUnion

Unions stack one variant per line.

  • given a data union declaration
  • when it is printed
  • then the body opens with ` =` on the declaration line
  • and each variant prints as `| Name` on its own line, indented one level
Flow Flow — CanonicalUnion scroll to zoom · drag to pan
FEATURECanonicalUnionfor PrinterGIVENa data union declarationWHENit is printedTHENthe body opens with ` =` on thedeclaration lineANDeach variant prints as `| Name` on itsown line, indented one level
CanonicalFeature

Features render one step per line.

  • given a feature declaration with given/when/then steps
  • when it is printed
  • then each step prints on its own line, keyword then prose
  • but the keywords are not column-aligned, keeping the form idempotent
Flow Flow — CanonicalFeature scroll to zoom · drag to pan
FEATURECanonicalFeaturefor PrinterGIVENa feature declaration withgiven/when/then stepsWHENit is printedTHENeach step prints on its own line,keyword then proseBUTthe keywords are not column-aligned,keeping the form idempotent
TwoSpaceIndent

Indentation is two spaces per nesting level.

  • given a disclosed node, block, union, or feature body
  • when its members are printed
  • then each nesting level adds two spaces of indentation
Flow Flow — TwoSpaceIndent scroll to zoom · drag to pan
FEATURETwoSpaceIndentfor PrinterGIVENa disclosed node, block, union, orfeature bodyWHENits members are printedTHENeach nesting level adds two spaces ofindentation
TriviaPreserved

Comments and blank-line runs survive; runs collapse to one.

  • given source interleaving declarations with comments and runs of blank lines
  • when it is printed
  • then each comment reproduces on its own line at the current indent
  • and a run of blank lines collapses to a single blank separator
  • but the first member in a block or file gets no leading blank line
Flow Flow — TriviaPreserved scroll to zoom · drag to pan
FEATURETriviaPreservedfor PrinterGIVENsource interleaving declarations withcomments and runs of blank linesWHENit is printedTHENeach comment reproduces on its ownline at the current indentANDa run of blank lines collapses to asingle blank separatorBUTthe first member in a block or filegets no leading blank line
BodyFormsPreserved

Black-box and disclosed forms are each kept, never converted.

  • given a mix of black-box `;` and disclosed `{ }` nodes and callables
  • when they are printed
  • then a black-box body stays `;`
  • and a disclosed body keeps its `{ }`, one member per line
  • but an empty disclosed body renders ` { }`
Flow Flow — BodyFormsPreserved scroll to zoom · drag to pan
FEATUREBodyFormsPreservedfor PrinterGIVENa mix of black-box `;` and disclosed`{ }` nodes and callablesWHENthey are printedTHENa black-box body stays `;`ANDa disclosed body keeps its `{ }`, onemember per lineBUTan empty disclosed body renders ` { }`
data

PrinterState #

private
format::PrinterState

The running pretty-printer state: the text buffer being built, the current nesting depth (two spaces each), and whether the current line has had its -indentation written yet so further writes append in place.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDPrinterStateoutstringindentnumberlineStartedbool
Generated by pds doc.
+indentation written yet so further writes append in place.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPrinterStateoutstringindentnumberlineStartedbool
Generated by pds doc.
diff --git a/model/site/module/ide.html b/model/site/module/ide.html index 3d71cfcb..88d0e89a 100644 --- a/model/site/module/ide.html +++ b/model/site/module/ide.html @@ -11,12 +11,12 @@ -
PseudoScript
Module

ide

component

Bootstrap #

public
ide::Bootstrap

Boots the IDE in a browser tab: load the one wasm bundle (IdeSession), then +

PseudoScript
Module

ide

component

Bootstrap #

public
ide::Bootstrap

Boots the IDE in a browser tab: load the one wasm bundle (IdeSession), then restore the URL hash's workspace — a share link mounts in memory and needs no disk — or fall back to the launcher. File System Access support gates only the disk features (open folder, save, watch): without it those actions are disabled with a notice, while share links and the bundled examples -still open.

Parent

Inbound

Outbound

Scenarios

DiskGatesOnlyDiskFeatures

Missing File System Access disables disk features, never the IDE.

  • given a browser without the File System Access API (Firefox, Safari)
  • when the IDE boots
  • then share links and bundled examples still open in memory
  • and the disk actions — open folder, save, watch — are disabled with a notice naming the supported browsers
  • but no full-screen wall blocks the product
Flow Flow — DiskGatesOnlyDiskFeatures scroll to zoom · drag to pan
FEATUREDiskGatesOnlyDiskFeaturesfor BootstrapGIVENa browser without the File SystemAccess API (Firefox, Safari)WHENthe IDE bootsTHENshare links and bundled examples stillopen in memoryANDthe disk actions — open folder, save,watch — are disabled with a noticenaming the supported browsersBUTno full-screen wall blocks the product
data

BrowserWorkspace #

public
ide::BrowserWorkspace

The in-browser workspace state the session holds: the open `.pds` modules, the +still open.

Parent

Inbound

Outbound

Scenarios

DiskGatesOnlyDiskFeatures

Missing File System Access disables disk features, never the IDE.

  • given a browser without the File System Access API (Firefox, Safari)
  • when the IDE boots
  • then share links and bundled examples still open in memory
  • and the disk actions — open folder, save, watch — are disabled with a notice naming the supported browsers
  • but no full-screen wall blocks the product
Flow Flow — DiskGatesOnlyDiskFeatures scroll to zoom · drag to pan
FEATUREDiskGatesOnlyDiskFeaturesfor BootstrapGIVENa browser without the File SystemAccess API (Firefox, Safari)WHENthe IDE bootsTHENshare links and bundled examples stillopen in memoryANDthe disk actions — open folder, save,watch — are disabled with a noticenaming the supported browsersBUTno full-screen wall blocks the product
data

BrowserWorkspace #

public
ide::BrowserWorkspace

The in-browser workspace state the session holds: the open `.pds` modules, the `pds.toml` manifest text, and the dependency modules resolved from `pds_modules/` as externals (LANG.md §8.3). Held in memory — a decoded sample/share link or a local directory loaded through the file-system port. @@ -46,18 +46,18 @@ Ollama, and vLLM. Stateless — the caller hands it the settings snapshot and owns cancellation (`GhostText` aborts the in-flight request on the next keystroke; the client honours the abort). Every failure comes back as a -classified `ProviderError`, so the caller can surface why a request bailed.

Parent

Inbound

Outbound

data

FimPrompt #

public
ide::FimPrompt

The assembled fill-in-the-middle request: the windowed buffer text before and +classified `ProviderError`, so the caller can surface why a request bailed.

Parent

Inbound

Outbound

data

FimPrompt #

public
ide::FimPrompt

The assembled fill-in-the-middle request: the windowed buffer text before and after the caret, and the cached grammar primer that steers a general model to -emit valid PseudoScript.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDFimPromptprefixstringsuffixstringprimerstring
component

FsAccessAdapter #

public
ide::FsAccessAdapter

The file-system adapter over the browser File System Access API: directory +emit valid PseudoScript.

Entities Entity diagram scroll to zoom · drag to pan
RECORDFimPromptprefixstringsuffixstringprimerstring
component

FsAccessAdapter #

public
ide::FsAccessAdapter

The file-system adapter over the browser File System Access API: directory picker, recursive walk for `.pds` modules, deriving each module's flat FQN from its path (LANG.md §8.1, ADR-031), `pds.lock` + `pds_modules/` reads for dependency externals, and per-file read/write through handles. The only place FS-Access specifics live; it pushes what it reads into the session via -`mount`.

Parent

Inbound

Outbound

component

GhostText #

private
ide::GhostText

Inline ghost-text completion over CodeMirror 6, beside the grammar dropdown: +`mount`.

Parent

Inbound

Outbound

component

GhostText #

private
ide::GhostText

Inline ghost-text completion over CodeMirror 6, beside the grammar dropdown: structural candidates stay local and deterministic in `CodeMirrorAdapter`; the remote model supplies only multi-token greyed text. CodeMirror has no native inline-suggestion UI, so this is a custom view plugin rendering a -widget decoration at the caret — Tab accepts, Esc dismisses, any edit clears.

Parent

Outbound

Scenarios

GhostTextSuggests

Ghost text comes from the configured provider, rendered as-is for the +widget decoration at the caret — Tab accepts, Esc dismisses, any edit clears.

Parent

Outbound

Scenarios

GhostTextSuggests

Ghost text comes from the configured provider, rendered as-is for the author to judge.

  • given AI completion enabled with a configured OpenAI-compatible provider
  • when the author pauses typing
  • then the windowed prefix/suffix and the grammar primer go to the provider
  • and the suggestion renders as greyed inline text — Tab accepts, Esc dismisses
  • but the suggestion is the author's to judge — accepting one with a syntax error lights the live diagnostics — and a provider failure surfaces on the AI status chip with the reason; it never blocks typing
Flow Flow — GhostTextSuggests scroll to zoom · drag to pan
FEATUREGhostTextSuggestsfor GhostTextGIVENAI completion enabled with aconfigured OpenAI-compatible providerWHENthe author pauses typingTHENthe windowed prefix/suffix and thegrammar primer go to the providerANDthe suggestion renders as greyedinline text — Tab accepts, EscdismissesBUTthe suggestion is the author's tojudge — accepting one with a syntaxerror lights the live diagnostics —and a provider failure surfaces on theAI status chip with the reason; itnever blocks typing
StructuralCompletionStaysLocal

The remote model augments, never replaces, the local grammar completer.

  • given the grammar dropdown completer answering from the wasm session
  • when AI completion is enabled beside it
  • then structural candidates stay local and deterministic
  • and the remote model supplies only multi-token inline suggestions
  • but with AI completion off or unconfigured, the editor behaves exactly as before
Flow Flow — StructuralCompletionStaysLocal scroll to zoom · drag to pan
FEATUREStructuralCompletionStaysLocalfor GhostTextGIVENthe grammar dropdown completeranswering from the wasm sessionWHENAI completion is enabled beside itTHENstructural candidates stay local anddeterministicANDthe remote model supplies onlymulti-token inline suggestionsBUTwith AI completion off orunconfigured, the editor behavesexactly as before
data

GridPin #

public
ide::GridPin

A node the user has dragged onto a grid cell, identified by FQN. Carried inside `LayoutTweaks`; honoured only under experimental grid placement.

Entities Entity diagram scroll to zoom · drag to pan
RECORDGridPinfqnstringrownumbercolnumber
container

IdeSession #

public
ide::IdeSession

`crates/pseudoscript-ide` — the IDE application, Rust compiled to a single wasm. It owns the workspace state and is the whole browser API: the host @@ -89,16 +89,16 @@ OpenAI-compatible endpoint, the bring-your-own API key (stored in localStorage, sent only to the configured endpoint), the model id, and the wire shape (`"fim"` for a native fill-in-the-middle route, `"chat"` for the -chat-completions fallback).

Entities Entity diagram scroll to zoom · drag to pan
RECORDLlmSettingsenabledboolproviderstringbaseUrlstringapiKeystringmodelstringmodestring
component

LlmSettingsStore #

private
ide::LlmSettingsStore

The author's AI-completion settings, persisted to browser localStorage and +chat-completions fallback).

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDLlmSettingsenabledboolproviderstringbaseUrlstringapiKeystringmodelstringmodestring
component

LlmSettingsStore #

private
ide::LlmSettingsStore

The author's AI-completion settings, persisted to browser localStorage and edited through the Settings dialog's "AI Completion" tab. Bring-your-own key: it never leaves the tab except on requests to the configured endpoint. Setup is preset-first: picking a provider applies its pinned endpoint and wire shape, so only the Custom preset exposes the raw fields. The store also carries the session's last completion failure (never persisted) so the -status chip and the settings tab can show why ghost text is bailing.

Parent

Inbound

Outbound

Scenarios

GuidedProviderSetup

Setup is preset-first; the connection test explains a failure instead of +status chip and the settings tab can show why ghost text is bailing.

Parent

Inbound

Outbound

Scenarios

GuidedProviderSetup

Setup is preset-first; the connection test explains a failure instead of leaving the author guessing.

  • given the Settings dialog's AI Completion tab
  • when the author picks the Ollama or OpenAI preset
  • then the endpoint and wire shape are pinned by the preset — no raw URL to type
  • and the model dropdown fills from the provider's live model list
  • and Test connection round-trips the provider and reports success or the classified failure with its fix hint
  • but the Custom preset still exposes every field for any OpenAI-compatible endpoint
Flow Flow — GuidedProviderSetup scroll to zoom · drag to pan
FEATUREGuidedProviderSetupfor LlmSettingsStoreGIVENthe Settings dialog's AI CompletiontabWHENthe author picks the Ollama or OpenAIpresetTHENthe endpoint and wire shape are pinnedby the preset — no raw URL to typeANDthe model dropdown fills from theprovider's live model listANDTest connection round-trips theprovider and reports success or theclassified failure with its fix hintBUTthe Custom preset still exposes everyfield for any OpenAI-compatibleendpoint
data

LocalInput #

public
ide::LocalInput

One local-source dependency file the host reads for `dependencyModules`: the dependency name (ADR-026), its FQN within the dependency workspace, and its -source.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDLocalInputnamestringfqnstringsourcestring
data

Occurrence #

public
ide::Occurrence

One occurrence of a symbol: the module it lies in, its line/column span, the +source.

Entities Entity diagram scroll to zoom · drag to pan
RECORDLocalInputnamestringfqnstringsourcestring
data

Occurrence #

public
ide::Occurrence

One occurrence of a symbol: the module it lies in, its line/column span, the source line text with the match offsets within it, and whether it is the declaration site.

Entities Entity diagram scroll to zoom · drag to pan
RECORDOccurrencefqnstringlinenumbercolnumberendLinenumberendColnumbertextstringmatchStartnumbermatchEndnumberdeclbool
data

OutlineNode #

public
ide::OutlineNode

One row of the workspace outline: a node's FQN, simple name, C4 kind, containment parent (absent on roots), whether it is a triggered flow entry, @@ -127,7 +127,7 @@ weighted edges, no positions (the renderer lays them out). The typed boundary form of a `universe::Snapshot`.

Entities Entity diagram scroll to zoom · drag to pan
RECORDUniverseSnapshotnodeside::UniverseNode[]edgeside::UniverseEdge[]RECORDUniverseNodeidstringlevelstringparentOption<string>RECORDUniverseEdgesourcestringtostringtrafficnumber
data

VendoredInput #

public
ide::VendoredInput

One vendored git-dependency file the host reads for `dependencyModules`: its `pds_modules/` slug, its FQN within the dependency workspace (the host's -path→FQN derivation, LANG.md §8.1), and its source.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDVendoredInputslugstringfqnstringsourcestring
container

WebIde #

public
ide::WebIde

`web-ide` — the IDE at `ide.pdscript.dev`: a SvelteKit shell on Cloudflare +path→FQN derivation, LANG.md §8.1), and its source.

Entities Entity diagram scroll to zoom · drag to pan
RECORDVendoredInputslugstringfqnstringsourcestring
container

WebIde #

public
ide::WebIde

`web-ide` — the IDE at `ide.pdscript.dev`: a SvelteKit shell on Cloudflare Workers static assets. Pure glue — it loads the one wasm bundle and binds the adapters to the session's ports. It holds no language logic.

Inbound

Outbound

Scenarios

JsOnlyAtAdapters

JavaScript exists only in the adapters plus the host's path→FQN derivation; all language and projection logic is Rust.

  • given the IDE session, the language queries, and diagram projection all in Rust
  • when the application touches the file system or the editor surface
  • then an adapter — `FsAccessAdapter`, `CodeMirrorAdapter` — pushes state into the typed session and applies the values it returns
  • and those adapters are the only JavaScript: File System Access and CodeMirror specifics live there
  • but the FS adapter also derives each module's flat FQN from its path (LANG.md §8.1, ADR-031) — the one workspace rule the host runs before pushing modules to the Rust core
Flow Flow — JsOnlyAtAdapters scroll to zoom · drag to pan
FEATUREJsOnlyAtAdaptersfor WebIdeGIVENthe IDE session, the language queries,and diagram projection all in RustWHENthe application touches the filesystem or the editor surfaceTHENan adapter — `FsAccessAdapter`,`CodeMirrorAdapter` — pushes stateinto the typed session and applies thevalues it returnsANDthose adapters are the onlyJavaScript: File System Access andCodeMirror specifics live thereBUTthe FS adapter also derives eachmodule's flat FQN from its path(LANG.md §8.1, ADR-031) — the oneworkspace rule the host runs beforepushing modules to the Rust core
EditInBrowser

Editing a model in the browser checks and redraws it live, with no backend.

  • given a `.pds` module open in the browser IDE
  • when the author edits it through the CodeMirror adapter
  • then the edit drives the Rust session, which re-checks against the externals
  • and diagnostics and the projected C4 or sequence diagram update live
  • but nothing is sent to a server
Flow Flow — EditInBrowser scroll to zoom · drag to pan
FEATUREEditInBrowserfor WebIdeGIVENa `.pds` module open in the browserIDEWHENthe author edits it through theCodeMirror adapterTHENthe edit drives the Rust session,which re-checks against the externalsANDdiagnostics and the projected C4 orsequence diagram update liveBUTnothing is sent to a server
Components Component diagram scroll to zoom · drag to pan
PseudoscriptWebIdeCOMPONENTFsAccessAdapterThe file-system adapter over the browserFile System Access API: directory picker,…COMPONENTCodeMirrorAdapterThe editor adapter over CodeMirror 6: thetext/cursor model, the completion source,…COMPONENTLlmSettingsStoreThe author's AI-completion settings,persisted to browser localStorage and edite…COMPONENTFimClientProvider-agnostic completion client: one`complete` contract over OpenAI-compatible…COMPONENTGhostTextInline ghost-text completion over CodeMirror6, beside the grammar dropdown: structural…COMPONENTDiagramCanvasThe diagram canvas: draws the **JSON**`Scene` interactively (Svelte Flow pan/zoom…COMPONENTBootstrapBoots the IDE in a browser tab: load the onewasm bundle (IdeSession), then restore the…COMPONENTSamplesThe bundled example catalogue, discovered atbuild time from the samples folder (one…COMPONENTLauncherThe project launcher — the first surface.Offers the recents, open-a-folder, New…CONTAINERIdeSession`crates/pseudoscript-ide` — the IDEapplication, Rust compiled to a single wasm…SYSTEMLlmProviderThe author's configured OpenAI-compatiblecompletion provider — hosted (OpenAI,…CONTAINERLanding`web-landing` — the marketing site at`pdscript.dev`. A static Svelte build on…showcompletediagnosticshoversetSourcechatCompletefimCompletelistModelsdependencyModulescompleteoutlineclearDropclearFailurecurrentisEnablednoteDropreportFailurepickFolderreadDependenciesreadModulesmountworkspaceOf
component

Workspace #

public
ide::Workspace

The in-memory project the session holds: the parsed modules keyed by FQN (an diff --git a/model/site/module/landing.html b/model/site/module/landing.html index ceb77957..0e555c2f 100644 --- a/model/site/module/landing.html +++ b/model/site/module/landing.html @@ -11,7 +11,7 @@ -

PseudoScript
Module

landing

container

Landing #

public
landing::Landing

`web-landing` — the marketing site at `pdscript.dev`. A static Svelte build on +

PseudoScript
Module

landing

container

Landing #

public
landing::Landing

`web-landing` — the marketing site at `pdscript.dev`. A static Svelte build on Cloudflare Workers static assets; no backend. It routes a visitor to one of three surfaces and otherwise just tells the model-driven story.

Inbound

Outbound

Scenarios

OpenTheIde

The landing page's one job: get a visitor into the product.

#headline
  • given a visitor on pdscript.dev
  • when they choose to try it in the browser
  • then the zero-install Web IDE opens at ide.pdscript.dev
Flow Flow — OpenTheIde scroll to zoom · drag to pan
FEATUREOpenTheIdefor LandingGIVENa visitor on pdscript.devWHENthey choose to try it in the browserTHENthe zero-install Web IDE opens atide.pdscript.dev
Components Component diagram scroll to zoom · drag to pan
PseudoscriptCONTAINERWebIde`web-ide` — the IDE at `ide.pdscript.dev`: aSvelteKit shell on Cloudflare Workers stati…PERSONVisitorA prospective user reading the landing page.
person

Visitor #

public
landing::Visitor

A prospective user reading the landing page.

Outbound

Sequence Sequence — installCli scroll to zoom · drag to pan
SEQUENCEinstallCliPERSONVisitorA prospective userreading the landingCONTAINERLandingPseudoscript`web-landing` — themarketing site at1showInstall()↩ return
Sequence Sequence — tryInBrowser scroll to zoom · drag to pan
SEQUENCEtryInBrowseralt[!restored]PERSONVisitorA prospective userreading the landingCONTAINERLandingPseudoscript`web-landing` — themarketing site atCONTAINERWebIdePseudoscript`web-ide` — the IDE at`ide.pdscript.dev`: aCOMPONENTBootstrapPseudoscript::WebIdeBoots the IDE in abrowser tab: load theCOMPONENTLauncherPseudoscript::WebIdeThe project launcher —the first surface.1openIde()2open()3start()4restoreFromHash5show()↩ return
Generated by pds doc.
diff --git a/model/site/module/layout.html b/model/site/module/layout.html index 3b75de1a..c3c39148 100644 --- a/model/site/module/layout.html +++ b/model/site/module/layout.html @@ -11,7 +11,7 @@ -
PseudoScript
Module

layout

data

Activation #

private
layout::Activation

An execution-activation bar on a lifeline, spanning a participant's first-to- +

PseudoScript
Module

layout

data

Activation #

private
layout::Activation

An execution-activation bar on a lifeline, spanning a participant's first-to- last involvement; `owner` marks the entry's focus lifeline.

Entities Entity diagram scroll to zoom · drag to pan
RECORDActivationparticipantstringxnumbertopnumberbottomnumberownerbool
component

Core #

public
layout::Core

The shared geometry/text core: measures label and title widths, wraps descriptions to a width, and accumulates bounding boxes. Every engine builds on it, so text sizing and bounds math live in exactly one place.

Parent

Scenarios

SharedGeometryCore

The geometry/text core is the single home for sizing and bounds.

  • given an engine that must size labels and accumulate a bounding box
  • when it measures text or grows its bounds
  • then it calls the shared core rather than re-implementing the math
  • and text sizing and bounds accumulation live in exactly one place
Flow Flow — SharedGeometryCore scroll to zoom · drag to pan
FEATURESharedGeometryCorefor CoreGIVENan engine that must size labels andaccumulate a bounding boxWHENit measures text or grows its boundsTHENit calls the shared core rather thanre-implementing the mathANDtext sizing and bounds accumulationlive in exactly one place
data

Diagram #

public
layout::Diagram

A structural sequence diagram: the participants (lifelines) and the ordered @@ -28,7 +28,7 @@ dimmed detail after it (a call signature or return type). `source` is the wire field `from`, renamed because `from` is a PseudoScript keyword.

Entities Entity diagram scroll to zoom · drag to pan
RECORDMessagesourcestringtostringkindlayout::MsgKindlabelstringdetailstringUNIONMsgKindCallReturnSelfMsg
data

MessageItem #

private
layout::MessageItem
Entities Entity diagram scroll to zoom · drag to pan
RECORDMessageItemmessagelayout::MessageRECORDMessagesourcestringtostringkindlayout::MsgKindlabelstringdetailstring
data

Metrics #

public
layout::Metrics

Tunable spacing and font metrics for the sequence engine — head-card sizing, row advance per message kind, fragment padding. Field detail is the engine's -concern; the defaults are the one source of truth consumers start from.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
BLACKBOXMetrics
data

MsgKind #

private
layout::MsgKind

The kind of a sequence message: a call to another lifeline, a return to the +concern; the defaults are the one source of truth consumers start from.

Entities Entity diagram scroll to zoom · drag to pan
BLACKBOXMetrics
data

MsgKind #

private
layout::MsgKind

The kind of a sequence message: a call to another lifeline, a return to the caller, or a self-message on the sender's own lifeline.

Entities Entity diagram scroll to zoom · drag to pan
UNIONMsgKindCallReturnSelfMsgRECORDCallmessagestringRECORDReturnmarkerstring
data

Participant #

private
layout::Participant

One lifeline: its id (FQN), display label, C4 kind for the head card, optional summary, and the ancestry path shown under the name for container/component lifelines.

Entities Entity diagram scroll to zoom · drag to pan
RECORDParticipantidstringlabelstringkindstringsummarystringparentPathstring
data

PlacedFragment #

private
layout::PlacedFragment

A placed fragment frame: its box, the operator-tab label (the first section's diff --git a/model/site/module/lsp.html b/model/site/module/lsp.html index f1fd5579..284d4488 100644 --- a/model/site/module/lsp.html +++ b/model/site/module/lsp.html @@ -11,24 +11,24 @@ -

PseudoScript
Module

lsp

data

DefTarget #

private
lsp::DefTarget

A go-to-definition target straight from `lsp_core`: the FQN of the module the +

PseudoScript
Module

lsp

data

DefTarget #

private
lsp::DefTarget

A go-to-definition target straight from `lsp_core`: the FQN of the module the definition lives in, and its span within that module's source. The server maps `fqn` to a file URI — the target file, not the file the request came from.

Entities Entity diagram scroll to zoom · drag to pan
RECORDDefTargetfqnstringspansyntax::SpanRECORDSpanstartnumberendnumber
container

Lsp #

public
lsp::Lsp

`crates/pseudoscript-lsp`. The stdio language server (tower-lsp): workspace diagnostics on change, and hover, definition, completion, references, rename, semantic tokens, symbols, folding, and formatting — every language answer delegated to `lsp_core`. Owns the transport, the document store, and the -URI↔FQN mapping, nothing more.

Inbound

Outbound

Components Component diagram scroll to zoom · drag to pan
PseudoscriptLspCOMPONENTServerThe tower-lsp `Backend`: owns the stdiotransport, the document store, the server…COMPONENTStoreThe workspace document store: the server'sown filesystem edge. Discovers the project…CONTAINERCli`crates/pseudoscript` — the binary crate(`pds`). The composition root and only I/O…CONTAINERLspCore`crates/pseudoscript-lsp-core`. The singlehome for language intelligence as…CONTAINERModel`crates/pseudoscript-model`. AST to oneresolved graph; static checks (resolution,…checkcheckWorkspaceModulesgraphchangeclosediagnosticsdiscoverfqnForsourceuriOfworkspacedefinitionformatEdithovercheck
data

Occurrence #

private
lsp::Occurrence

One resolved editor location: the file URI a target FQN maps back to, and the +URI↔FQN mapping, nothing more.

Inbound

Outbound

Components Component diagram scroll to zoom · drag to pan
PseudoscriptLspCOMPONENTServerThe tower-lsp `Backend`: owns the stdiotransport, the document store, the server…COMPONENTStoreThe workspace document store: the server'sown filesystem edge. Discovers the project…CONTAINERCli`crates/pseudoscript` — the binary crate(`pds`). The composition root and only I/O…CONTAINERLspCore`crates/pseudoscript-lsp-core`. The singlehome for language intelligence as…changeclosediagnosticsdiscoverfqnForsourceuriOfworkspacedefinitionformatEdithover
data

Occurrence #

private
lsp::Occurrence

One resolved editor location: the file URI a target FQN maps back to, and the range within it. The server's transport-level mapping of a `lsp_core` answer.

Entities Entity diagram scroll to zoom · drag to pan
RECORDOccurrenceuristringrangelsp_core::RangeRECORDRangestartlsp_core::Positionendlsp_core::Position
data

OpenDocument #

private
lsp::OpenDocument

One open editor buffer: its document URI, current source text, and the editor's version counter. The buffer overlays on-disk text until closed.

Entities Entity diagram scroll to zoom · drag to pan
RECORDOpenDocumenturistringtextstringversionnumber
component

Server #

private
lsp::Server

The tower-lsp `Backend`: owns the stdio transport, the document store, the server lifecycle, and request routing. Holds no language logic — it locks the store, hands the active text and the resolved workspace to a `lsp_core` -handler, and maps the result back to file locations.

Parent

Inbound

Outbound

Scenarios

SpawnedOverStdio

The editor spawns `pds lsp` as a child process and drives it over stdio.

  • given an editor that speaks the Language Server Protocol
  • when it needs language support for a .pds document
  • then it spawns `pds lsp` as a child process
  • and the server reads requests and writes responses over stdin and stdout
Flow Flow — SpawnedOverStdio scroll to zoom · drag to pan
FEATURESpawnedOverStdiofor ServerGIVENan editor that speaks the LanguageServer ProtocolWHENit needs language support for a .pdsdocumentTHENit spawns `pds lsp` as a child processANDthe server reads requests and writesresponses over stdin and stdout
RepublishOnChange

Every change re-checks and republishes diagnostics for the whole workspace.

  • given a document open in the editor
  • when the editor sends didOpen, didChange, or didClose
  • then the store re-converges from the document's current text
  • and every module's diagnostics are published against its own file
  • but a cross-file issue surfaces in the file that causes it
Flow Flow — RepublishOnChange scroll to zoom · drag to pan
FEATURERepublishOnChangefor ServerGIVENa document open in the editorWHENthe editor sends didOpen, didChange,or didCloseTHENthe store re-converges from thedocument's current textANDevery module's diagnostics arepublished against its own fileBUTa cross-file issue surfaces in thefile that causes it
FullDocumentSync

Full document sync: each change carries the entire new text.

  • given the server advertised full text-document sync
  • when the editor sends a didChange notification
  • then the last content change carries the whole new document text
  • and the store replaces the buffer wholesale rather than patching a range
Flow Flow — FullDocumentSync scroll to zoom · drag to pan
FEATUREFullDocumentSyncfor ServerGIVENthe server advertised fulltext-document syncWHENthe editor sends a didChangenotificationTHENthe last content change carries thewhole new document textANDthe store replaces the bufferwholesale rather than patching a range
AnswerFromCurrentText

All requests are answered from the document's current source.

  • given a document with unsaved edits open in the editor
  • when the editor requests hover, definition, completion, or formatting
  • then the active buffer's current text is handed to lsp_core
  • but the on-disk version is not consulted while a buffer is open
Flow Flow — AnswerFromCurrentText scroll to zoom · drag to pan
FEATUREAnswerFromCurrentTextfor ServerGIVENa document with unsaved edits open inthe editorWHENthe editor requests hover, definition,completion, or formattingTHENthe active buffer's current text ishanded to lsp_coreBUTthe on-disk version is not consultedwhile a buffer is open
DelegatesAllLanguageLogic

The server owns no language logic — it delegates to lsp_core.

  • given a request that needs parsing, checking, resolution, or formatting
  • when the server handles it
  • then every language answer is computed by the lsp_core handlers
  • and checking delegates to the model crate
  • but the server adds no language rules of its own — only transport, the disk walk, and URI mapping
Flow Flow — DelegatesAllLanguageLogic scroll to zoom · drag to pan
FEATUREDelegatesAllLanguageLogicfor ServerGIVENa request that needs parsing,checking, resolution, or formattingWHENthe server handles itTHENevery language answer is computed bythe lsp_core handlersANDchecking delegates to the model crateBUTthe server adds no language rules ofits own — only transport, the diskwalk, and URI mapping
DefinitionJumpsToDeclaration

Go-to-definition jumps to the declaring span, across files.

  • given the cursor on a reference to a node, member, or data type
  • when the editor requests go-to-definition
  • then lsp_core resolves the reference to its declaring module FQN and span
  • and the server maps that FQN back to its file URI, even in another module
Flow Flow — DefinitionJumpsToDeclaration scroll to zoom · drag to pan
FEATUREDefinitionJumpsToDeclarationfor ServerGIVENthe cursor on a reference to a node,member, or data typeWHENthe editor requests go-to-definitionTHENlsp_core resolves the reference to itsdeclaring module FQN and spanANDthe server maps that FQN back to itsfile URI, even in another module
component

Store #

private
lsp::Store

The workspace document store: the server's own filesystem edge. Discovers the +handler, and maps the result back to file locations.

Parent

Inbound

Outbound

Scenarios

SpawnedOverStdio

The editor spawns `pds lsp` as a child process and drives it over stdio.

  • given an editor that speaks the Language Server Protocol
  • when it needs language support for a .pds document
  • then it spawns `pds lsp` as a child process
  • and the server reads requests and writes responses over stdin and stdout
Flow Flow — SpawnedOverStdio scroll to zoom · drag to pan
FEATURESpawnedOverStdiofor ServerGIVENan editor that speaks the LanguageServer ProtocolWHENit needs language support for a .pdsdocumentTHENit spawns `pds lsp` as a child processANDthe server reads requests and writesresponses over stdin and stdout
RepublishOnChange

Every change re-checks and republishes diagnostics for the whole workspace.

  • given a document open in the editor
  • when the editor sends didOpen, didChange, or didClose
  • then the store re-converges from the document's current text
  • and every module's diagnostics are published against its own file
  • but a cross-file issue surfaces in the file that causes it
Flow Flow — RepublishOnChange scroll to zoom · drag to pan
FEATURERepublishOnChangefor ServerGIVENa document open in the editorWHENthe editor sends didOpen, didChange,or didCloseTHENthe store re-converges from thedocument's current textANDevery module's diagnostics arepublished against its own fileBUTa cross-file issue surfaces in thefile that causes it
FullDocumentSync

Full document sync: each change carries the entire new text.

  • given the server advertised full text-document sync
  • when the editor sends a didChange notification
  • then the last content change carries the whole new document text
  • and the store replaces the buffer wholesale rather than patching a range
Flow Flow — FullDocumentSync scroll to zoom · drag to pan
FEATUREFullDocumentSyncfor ServerGIVENthe server advertised fulltext-document syncWHENthe editor sends a didChangenotificationTHENthe last content change carries thewhole new document textANDthe store replaces the bufferwholesale rather than patching a range
AnswerFromCurrentText

All requests are answered from the document's current source.

  • given a document with unsaved edits open in the editor
  • when the editor requests hover, definition, completion, or formatting
  • then the active buffer's current text is handed to lsp_core
  • but the on-disk version is not consulted while a buffer is open
Flow Flow — AnswerFromCurrentText scroll to zoom · drag to pan
FEATUREAnswerFromCurrentTextfor ServerGIVENa document with unsaved edits open inthe editorWHENthe editor requests hover, definition,completion, or formattingTHENthe active buffer's current text ishanded to lsp_coreBUTthe on-disk version is not consultedwhile a buffer is open
DelegatesAllLanguageLogic

The server owns no language logic — it delegates to lsp_core.

  • given a request that needs parsing, checking, resolution, or formatting
  • when the server handles it
  • then every language answer is computed by the lsp_core handlers
  • and checking delegates to the model crate
  • but the server adds no language rules of its own — only transport, the disk walk, and URI mapping
Flow Flow — DelegatesAllLanguageLogic scroll to zoom · drag to pan
FEATUREDelegatesAllLanguageLogicfor ServerGIVENa request that needs parsing,checking, resolution, or formattingWHENthe server handles itTHENevery language answer is computed bythe lsp_core handlersANDchecking delegates to the model crateBUTthe server adds no language rules ofits own — only transport, the diskwalk, and URI mapping
DefinitionJumpsToDeclaration

Go-to-definition jumps to the declaring span, across files.

  • given the cursor on a reference to a node, member, or data type
  • when the editor requests go-to-definition
  • then lsp_core resolves the reference to its declaring module FQN and span
  • and the server maps that FQN back to its file URI, even in another module
Flow Flow — DefinitionJumpsToDeclaration scroll to zoom · drag to pan
FEATUREDefinitionJumpsToDeclarationfor ServerGIVENthe cursor on a reference to a node,member, or data typeWHENthe editor requests go-to-definitionTHENlsp_core resolves the reference to itsdeclaring module FQN and spanANDthe server maps that FQN back to itsfile URI, even in another module
component

Store #

private
lsp::Store

The workspace document store: the server's own filesystem edge. Discovers the project root (`pds.toml`, §8.1) by walking up from the opened URI, walks the tree for every visible `.pds` file, derives each module's FQN from its path, and overlays open buffers on disk text. Caches each parse and memoises the resolved `model::Workspace` — built from the local parses with the workspace's direct-dependency externals bound (§8.3) — both invalidated on any edit. The -externals are cached separately and reload only on a dependency change.

Parent

Inbound

Outbound

Generated by pds doc.
+externals are cached separately and reload only on a dependency change.

Parent

Inbound

Outbound

  • from model::ModuleDiagnostics
  • from lsp::string
  • from lsp::string
  • from lsp::string
  • from lsp::string
  • from lsp::string
  • from lsp::string
  • from lsp::string
  • from lsp::string
Generated by pds doc.
diff --git a/model/site/module/lsp_core.html b/model/site/module/lsp_core.html index 5c251fcf..f8fc36aa 100644 --- a/model/site/module/lsp_core.html +++ b/model/site/module/lsp_core.html @@ -11,7 +11,7 @@ -
PseudoScript
Module

lsp_core

component

Analysis #

private
lsp_core::Analysis

Document-level analysis: parse-and-check diagnostics, Markdown hover for the +

PseudoScript
Module

lsp_core

component

Analysis #

private
lsp_core::Analysis

Document-level analysis: parse-and-check diagnostics, Markdown hover for the symbol under the caret (falling back to an inferred type), go-to-definition, inlay hints, and the canonical format edit. Resolution is delegated to `model`; this maps the result into LSP shapes.

Parent

Inbound

Outbound

Scenarios

ResolutionDelegatedToModel

Resolution is delegated to the model, not re-implemented.

  • given a cursor feature — hover, definition, references, completion
  • when the symbol under the caret must be resolved
  • then resolution runs through model's resolver against the workspace and externals
  • and this crate only maps the resolved answer into the LSP shape
Flow Flow — ResolutionDelegatedToModel scroll to zoom · drag to pan
FEATUREResolutionDelegatedToModelfor AnalysisGIVENa cursor feature — hover, definition,references, completionWHENthe symbol under the caret must beresolvedTHENresolution runs through model'sresolver against the workspace andexternalsANDthis crate only maps the resolvedanswer into the LSP shape
HoverShowsDocSummary

Hover shows the `///` summary of the symbol under the caret.

  • given the caret resting on a declared node, data type, or member
  • when hover runs
  • then the symbol's kind and FQN, or its member signature, is shown
  • and its `///` summary is appended under the title
Flow Flow — HoverShowsDocSummary scroll to zoom · drag to pan
FEATUREHoverShowsDocSummaryfor AnalysisGIVENthe caret resting on a declared node,data type, or memberWHENhover runsTHENthe symbol's kind and FQN, or itsmember signature, is shownANDits `///` summary is appended underthe title
HoverFallsBackToInferredType

An unresolved caret on a local binding falls back to its inferred type.

  • given the caret resting on a local binding rather than a declaration
  • when hover finds no declared symbol there
  • then the binding's inferred type is shown as `name: Type`
  • but an expression that cannot be typed yields no hover
Flow Flow — HoverFallsBackToInferredType scroll to zoom · drag to pan
FEATUREHoverFallsBackToInferredTypefor AnalysisGIVENthe caret resting on a local bindingrather than a declarationWHENhover finds no declared symbol thereTHENthe binding's inferred type is shownas `name: Type`BUTan expression that cannot be typedyields no hover
ResolutionSurvivesPartialParse

Resolution survives a partial parse by working at the token level.

  • given a document that does not fully parse
  • when the caret rests on an identifier in a well-formed region
  • then resolution still tokenizes and resolves that identifier
  • but a caret on whitespace, punctuation, or an unknown name yields nothing
Flow Flow — ResolutionSurvivesPartialParse scroll to zoom · drag to pan
FEATUREResolutionSurvivesPartialParsefor AnalysisGIVENa document that does not fully parseWHENthe caret rests on an identifier in awell-formed regionTHENresolution still tokenizes andresolves that identifierBUTa caret on whitespace, punctuation, oran unknown name yields nothing
FormatOrLeaveUntouched

Whole-document format produces an edit, or nothing on a parse error.

  • given a document buffer
  • when a format edit is requested
  • then a parseable document yields one whole-document edit to canonical form
  • but an unparseable document yields no edit
Flow Flow — FormatOrLeaveUntouched scroll to zoom · drag to pan
FEATUREFormatOrLeaveUntouchedfor AnalysisGIVENa document bufferWHENa format edit is requestedTHENa parseable document yields onewhole-document edit to canonical formBUTan unparseable document yields no edit
component

Complete #

public
lsp_core::Complete

Completion: the candidates offered at a byte offset, resolved across the @@ -28,10 +28,10 @@ parser/checker's byte-span diagnostics; the code and its URL pass through unchanged.

Entities Entity diagram scroll to zoom · drag to pan
RECORDDiagnosticrangelsp_core::RangeseveritynumbermessagestringcodestringcodeDescriptionstringRECORDRangestartlsp_core::Positionendlsp_core::Position
data

Hover #

public
lsp_core::Hover

Markdown hover content the editor renders in a tooltip.

Entities Entity diagram scroll to zoom · drag to pan
RECORDHovercontentsstring
container

LspCore #

public
lsp_core::LspCore

`crates/pseudoscript-lsp-core`. The single home for language intelligence as transport-neutral handlers — the same logic the stdio server and the browser -wasm both call.

Inbound

Outbound

Scenarios

TransportNeutralHandlers

One implementation answers from current text, transport-free.

  • given a buffer's current text and a caret position
  • when a language query runs
  • then the answer is computed as a pure text-plus-position function
  • but no transport, async runtime, or tower-lsp is involved — both edges share it
Flow Flow — TransportNeutralHandlers scroll to zoom · drag to pan
FEATURETransportNeutralHandlersfor LspCoreGIVENa buffer's current text and a caretpositionWHENa language query runsTHENthe answer is computed as a puretext-plus-position functionBUTno transport, async runtime, ortower-lsp is involved — both edgesshare it
Components Component diagram scroll to zoom · drag to pan
PseudoscriptLspCoreCOMPONENTAnalysisDocument-level analysis: parse-and-checkdiagnostics, Markdown hover for the symbol…COMPONENTCompleteCompletion: the candidates offered at a byteoffset, resolved across the workspace and…COMPONENTConvertPosition conversion: the single home formapping between byte offsets and UTF-16…COMPONENTRefsReferences and rename: every occurrence ofthe symbol under the caret across the…COMPONENTSemanticSemantic tokens: AST-aware highlighting thatcolours each token by its declared role,…COMPONENTSymbolsDocument and workspace structure: theoutline symbols, the foldable ranges, and…CONTAINERLsp`crates/pseudoscript-lsp`. The stdiolanguage server (tower-lsp): workspace…CONTAINERModel`crates/pseudoscript-model`. AST to oneresolved graph; static checks (resolution,…check
data

Position #

public
lsp_core::Position

A zero-based caret position: line and UTF-16 character offset within the +wasm both call.

Inbound

Outbound

Scenarios

TransportNeutralHandlers

One implementation answers from current text, transport-free.

  • given a buffer's current text and a caret position
  • when a language query runs
  • then the answer is computed as a pure text-plus-position function
  • but no transport, async runtime, or tower-lsp is involved — both edges share it
Flow Flow — TransportNeutralHandlers scroll to zoom · drag to pan
FEATURETransportNeutralHandlersfor LspCoreGIVENa buffer's current text and a caretpositionWHENa language query runsTHENthe answer is computed as a puretext-plus-position functionBUTno transport, async runtime, ortower-lsp is involved — both edgesshare it
Components Component diagram scroll to zoom · drag to pan
PseudoscriptLspCoreCOMPONENTAnalysisDocument-level analysis: parse-and-checkdiagnostics, Markdown hover for the symbol…COMPONENTCompleteCompletion: the candidates offered at a byteoffset, resolved across the workspace and…COMPONENTConvertPosition conversion: the single home formapping between byte offsets and UTF-16…COMPONENTRefsReferences and rename: every occurrence ofthe symbol under the caret across the…COMPONENTSemanticSemantic tokens: AST-aware highlighting thatcolours each token by its declared role,…COMPONENTSymbolsDocument and workspace structure: theoutline symbols, the foldable ranges, and…CONTAINERLsp`crates/pseudoscript-lsp`. The stdiolanguage server (tower-lsp): workspace…CONTAINERModel`crates/pseudoscript-model`. AST to oneresolved graph; static checks (resolution,…check
data

Position #

public
lsp_core::Position

A zero-based caret position: line and UTF-16 character offset within the line. This and the shapes below model the standalone `lsp_types` vocabulary the crate returns (pinned to the version tower-lsp re-exports, wasm-safe), -so the server edge passes values through with no conversion.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPositionlinenumbercharacternumber
data

Range #

public
lsp_core::Range

A half-open text range between two positions.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDRangestartlsp_core::Positionendlsp_core::PositionRECORDPositionlinenumbercharacternumber
component

Refs #

public
lsp_core::Refs

References and rename: every occurrence of the symbol under the caret across +so the server edge passes values through with no conversion.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPositionlinenumbercharacternumber
data

Range #

public
lsp_core::Range

A half-open text range between two positions.

Entities Entity diagram scroll to zoom · drag to pan
RECORDRangestartlsp_core::Positionendlsp_core::PositionRECORDPositionlinenumbercharacternumber
component

Refs #

public
lsp_core::Refs

References and rename: every occurrence of the symbol under the caret across the workspace, the document highlights for it, and the workspace-wide rename edits.

Parent

Scenarios

RenameAcrossWorkspace

Rename rewrites every occurrence of one definition across the workspace.

  • given the caret on a renameable symbol
  • when rename runs
  • then every identifier resolving to the same definition is collected
  • and each occurrence is rewritten across all files that reference it
  • but a `::` module qualifier is never treated as an occurrence
Flow Flow — RenameAcrossWorkspace scroll to zoom · drag to pan
FEATURERenameAcrossWorkspacefor RefsGIVENthe caret on a renameable symbolWHENrename runsTHENevery identifier resolving to the samedefinition is collectedANDeach occurrence is rewritten acrossall files that reference itBUTa `::` module qualifier is nevertreated as an occurrence
component

Semantic #

public
lsp_core::Semantic

Semantic tokens: AST-aware highlighting that colours each token by its declared role, plus the legend the editor binds.

Parent

Scenarios

ColourByDeclaredRole

Semantic tokens colour identifiers by their declared role.

  • given a parsed module
  • when full semantic tokens are requested
  • then each identifier is coloured by its role — node, data, callable, parameter
  • and keywords, docs, and literals are coloured too
  • but overlapping, multi-line, or zero-width spans are dropped before encoding
Flow Flow — ColourByDeclaredRole scroll to zoom · drag to pan
FEATUREColourByDeclaredRolefor SemanticGIVENa parsed moduleWHENfull semantic tokens are requestedTHENeach identifier is coloured by itsrole — node, data, callable, parameterANDkeywords, docs, and literals arecoloured tooBUToverlapping, multi-line, or zero-widthspans are dropped before encoding
component

Symbols #

public
lsp_core::Symbols

Document and workspace structure: the outline symbols, the foldable ranges, diff --git a/model/site/module/model.html b/model/site/module/model.html index 93ccca44..1f5d67f6 100644 --- a/model/site/module/model.html +++ b/model/site/module/model.html @@ -11,7 +11,7 @@ -

PseudoScript
Module

model

data

Alt #

public
model::Alt
Entities Entity diagram scroll to zoom · drag to pan
RECORDAltcondLabelstringthenStepsmodel::Step[]elseStepsmodel::Step[]UNIONStepCallSelfCallReturnAltLoop
data

ArchRule #

public
model::ArchRule

One architectural-principle lint rule (LANG.md §9 — the C4 facade/boundary +

PseudoScript
Module

model

data

Alt #

public
model::Alt
Entities Entity diagram scroll to zoom · drag to pan
RECORDAltcondLabelstringthenStepsmodel::Step[]elseStepsmodel::Step[]UNIONStepCallSelfCallReturnAltLoop
data

ArchRule #

public
model::ArchRule

One architectural-principle lint rule (LANG.md §9 — the C4 facade/boundary principles). `code` is its stable identifier (`PDS-ARCH-001`); `slug` is the filename of the article documenting it under the published principles base URL. Every firing is a `Warning` — a violation advises rather than @@ -21,18 +21,18 @@ not an internal `component` (Facade/Gateway); the module dependency graph SHOULD stay acyclic; a cross-system call SHOULD couple to the other system's boundary, not its containers. Each firing is a `Warning` stamped with the rule's `code` -and `codeDescription` (its article URL); the model stays valid.

Parent

Inbound

Outbound

data

Branch #

public
model::Branch
Entities Entity diagram scroll to zoom · drag to pan
RECORDBranchnamestring
component

Builder #

private
model::Builder

Projects the parsed, resolved workspace into the architecture graph: structural +and `codeDescription` (its article URL); the model stays valid.

Parent

Inbound

Outbound

data

Branch #

public
model::Branch
Entities Entity diagram scroll to zoom · drag to pan
RECORDBranchnamestring
component

Builder #

private
model::Builder

Projects the parsed, resolved workspace into the architecture graph: structural nodes plus edges from `for` parents, body calls, triggers, and `from` provenance, with a sequence trace recorded per disclosed body. A pure projection — no I/O — so the emit crate and a future salsa/LSP layer can both -adopt it.

Parent

Inbound

Outbound

Scenarios

ResolveToOneGraph

One resolved graph feeds every view (§9).

  • given a parsed, resolved workspace
  • when the graph is built
  • then every structural node and callable becomes a graph node
  • and for-parent, call, trigger, and provenance edges are recorded
  • and each disclosed body carries an ordered sequence trace
Flow Flow — ResolveToOneGraph scroll to zoom · drag to pan
FEATUREResolveToOneGraphfor BuilderGIVENa parsed, resolved workspaceWHENthe graph is builtTHENevery structural node and callablebecomes a graph nodeANDfor-parent, call, trigger, andprovenance edges are recordedANDeach disclosed body carries an orderedsequence trace
data

Call #

public
model::Call
Entities Entity diagram scroll to zoom · drag to pan
RECORDCalltargetFqnstringmethodstring
component

CaretResolve #

public
model::CaretResolve

Caret resolution — the engine every cursor feature shares (`lsp_core` hover, +adopt it.

Parent

Inbound

Outbound

Scenarios

ResolveToOneGraph

One resolved graph feeds every view (§9).

  • given a parsed, resolved workspace
  • when the graph is built
  • then every structural node and callable becomes a graph node
  • and for-parent, call, trigger, and provenance edges are recorded
  • and each disclosed body carries an ordered sequence trace
Flow Flow — ResolveToOneGraph scroll to zoom · drag to pan
FEATUREResolveToOneGraphfor BuilderGIVENa parsed, resolved workspaceWHENthe graph is builtTHENevery structural node and callablebecomes a graph nodeANDfor-parent, call, trigger, andprovenance edges are recordedANDeach disclosed body carries an orderedsequence trace
data

Call #

public
model::Call
Entities Entity diagram scroll to zoom · drag to pan
RECORDCalltargetFqnstringmethodstring
component

CaretResolve #

public
model::CaretResolve

Caret resolution — the engine every cursor feature shares (`lsp_core` hover, definition, references, rename; the IDE's symbol actions). Tokenizes the active source, finds the identifier under the offset, and resolves it across -the workspace and its externals: a node or data FQN, a `self.`/member access, +the workspace and its externals: a node or data FQN, a same-node call or member access, or a local reference. Token-level, so it survives a partial parse.

Parent

component

Checks #

private
model::Checks

Static analysis (LANG.md §2.2, §2.3, §2.4, §3.3, §3.4, §3.5, §4, §5.1, §5.2, §6, §7, §8): builds the symbol tables, then runs the well-formedness checks. Every entry returns a `syntax::Diagnostic[]`; well-formed input yields an empty one. -Checks are conservative — each rule fires only when the violation is certain.

Parent

Inbound

Outbound

Scenarios

DiagnosticsAreData

Well-formed input yields no diagnostics: a check returns data, never throws.

  • given a well-formed module
  • when it is checked
  • then the returned diagnostic list is empty
  • but the check never throws or aborts
Flow Flow — DiagnosticsAreData scroll to zoom · drag to pan
FEATUREDiagnosticsAreDatafor ChecksGIVENa well-formed moduleWHENit is checkedTHENthe returned diagnostic list is emptyBUTthe check never throws or aborts
ErrorAttributedToReferrer

A cross-module visibility error is attributed to the referrer (§8.2).

  • given module B referencing a private node in module A
  • when the workspace is checked per module
  • then the diagnostic lands in module B, where the reference is written
  • but module A, the declarer, stays clean
Flow Flow — ErrorAttributedToReferrer scroll to zoom · drag to pan
FEATUREErrorAttributedToReferrerfor ChecksGIVENmodule B referencing a private node inmodule AWHENthe workspace is checked per moduleTHENthe diagnostic lands in module B,where the reference is writtenBUTmodule A, the declarer, stays clean
data

Commit #

public
model::Commit
Entities Entity diagram scroll to zoom · drag to pan
RECORDCommithashstring
component

Completion #

public
model::Completion

Context-aware completion (the model-native producer `lsp_core` maps to LSP +Checks are conservative — each rule fires only when the violation is certain.

Parent

Inbound

Outbound

Scenarios

DiagnosticsAreData

Well-formed input yields no diagnostics: a check returns data, never throws.

  • given a well-formed module
  • when it is checked
  • then the returned diagnostic list is empty
  • but the check never throws or aborts
Flow Flow — DiagnosticsAreData scroll to zoom · drag to pan
FEATUREDiagnosticsAreDatafor ChecksGIVENa well-formed moduleWHENit is checkedTHENthe returned diagnostic list is emptyBUTthe check never throws or aborts
ErrorAttributedToReferrer

A cross-module visibility error is attributed to the referrer (§8.2).

  • given module B referencing a private node in module A
  • when the workspace is checked per module
  • then the diagnostic lands in module B, where the reference is written
  • but module A, the declarer, stays clean
Flow Flow — ErrorAttributedToReferrer scroll to zoom · drag to pan
FEATUREErrorAttributedToReferrerfor ChecksGIVENmodule B referencing a private node inmodule AWHENthe workspace is checked per moduleTHENthe diagnostic lands in module B,where the reference is writtenBUTmodule A, the declarer, stays clean
data

Commit #

public
model::Commit
Entities Entity diagram scroll to zoom · drag to pan
RECORDCommithashstring
component

Completion #

public
model::Completion

Context-aware completion (the model-native producer `lsp_core` maps to LSP items): candidates at a caret offset resolved across the workspace and its externals — members after `.`, a module's public symbols after `::`, the built-in macros after `#[`, types in type position, else keywords and symbols.

Parent

data

ConstantType #

public
model::ConstantType

One `constant`'s FQN paired with its declared primitive type name (§3.6, @@ -77,11 +77,11 @@ statement block, and doc-comment runs.

Parent

data

Git #

public
model::Git
Entities Entity diagram scroll to zoom · drag to pan
RECORDGiturlstringselectormodel::RevsubstringUNIONRevTagBranchCommitDefault
data

Graph #

public
model::Graph

The resolved architecture graph: every node and typed edge across all C4 levels, plus feature scenarios. The single source every view projects from — enough to extract any view without re-reading source. Per-callable sequence -traces (`Step[]`) are held alongside, keyed by the owning callable's FQN.

Entities Entity diagram scroll to zoom · drag to pan
RECORDGraphnodesmodel::GraphNode[]edgesmodel::Edge[]scenariosmodel::Scenario[]RECORDGraphNodefqnstringnamestringkindmodel::NodeKindparentstringmodulestringvisibilitymodel::Visibilityspansyntax::Spantriggersmodel::Trigger[]signaturemodel::Signatureshapemodel::DataShapedocmodel::NodeDocRECORDEdgesourcestringtostringkindmodel::EdgeKindlabelstringspansyntax::SpanRECORDScenarionamestringtargetFqnstringmodulestringspansyntax::Spandocmodel::NodeDocstepsmodel::ScenarioStep[]
data

GraphNode #

public
model::GraphNode

One node in the resolved graph — a structural declaration, a `data` type, or a +traces (`Step[]`) are held alongside, keyed by the owning callable's FQN.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDGraphnodesmodel::GraphNode[]edgesmodel::Edge[]scenariosmodel::Scenario[]RECORDGraphNodefqnstringnamestringkindmodel::NodeKindparentstringmodulestringvisibilitymodel::Visibilityspansyntax::Spantriggersmodel::Trigger[]signaturemodel::Signatureshapemodel::DataShapedocmodel::NodeDocRECORDEdgesourcestringtostringkindmodel::EdgeKindlabelstringspansyntax::SpanRECORDScenarionamestringtargetFqnstringmodulestringspansyntax::Spandocmodel::NodeDocstepsmodel::ScenarioStep[]
data

GraphNode #

public
model::GraphNode

One node in the resolved graph — a structural declaration, a `data` type, or a callable. Carries its FQN, simple name, kind, enclosing-node FQN (`for` parent, owning node, or empty at top level), declaring module, visibility, name span, any trigger macros (callables only), the call signature (callables only), the -disclosed shape (`data` only), and lifted `///` documentation.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDGraphNodefqnstringnamestringkindmodel::NodeKindparentstringmodulestringvisibilitymodel::Visibilityspansyntax::Spantriggersmodel::Trigger[]signaturemodel::Signatureshapemodel::DataShapedocmodel::NodeDocUNIONNodeKindPersonSystemContainerComponentDataCallableUNIONVisibilityPublicPrivateRECORDSpanstartnumberendnumberUNIONTriggerOnEventScheduleHttpManualRECORDSignatureparamsmodel::SigParam[]retstringUNIONDataShapeRecordUnionBlackBoxRECORDNodeDocsummarystringextendedstringtagsstring[]
component

Inference #

public
model::Inference

Local-binding type inference, for inlay hints and hover. Assignments are +disclosed shape (`data` only), and lifted `///` documentation.

Entities Entity diagram scroll to zoom · drag to pan
RECORDGraphNodefqnstringnamestringkindmodel::NodeKindparentstringmodulestringvisibilitymodel::Visibilityspansyntax::Spantriggersmodel::Trigger[]signaturemodel::Signatureshapemodel::DataShapedocmodel::NodeDocUNIONNodeKindPersonSystemContainerComponentDataCallableUNIONVisibilityPublicPrivateRECORDSpanstartnumberendnumberUNIONTriggerOnEventScheduleHttpManualRECORDSignatureparamsmodel::SigParam[]retstringUNIONDataShapeRecordUnionBlackBoxRECORDNodeDocsummarystringextendedstringtagsstring[]
component

Inference #

public
model::Inference

Local-binding type inference, for inlay hints and hover. Assignments are untyped (`x = expr`); this infers each binding's type from its right-hand side — a call's return type, a field access, a `from` expression, or a literal. Best-effort: an un-typeable expression yields nothing rather than a guess.

Parent

data

Local #

public
model::Local
Entities Entity diagram scroll to zoom · drag to pan
RECORDLocalpathstring
data

Lock #

public
model::Lock

A parsed `pds.lock` (§8.4): the pinned dependency graph. A black box here — its @@ -98,10 +98,10 @@ (§5.1) or a `data` record field (§3.4).

Entities Entity diagram scroll to zoom · drag to pan
UNIONMemberKindCallableFieldRECORDFieldnamestringtysyntax::Typespansyntax::Span
container

Model #

public
model::Model

`crates/pseudoscript-model`. AST to one resolved graph; static checks (resolution, visibility, Result flow, return coverage); the §8.3 dependency resolver; and the LSP analysis primitives (completion, folding, semantic -tokens, inference). WASM-safe.

Inbound

Outbound

Components Component diagram scroll to zoom · drag to pan
PseudoscriptModelCOMPONENTResolverResolves a module's declarations into asymbol table (LANG.md §8). FQNs are…COMPONENTWorkspaceThe resolved set of modules keyed by FQN(LANG.md §8). Built once per workspace from…COMPONENTChecksStatic analysis (LANG.md §2.2, §2.3, §2.4,§3.3, §3.4, §3.5, §4, §5.1, §5.2, §6, §7,…COMPONENTCrossModule§8.2 — cross-module visibility resolution.Walks each module's qualified references —…COMPONENTArchitectureLANG.md §9 — architectural-principle lintsover the resolved graph. Beyond §8.2…COMPONENTResultFlow§6 — flow-sensitive `Result` / `Option`typestate. Each binding carries a state…COMPONENTReturnCoverageADR-016 — return coverage. A disclosednon-void callable MUST return on every path…COMPONENTMacroTargeting§2.4 / ADR-015 — macro targeting. Everybuilt-in macro (`onevent`, `schedule`,…COMPONENTParentKind§4 / ADR-010 — C4 parent-kindwell-formedness. A `for` parent links a nod…COMPONENTVariantCollision§3.5 / ADR-006 — union variant collision. Atop-level `data Name` followed by an inline…COMPONENTFeatureCheck§5.2 / §8.1 — feature checks. A feature's`for` target MUST resolve to a node, not a…COMPONENTReservedNames§2.3 / ADR-012 — reserved-word identifiers.A declared identifier MUST NOT be a reserve…COMPONENTTypeRefs§3.3 / §8.1 — type-reference resolution.Every named type in a declaration — a field…COMPONENTBuilderProjects the parsed, resolved workspace intothe architecture graph: structural nodes…COMPONENTDeps§8.3 — the dependency resolver. Parses amanifest's `[dependencies]` and a…COMPONENTCaretResolveCaret resolution — the engine every cursorfeature shares (`lsp_core` hover,…COMPONENTCompletionContext-aware completion (the model-nativeproducer `lsp_core` maps to LSP items):…COMPONENTFoldingThe foldable regions of a module: everymulti-line disclosed declaration and…COMPONENTSemanticAST-aware semantic colouring: eachidentifier classified by its declared role…COMPONENTInferenceLocal-binding type inference, for inlayhints and hover. Assignments are untyped (`…CONTAINERCli`crates/pseudoscript` — the binary crate(`pds`). The composition root and only I/O…CONTAINERIdeSession`crates/pseudoscript-ide` — the IDEapplication, Rust compiled to a single wasm…CONTAINERLsp`crates/pseudoscript-lsp`. The stdiolanguage server (tower-lsp): workspace…CONTAINERLspCore`crates/pseudoscript-lsp-core`. The singlehome for language intelligence as…CONTAINERSyntax`crates/pseudoscript-syntax`. The foundationcrate: source text to tokens and a typed…CONTAINERUniverse`crates/pseudoscript-universe`. Maps theresolved C4 model into the software graph…runStdiorenderTokensbuildflowssnapshotdefinitionformatEdithoverbuildcheckgraphOfcheckcheckcheckbuildcheckcheckbuildWithExternalsparse
data

ModuleDiagnostics #

public
model::ModuleDiagnostics

One workspace module's diagnostics, attributed to its FQN. Every span lies in +tokens, inference). WASM-safe.

Inbound

Outbound

Components Component diagram scroll to zoom · drag to pan
PseudoscriptModelCOMPONENTResolverResolves a module's declarations into asymbol table (LANG.md §8). FQNs are…COMPONENTWorkspaceThe resolved set of modules keyed by FQN(LANG.md §8). Built once per workspace from…COMPONENTChecksStatic analysis (LANG.md §2.2, §2.3, §2.4,§3.3, §3.4, §3.5, §4, §5.1, §5.2, §6, §7,…COMPONENTCrossModule§8.2 — cross-module visibility resolution.Walks each module's qualified references —…COMPONENTArchitectureLANG.md §9 — architectural-principle lintsover the resolved graph. Beyond §8.2…COMPONENTResultFlow§6 — flow-sensitive `Result` / `Option`typestate. Each binding carries a state…COMPONENTReturnCoverageADR-016 — return coverage. A disclosednon-void callable MUST return on every path…COMPONENTMacroTargeting§2.4 / ADR-015 — macro targeting. Everybuilt-in macro (`onevent`, `schedule`,…COMPONENTParentKind§4 / ADR-010 — C4 parent-kindwell-formedness. A `for` parent links a nod…COMPONENTVariantCollision§3.5 / ADR-006 — union variant collision. Atop-level `data Name` followed by an inline…COMPONENTFeatureCheck§5.2 / §8.1 — feature checks. A feature's`for` target MUST resolve to a node, not a…COMPONENTReservedNames§2.3 / ADR-012 — reserved-word identifiers.A declared identifier MUST NOT be a reserve…COMPONENTTypeRefs§3.3 / §8.1 — type-reference resolution.Every named type in a declaration — a field…COMPONENTBuilderProjects the parsed, resolved workspace intothe architecture graph: structural nodes…COMPONENTDeps§8.3 — the dependency resolver. Parses amanifest's `[dependencies]` and a…COMPONENTCaretResolveCaret resolution — the engine every cursorfeature shares (`lsp_core` hover,…COMPONENTCompletionContext-aware completion (the model-nativeproducer `lsp_core` maps to LSP items):…COMPONENTFoldingThe foldable regions of a module: everymulti-line disclosed declaration and…COMPONENTSemanticAST-aware semantic colouring: eachidentifier classified by its declared role…COMPONENTInferenceLocal-binding type inference, for inlayhints and hover. Assignments are untyped (`…CONTAINERCli`crates/pseudoscript` — the binary crate(`pds`). The composition root and only I/O…CONTAINERIdeSession`crates/pseudoscript-ide` — the IDEapplication, Rust compiled to a single wasm…CONTAINERLspCore`crates/pseudoscript-lsp-core`. The singlehome for language intelligence as…CONTAINERSyntax`crates/pseudoscript-syntax`. The foundationcrate: source text to tokens and a typed…CONTAINERUniverse`crates/pseudoscript-universe`. Maps theresolved C4 model into the software graph…renderTokensbuildflowssnapshotbuildcheckgraphOfcheckcheckcheckbuildcheckcheckbuildWithExternalsparse
data

ModuleDiagnostics #

public
model::ModuleDiagnostics

One workspace module's diagnostics, attributed to its FQN. Every span lies in that module's own source, so a tool with the FQN-to-file mapping publishes each list against the right file.

Entities Entity diagram scroll to zoom · drag to pan
RECORDModuleDiagnosticsfqnstringdiagnosticssyntax::Diagnostic[]RECORDDiagnosticseveritysyntax::Severityspansyntax::SpancodestringcodeDescriptionstringmessagestring
data

ModuleEntry #

public
model::ModuleEntry

One module's parsed AST, resolved symbol table, and FQN, as held by a -`Workspace`.

Entities Entity diagram scroll to zoom · drag to pan
RECORDModuleEntryfqnstringastsyntax::Modulemodelmodel::SymbolTableRECORDModuleinnerDocssyntax::InnerDoc[]itemssyntax::Item[]spansyntax::SpanRECORDSymbolTablemodulePathstringsymbolsmodel::Symbol[]membersmodel::Member[]unionVariantsmodel::FieldlessVariants[]constantTypesmodel::ConstantType[]
data

NodeDoc #

public
model::NodeDoc

A node's documentation, lifted from its `///` block (LANG.md §2.1): the +`Workspace`.

Entities Entity diagram scroll to zoom · drag to pan
RECORDModuleEntryfqnstringastsyntax::Modulemodelmodel::SymbolTableRECORDModuleinnerDocssyntax::InnerDoc[]itemssyntax::Item[]spansyntax::SpanRECORDSymbolTablemodulePathstringsymbolsmodel::Symbol[]membersmodel::Member[]unionVariantsmodel::FieldlessVariants[]constantTypesmodel::ConstantType[]
data

NodeDoc #

public
model::NodeDoc

A node's documentation, lifted from its `///` block (LANG.md §2.1): the summary (compact-diagram text), the extended description (tooltip text), and its tags (` `, including the `#`), in source order.

#name
Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeDocsummarystringextendedstringtagsstring[]
data

NodeKind #

public
model::NodeKind

What kind of model element a `GraphNode` is. Adds `Callable` to the structural @@ -142,7 +142,7 @@ the declaring module's FQN, the span of the feature name (a host's go-to-definition target), lifted `///` documentation, and the ordered given/when/then steps. Steps are prose, not resolved, so a scenario adds no -edges; it renders as a card on its target node's doc page.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDScenarionamestringtargetFqnstringmodulestringspansyntax::Spandocmodel::NodeDocstepsmodel::ScenarioStep[]RECORDSpanstartnumberendnumberRECORDNodeDocsummarystringextendedstringtagsstring[]RECORDScenarioStepkeywordstringtextstring
data

ScenarioStep #

public
model::ScenarioStep

One step of a `Scenario` (LANG.md §5.2): a keyword (`given`/`when`/`then`/ +edges; it renders as a card on its target node's doc page.

Entities Entity diagram scroll to zoom · drag to pan
RECORDScenarionamestringtargetFqnstringmodulestringspansyntax::Spandocmodel::NodeDocstepsmodel::ScenarioStep[]RECORDSpanstartnumberendnumberRECORDNodeDocsummarystringextendedstringtagsstring[]RECORDScenarioStepkeywordstringtextstring
data

ScenarioStep #

public
model::ScenarioStep

One step of a `Scenario` (LANG.md §5.2): a keyword (`given`/`when`/`then`/ `and`/`but`) and its prose, with the string literal's quotes stripped.

Entities Entity diagram scroll to zoom · drag to pan
RECORDScenarioStepkeywordstringtextstring
data

SelfCall #

public
model::SelfCall
Entities Entity diagram scroll to zoom · drag to pan
RECORDSelfCallmethodstring
component

Semantic #

public
model::Semantic

AST-aware semantic colouring: each identifier classified by its declared role (node→namespace, `data`→class, callable→method, parameter, call segment), the spans `lsp_core` delta-encodes for the editor.

Parent

data

SigParam #

public
model::SigParam

One parameter of a callable signature: its name and rendered type.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSigParamnamestringtystring
data

Signature #

public
model::Signature

A callable node's signature (§5.1): its ordered parameters and rendered return @@ -170,10 +170,10 @@ variants reference existing data and never collide.

Parent

Inbound

Outbound

Scenarios

VariantNameCollision

An inline union variant colliding with a prior data name is rejected (§3.5).

  • given a top-level `data Name` and an inline variant `| Name { ... }`
  • when the module is checked
  • then the colliding inline variant is rejected
  • but a bare `| Name` referencing the existing data is accepted
Flow Flow — VariantNameCollision scroll to zoom · drag to pan
FEATUREVariantNameCollisionfor VariantCollisionGIVENa top-level `data Name` and an inlinevariant `| Name { ... }`WHENthe module is checkedTHENthe colliding inline variant isrejectedBUTa bare `| Name` referencing theexisting data is accepted
data

Visibility #

public
model::Visibility

Whether a node is `public` (cross-module addressable) or module-private (LANG.md §8.2).

Entities Entity diagram scroll to zoom · drag to pan
UNIONVisibilityPublicPrivateRECORDPublicsymbolmodel::SymbolRECORDPrivatesymbolmodel::Symbol
component

Workspace #

public
model::Workspace

The resolved set of modules keyed by FQN (LANG.md §8). Built once per workspace from `(fqn, parsed module)` pairs; owns the per-module models and a -global FQN-to-`Symbol` index for cross-module resolution.

Parent

Inbound

Outbound

Scenarios

SameModuleAlwaysResolves

A same-module reference resolves regardless of visibility (§8.2).

  • given a reference whose target is declared in the same module
  • when the reference is resolved
  • then it resolves whether the target is public or private
Flow Flow — SameModuleAlwaysResolves scroll to zoom · drag to pan
FEATURESameModuleAlwaysResolvesfor WorkspaceGIVENa reference whose target is declaredin the same moduleWHENthe reference is resolvedTHENit resolves whether the target ispublic or private
data

WorkspaceModule #

public
model::WorkspaceModule

One `.pds` module of a workspace: its caller-supplied FQN (the loader derives +global FQN-to-`Symbol` index for cross-module resolution.

Parent

Inbound

Outbound

Scenarios

SameModuleAlwaysResolves

A same-module reference resolves regardless of visibility (§8.2).

  • given a reference whose target is declared in the same module
  • when the reference is resolved
  • then it resolves whether the target is public or private
Flow Flow — SameModuleAlwaysResolves scroll to zoom · drag to pan
FEATURESameModuleAlwaysResolvesfor WorkspaceGIVENa reference whose target is declaredin the same moduleWHENthe reference is resolvedTHENit resolves whether the target ispublic or private
data

WorkspaceModule #

public
model::WorkspaceModule

One `.pds` module of a workspace: its caller-supplied FQN (the loader derives it from the file path relative to `pds.toml`, LANG.md §8.1) and its source text. This crate stays pure over in-memory modules; it never touches the -filesystem.

Entities Entity diagram scroll to zoom · drag to pan
RECORDWorkspaceModulefqnstringsourcestring
Generated by pds doc.
+filesystem.

Entities Entity diagram scroll to zoom · drag to pan
RECORDWorkspaceModulefqnstringsourcestring
Generated by pds doc.
diff --git a/model/site/module/project.html b/model/site/module/project.html index 777c34d0..6c5dabe1 100644 --- a/model/site/module/project.html +++ b/model/site/module/project.html @@ -11,14 +11,14 @@ -
PseudoScript
Module

project

component

Deps #

public
project::Deps

Resolves a workspace's declared dependencies into modules the checker binds as +

PseudoScript
Module

project

component

Deps #

public
project::Deps

Resolves a workspace's declared dependencies into modules the checker binds as externals (§8.3): reads `pds.lock` + `pds_modules/`, materialises each direct dependency's `.pds` modules under its dependency-name prefix, and resolves a local path dependency's directory.

Parent

Scenarios

DependenciesLoadAsExternals

Dependencies load as prefixed externals for cross-workspace resolution.

  • given a workspace declaring a git or local dependency, vendored under pds_modules/
  • when its dependency modules are read
  • then each direct dependency's .pds modules load under the dependency-name prefix (§8.3)
  • but the dependency's own pds_modules/ are not re-exported
Flow Flow — DependenciesLoadAsExternals scroll to zoom · drag to pan
FEATUREDependenciesLoadAsExternalsfor DepsGIVENa workspace declaring a git or localdependency, vendored underpds_modules/WHENits dependency modules are readTHENeach direct dependency's .pds modulesload under the dependency-name prefix(§8.3)BUTthe dependency's own pds_modules/ arenot re-exported
data

IoError #

public
project::IoError

A filesystem failure surfaced to the caller: the path or manifest that could not be read, with a human-readable message.

Entities Entity diagram scroll to zoom · drag to pan
RECORDIoErrormessagestring
component

Loader #

private
project::Loader

Resolves and reads a workspace off disk: finds the root by walking up to the nearest `pds.toml`, then walks the tree for visible `.pds` files and loads each as a `(fqn, source)` module — mapping its path to a flat module FQN (§8.1) and -skipping `target/` and `pds_modules/`.

Parent

Inbound

Outbound

  • from project::Result
  • from project::Result

Scenarios

FindWorkspaceRoot

The root is the nearest ancestor holding `pds.toml`.

  • given a path inside a workspace directory tree
  • when the root is resolved
  • then the nearest ancestor directory holding a pds.toml is the root
  • but a tree with no pds.toml up to the filesystem root fails with an IoError
Flow Flow — FindWorkspaceRoot scroll to zoom · drag to pan
FEATUREFindWorkspaceRootfor LoaderGIVENa path inside a workspace directorytreeWHENthe root is resolvedTHENthe nearest ancestor directory holdinga pds.toml is the rootBUTa tree with no pds.toml up to thefilesystem root fails with an IoError
LoadModulesSkippingBuildAndVendor

The walk loads `.pds` modules and skips build and vendor directories.

  • given a workspace whose tree includes target/ and pds_modules/ directories
  • when its modules are loaded
  • then every visible .pds file becomes a (fqn, source) module
  • and each file's path maps to its flat module FQN (§8.1)
  • but files under target/ and pds_modules/ are skipped
Flow Flow — LoadModulesSkippingBuildAndVendor scroll to zoom · drag to pan
FEATURELoadModulesSkippingBuildAndVendorfor LoaderGIVENa workspace whose tree includestarget/ and pds_modules/ directoriesWHENits modules are loadedTHENevery visible .pds file becomes a(fqn, source) moduleANDeach file's path maps to its flatmodule FQN (§8.1)BUTfiles under target/ and pds_modules/are skipped
container

Project #

public
project::Project

`crates/pseudoscript-project`. The disk-facing loader: resolves the workspace +skipping `target/` and `pds_modules/`.

Parent

Inbound

Scenarios

FindWorkspaceRoot

The root is the nearest ancestor holding `pds.toml`.

  • given a path inside a workspace directory tree
  • when the root is resolved
  • then the nearest ancestor directory holding a pds.toml is the root
  • but a tree with no pds.toml up to the filesystem root fails with an IoError
Flow Flow — FindWorkspaceRoot scroll to zoom · drag to pan
FEATUREFindWorkspaceRootfor LoaderGIVENa path inside a workspace directorytreeWHENthe root is resolvedTHENthe nearest ancestor directory holdinga pds.toml is the rootBUTa tree with no pds.toml up to thefilesystem root fails with an IoError
LoadModulesSkippingBuildAndVendor

The walk loads `.pds` modules and skips build and vendor directories.

  • given a workspace whose tree includes target/ and pds_modules/ directories
  • when its modules are loaded
  • then every visible .pds file becomes a (fqn, source) module
  • and each file's path maps to its flat module FQN (§8.1)
  • but files under target/ and pds_modules/ are skipped
Flow Flow — LoadModulesSkippingBuildAndVendor scroll to zoom · drag to pan
FEATURELoadModulesSkippingBuildAndVendorfor LoaderGIVENa workspace whose tree includestarget/ and pds_modules/ directoriesWHENits modules are loadedTHENevery visible .pds file becomes a(fqn, source) moduleANDeach file's path maps to its flatmodule FQN (§8.1)BUTfiles under target/ and pds_modules/are skipped
container

Project #

public
project::Project

`crates/pseudoscript-project`. The disk-facing loader: resolves the workspace root, reads its `.pds` modules, and materialises dependency modules from the vendor directory. The only place the native toolchain touches the filesystem for project loading.

Inbound

Outbound

Components Component diagram scroll to zoom · drag to pan
PseudoscriptProjectCOMPONENTLoaderResolves and reads a workspace off disk:finds the root by walking up to the nearest…COMPONENTDepsResolves a workspace's declared dependenciesinto modules the checker binds as externals…CONTAINERCli`crates/pseudoscript` — the binary crate(`pds`). The composition root and only I/O…
Generated by pds doc.
diff --git a/model/site/module/syntax.html b/model/site/module/syntax.html index 31ba2e03..3cdfb84c 100644 --- a/model/site/module/syntax.html +++ b/model/site/module/syntax.html @@ -11,7 +11,7 @@ -
PseudoScript
Module

syntax

data

Assign #

public
syntax::Assign
Entities Entity diagram scroll to zoom · drag to pan
RECORDAssignnamestringvaluesyntax::ExprRECORDExprkindsyntax::ExprKindspansyntax::Span
data

BinOp #

public
syntax::BinOp

A binary operator (§7.5): arithmetic, comparison, equality, and boolean.

Entities Entity diagram scroll to zoom · drag to pan
UNIONBinOpAddSubMulDivRemEqNeLtGtLeGeAndOr
data

Binary #

public
syntax::Binary
Entities Entity diagram scroll to zoom · drag to pan
RECORDBinaryleftsyntax::Expropsyntax::BinOpopSpansyntax::Spanrightsyntax::ExprRECORDExprkindsyntax::ExprKindspansyntax::SpanUNIONBinOpAddSubMulDivRemEqNeLtGtLeGeAndOrRECORDSpanstartnumberendnumber
data

BlankLines #

public
syntax::BlankLines
Entities Entity diagram scroll to zoom · drag to pan
RECORDBlankLinescountnumber
data

Block #

public
syntax::Block

A `{ ... }` statement block (§7): statements in order, plus the brace span.

Entities Entity diagram scroll to zoom · drag to pan
RECORDBlockstmtssyntax::Stmt[]spansyntax::SpanRECORDStmtkindsyntax::StmtKindleadingTriviasyntax::SpannedTrivia[]spansyntax::SpanRECORDSpanstartnumberendnumber
data

BlockComment #

public
syntax::BlockComment
Entities Entity diagram scroll to zoom · drag to pan
RECORDBlockCommenttextstring
data

BodyMember #

public
syntax::BodyMember

A member of a disclosed node body: a callable, or — under error recovery — +

PseudoScript
Module

syntax

data

Assign #

public
syntax::Assign
Entities Entity diagram scroll to zoom · drag to pan
RECORDAssignnamestringvaluesyntax::ExprRECORDExprkindsyntax::ExprKindspansyntax::Span
data

BinOp #

public
syntax::BinOp

A binary operator (§7.5): arithmetic, comparison, equality, and boolean.

Entities Entity diagram scroll to zoom · drag to pan
UNIONBinOpAddSubMulDivRemEqNeLtGtLeGeAndOr
data

Binary #

public
syntax::Binary
Entities Entity diagram scroll to zoom · drag to pan
RECORDBinaryleftsyntax::Expropsyntax::BinOpopSpansyntax::Spanrightsyntax::ExprRECORDExprkindsyntax::ExprKindspansyntax::SpanUNIONBinOpAddSubMulDivRemEqNeLtGtLeGeAndOrRECORDSpanstartnumberendnumber
data

BlankLines #

public
syntax::BlankLines
Entities Entity diagram scroll to zoom · drag to pan
RECORDBlankLinescountnumber
data

Block #

public
syntax::Block

A `{ ... }` statement block (§7): statements in order, plus the brace span.

Entities Entity diagram scroll to zoom · drag to pan
RECORDBlockstmtssyntax::Stmt[]spansyntax::SpanRECORDStmtkindsyntax::StmtKindleadingTriviasyntax::SpannedTrivia[]spansyntax::SpanRECORDSpanstartnumberendnumber
data

BlockComment #

public
syntax::BlockComment
Entities Entity diagram scroll to zoom · drag to pan
RECORDBlockCommenttextstring
data

BodyMember #

public
syntax::BodyMember

A member of a disclosed node body: a callable, or — under error recovery — a nested structural declaration the parser flags as illegal (ADR-011).

Entities Entity diagram scroll to zoom · drag to pan
UNIONBodyMemberCallableMemberDeclMemberRECORDCallableMembercallablesyntax::CallableDeclRECORDDeclMemberdeclsyntax::Declaration
data

BoolLit #

public
syntax::BoolLit
Entities Entity diagram scroll to zoom · drag to pan
RECORDBoolLitvalueboolspansyntax::SpanRECORDSpanstartnumberendnumber
data

CallableDecl #

public
syntax::CallableDecl

A callable (implicit operation) declared inside a disclosed node (§5.1). `returnTy` is required (ADR-040) — a missing return type is a syntax error, recovered as `void`; `body` absent means a black box (`;`).

Entities Entity diagram scroll to zoom · drag to pan
RECORDCallableDecldocsyntax::DocBlockmacrossyntax::Macro[]isPublicboolnamestringparamssyntax::Param[]returnTysyntax::Typebodysyntax::BlockleadingTriviasyntax::SpannedTrivia[]spansyntax::SpanRECORDDocBlocksummarystring[]extendedstring[]tagssyntax::Tag[]RECORDMacronamesyntax::Pathargssyntax::MacroArgsspansyntax::SpanRECORDParamnamestringtysyntax::Typespansyntax::SpanRECORDTypenamesyntax::Pathgenericssyntax::Type[]isArrayboolspansyntax::SpanRECORDBlockstmtssyntax::Stmt[]spansyntax::SpanRECORDSpannedTriviatriviasyntax::Triviaspansyntax::SpanRECORDSpanstartnumberendnumber
data

CallableMember #

public
syntax::CallableMember
Entities Entity diagram scroll to zoom · drag to pan
RECORDCallableMembercallablesyntax::CallableDeclRECORDCallableDecldocsyntax::DocBlockmacrossyntax::Macro[]isPublicboolnamestringparamssyntax::Param[]returnTysyntax::Typebodysyntax::BlockleadingTriviasyntax::SpannedTrivia[]spansyntax::Span
data

Compose #

public
syntax::Compose
Entities Entity diagram scroll to zoom · drag to pan
RECORDComposesourcessyntax::Expr[]RECORDExprkindsyntax::ExprKindspansyntax::Span
data

Constant #

public
syntax::Constant
Entities Entity diagram scroll to zoom · drag to pan
RECORDConstantdeclsyntax::ConstantDeclRECORDConstantDeclnamestringvaluesyntax::Literalspansyntax::Span
data

ConstantDecl #

public
syntax::ConstantDecl

A `constant NAME = Literal` declaration (§3.6, ADR-039): a top-level primitive @@ -24,25 +24,25 @@ tooling; the human-facing text is `message`. `codeDescription` is an optional URL the code resolves to — an article explaining the rule — which the LSP surfaces as the diagnostic's clickable link. Empty when the code carries no -article.

Entities Entity diagram scroll to zoom · drag to pan
RECORDDiagnosticseveritysyntax::Severityspansyntax::SpancodestringcodeDescriptionstringmessagestringUNIONSeverityErrorWarningInfoRECORDSpanstartnumberendnumber
data

DocBlock #

public
syntax::DocBlock

A `///` doc block split into summary and extended on the first blank `///` +article.

Entities Entity diagram scroll to zoom · drag to pan
RECORDDiagnosticseveritysyntax::Severityspansyntax::SpancodestringcodeDescriptionstringmessagestringUNIONSeverityErrorWarningInfoRECORDSpanstartnumberendnumber
data

DocBlock #

public
syntax::DocBlock

A `///` doc block split into summary and extended on the first blank `///` line (ADR-009): summary feeds compact diagrams, extended feeds tooltips, tags carry the ` -` markers.

#name
Entities Entity diagram scroll to zoom · drag to pan
RECORDDocBlocksummarystring[]extendedstring[]tagssyntax::Tag[]RECORDTagtextstringspansyntax::Span
data

Expr #

public
syntax::Expr

An expression (§7, §10): its form and source span.

Entities Entity diagram scroll to zoom · drag to pan
RECORDExprkindsyntax::ExprKindspansyntax::SpanUNIONExprKindMarkerFromPostfixRefLitUnaryBinaryParenRECORDSpanstartnumberendnumber
data

ExprKind #

public
syntax::ExprKind

The expression forms (§10). `Marker` is a built-in generic constructor — +` markers.

#name
Entities Entity diagram scroll to zoom · drag to pan
RECORDDocBlocksummarystring[]extendedstring[]tagssyntax::Tag[]RECORDTagtextstringspansyntax::Span
data

Expr #

public
syntax::Expr

An expression (§7, §10): its form and source span.

Entities Entity diagram scroll to zoom · drag to pan
RECORDExprkindsyntax::ExprKindspansyntax::SpanUNIONExprKindMarkerFromPostfixOwnCallRefLitUnaryBinaryParenRECORDSpanstartnumberendnumber
data

ExprKind #

public
syntax::ExprKind

The expression forms (§10). `Marker` is a built-in generic constructor — `Ok`/`Err` (`Result`) or `Some`/`None` (`Option`); `From` names a target type and its `source`, whose `FromSource` form is the §7.2 vs §7.4 split; -`Postfix` is a `.name`/`.name(..)` access chain (ADR-007); `Unary`/`Binary` -are the §7.5 operator forms. A `Marker` or `From` head is never a binary -operand (§7.5).

Entities Entity diagram scroll to zoom · drag to pan
UNIONExprKindMarkerFromPostfixRefLitUnaryBinaryParenRECORDMarkerkindsyntax::MarkerKindpayloadsyntax::ExprRECORDFromtysyntax::Typesourcesyntax::FromSourceRECORDPostfixbasesyntax::Exprsegmentssyntax::PostfixSeg[]RECORDReftargetsyntax::ReferenceRECORDLitvaluesyntax::LiteralRECORDUnaryopsyntax::UnaryOpopSpansyntax::Spanexprsyntax::ExprRECORDBinaryleftsyntax::Expropsyntax::BinOpopSpansyntax::Spanrightsyntax::ExprRECORDPareninnersyntax::Expr
data

ExprStmt #

public
syntax::ExprStmt
Entities Entity diagram scroll to zoom · drag to pan
RECORDExprStmtexprsyntax::ExprRECORDExprkindsyntax::ExprKindspansyntax::Span
data

FeatureDecl #

public
syntax::FeatureDecl

A `feature Name for Path { given* when+ then+ }` BDD scenario (§5.2). Steps +`Postfix` is a `.name`/`.name(..)` access chain (ADR-007); `OwnCall` is a +bare same-node call `Name(args)` (§5.1, ADR-041); `Unary`/`Binary` are the +§7.5 operator forms. A `Marker` or `From` head is never a binary operand (§7.5).

Entities Entity diagram scroll to zoom · drag to pan
UNIONExprKindMarkerFromPostfixOwnCallRefLitUnaryBinaryParenRECORDMarkerkindsyntax::MarkerKindpayloadsyntax::ExprRECORDFromtysyntax::Typesourcesyntax::FromSourceRECORDPostfixbasesyntax::Exprsegmentssyntax::PostfixSeg[]RECORDOwnCallnamestringargssyntax::Expr[]RECORDReftargetsyntax::ReferenceRECORDLitvaluesyntax::LiteralRECORDUnaryopsyntax::UnaryOpopSpansyntax::Spanexprsyntax::ExprRECORDBinaryleftsyntax::Expropsyntax::BinOpopSpansyntax::Spanrightsyntax::ExprRECORDPareninnersyntax::Expr
data

ExprStmt #

public
syntax::ExprStmt
Entities Entity diagram scroll to zoom · drag to pan
RECORDExprStmtexprsyntax::ExprRECORDExprkindsyntax::ExprKindspansyntax::Span
data

FeatureDecl #

public
syntax::FeatureDecl

A `feature Name for Path { given* when+ then+ }` BDD scenario (§5.2). Steps are prose, not resolved against the model; the strict given→when→then order is enforced by the parser. A feature takes no macros and no `public`.

Entities Entity diagram scroll to zoom · drag to pan
RECORDFeatureDecldocsyntax::DocBlocknamestringtargetsyntax::Pathstepssyntax::FeatureStep[]leadingTriviasyntax::SpannedTrivia[]spansyntax::SpanRECORDDocBlocksummarystring[]extendedstring[]tagssyntax::Tag[]RECORDPathsegmentsstring[]spansyntax::SpanRECORDFeatureStepkindsyntax::StepKindtextstringspansyntax::SpanRECORDSpannedTriviatriviasyntax::Triviaspansyntax::SpanRECORDSpanstartnumberendnumber
data

FeatureItem #

public
syntax::FeatureItem
Entities Entity diagram scroll to zoom · drag to pan
RECORDFeatureItemdeclsyntax::FeatureDeclRECORDFeatureDecldocsyntax::DocBlocknamestringtargetsyntax::Pathstepssyntax::FeatureStep[]leadingTriviasyntax::SpannedTrivia[]spansyntax::Span
data

FeatureStep #

public
syntax::FeatureStep

One step line in a feature flow: a step keyword and its prose string (§5.2).

Entities Entity diagram scroll to zoom · drag to pan
RECORDFeatureStepkindsyntax::StepKindtextstringspansyntax::SpanUNIONStepKindGivenWhenThenAndButRECORDSpanstartnumberendnumber
data

Field #

public
syntax::Field

A record field `name: Type`.

Entities Entity diagram scroll to zoom · drag to pan
RECORDFieldnamestringtysyntax::Typespansyntax::SpanRECORDTypenamesyntax::Pathgenericssyntax::Type[]isArrayboolspansyntax::SpanRECORDSpanstartnumberendnumber
data

For #

public
syntax::For
Entities Entity diagram scroll to zoom · drag to pan
RECORDForbindingstringitersyntax::Exprbodysyntax::BlockRECORDExprkindsyntax::ExprKindspansyntax::SpanRECORDBlockstmtssyntax::Stmt[]spansyntax::Span
data

From #

public
syntax::From
Entities Entity diagram scroll to zoom · drag to pan
RECORDFromtysyntax::Typesourcesyntax::FromSourceRECORDTypenamesyntax::Pathgenericssyntax::Type[]isArrayboolspansyntax::SpanUNIONFromSourceComposeConvert
data

FromSource #

public
syntax::FromSource

The source of a `from` expression (§7.2, §7.4, ADR-035) — the parser branches on the leading token, so the two forms are distinct: a `{` opens `Compose`, a set the target `data` record/variant is built from; anything else is `Convert`, carrying the target type onto one value.

Entities Entity diagram scroll to zoom · drag to pan
UNIONFromSourceComposeConvertRECORDComposesourcessyntax::Expr[]RECORDConvertvaluesyntax::Expr
data

If #

public
syntax::If
Entities Entity diagram scroll to zoom · drag to pan
RECORDIfcondsyntax::ExprthenBlocksyntax::BlockelseBlocksyntax::BlockRECORDExprkindsyntax::ExprKindspansyntax::SpanRECORDBlockstmtssyntax::Stmt[]spansyntax::Span
data

InnerDoc #

public
syntax::InnerDoc

One `//!` inner-doc line documenting the module (§2.1).

Entities Entity diagram scroll to zoom · drag to pan
RECORDInnerDoctextstringspansyntax::SpanRECORDSpanstartnumberendnumber
data

Item #

public
syntax::Item

A top-level item: a documented structural declaration, or a `feature` BDD scenario.

Entities Entity diagram scroll to zoom · drag to pan
UNIONItemDeclItemFeatureItemRECORDDeclItemdeclsyntax::DeclarationRECORDFeatureItemdeclsyntax::FeatureDecl
data

Lexed #

public
syntax::Lexed

The full result of lexing: the conformance token stream plus interleaved -trivia for the formatter.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDLexedtokenssyntax::Token[]triviasyntax::SpannedTrivia[]RECORDTokenkindsyntax::TokenKindspansyntax::SpantextstringRECORDSpannedTriviatriviasyntax::Triviaspansyntax::Span
component

Lexer #

private
syntax::Lexer

Hand-written lexer for LANG.md §2. One pass yields the conformance token +trivia for the formatter.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDLexedtokenssyntax::Token[]triviasyntax::SpannedTrivia[]RECORDTokenkindsyntax::TokenKindspansyntax::SpantextstringRECORDSpannedTriviatriviasyntax::Triviaspansyntax::Span
component

Lexer #

private
syntax::Lexer

Hand-written lexer for LANG.md §2. One pass yields the conformance token stream (comments discarded, `///`→`Doc`+`Tag`, `//!`→`InnerDoc`) and full-fidelity trivia for the formatter. Infallible: lexical anomalies surface -as parser diagnostics, never a failed lex.

#critical

Parent

Inbound

Outbound

Scenarios

LexerNeverStalls

The lexer is greedy and never stalls on bad input.

  • given source containing an unknown byte or an unterminated string
  • when the lexer scans it
  • then the offending bytes are skipped or the string is closed at end of line
  • and lexing reaches the end of input
Flow Flow — LexerNeverStalls scroll to zoom · drag to pan
FEATURELexerNeverStallsfor LexerGIVENsource containing an unknown byte oran unterminated stringWHENthe lexer scans itTHENthe offending bytes are skipped or thestring is closed at end of lineANDlexing reaches the end of input
GreedyKeywordMatch

The longest identifier wins, then a keyword lookup classifies it.

  • given an identifier-start byte
  • when the lexer consumes the run of identifier bytes
  • then a reserved spelling lexes as its keyword kind
  • but a primitive name or `Result` stays an identifier, classified later
Flow Flow — GreedyKeywordMatch scroll to zoom · drag to pan
FEATUREGreedyKeywordMatchfor LexerGIVENan identifier-start byteWHENthe lexer consumes the run ofidentifier bytesTHENa reserved spelling lexes as itskeyword kindBUTa primitive name or `Result` stays anidentifier, classified later
DocCommentDisambiguation

A leading `/` resolves to a comment, doc, or inner doc by its run length.

  • given a line beginning with two or more slashes
  • when the lexer reads the marker
  • then `//!` becomes an inner-doc token
  • and `///` becomes a doc line of prose and tag tokens
  • but plain `//` is discarded as line-comment trivia
Flow Flow — DocCommentDisambiguation scroll to zoom · drag to pan
FEATUREDocCommentDisambiguationfor LexerGIVENa line beginning with two or moreslashesWHENthe lexer reads the markerTHEN`//!` becomes an inner-doc tokenAND`///` becomes a doc line of prose andtag tokensBUTplain `//` is discarded asline-comment trivia
HashDisambiguation

On a `///` line, ` +as parser diagnostics, never a failed lex.

#critical

Parent

Inbound

Outbound

Scenarios

LexerNeverStalls

The lexer is greedy and never stalls on bad input.

  • given source containing an unknown byte or an unterminated string
  • when the lexer scans it
  • then the offending bytes are skipped or the string is closed at end of line
  • and lexing reaches the end of input
Flow Flow — LexerNeverStalls scroll to zoom · drag to pan
FEATURELexerNeverStallsfor LexerGIVENsource containing an unknown byte oran unterminated stringWHENthe lexer scans itTHENthe offending bytes are skipped or thestring is closed at end of lineANDlexing reaches the end of input
GreedyKeywordMatch

The longest identifier wins, then a keyword lookup classifies it.

  • given an identifier-start byte
  • when the lexer consumes the run of identifier bytes
  • then a reserved spelling lexes as its keyword kind
  • but a primitive name or `Result` stays an identifier, classified later
Flow Flow — GreedyKeywordMatch scroll to zoom · drag to pan
FEATUREGreedyKeywordMatchfor LexerGIVENan identifier-start byteWHENthe lexer consumes the run ofidentifier bytesTHENa reserved spelling lexes as itskeyword kindBUTa primitive name or `Result` stays anidentifier, classified later
DocCommentDisambiguation

A leading `/` resolves to a comment, doc, or inner doc by its run length.

  • given a line beginning with two or more slashes
  • when the lexer reads the marker
  • then `//!` becomes an inner-doc token
  • and `///` becomes a doc line of prose and tag tokens
  • but plain `//` is discarded as line-comment trivia
Flow Flow — DocCommentDisambiguation scroll to zoom · drag to pan
FEATUREDocCommentDisambiguationfor LexerGIVENa line beginning with two or moreslashesWHENthe lexer reads the markerTHEN`//!` becomes an inner-doc tokenAND`///` becomes a doc line of prose andtag tokensBUTplain `//` is discarded asline-comment trivia
HashDisambiguation

On a `///` line, ` ` is a tag while `#[` is a macro.

#name
  • given a `#` in the source
  • when the lexer classifies it
  • then `#name` on a doc line becomes a tag token including the `#`
  • and `#[` becomes a macro-open token
  • but a bare `#` in ordinary prose is skipped, with no grammar role
Flow Flow — HashDisambiguation scroll to zoom · drag to pan
FEATUREHashDisambiguationfor LexerGIVENa `#` in the sourceWHENthe lexer classifies itTHEN`#name` on a doc line becomes a tagtoken including the `#`AND`#[` becomes a macro-open tokenBUTa bare `#` in ordinary prose isskipped, with no grammar role
TriviaPreservedForFormatter

Comments and blank-line runs are preserved for the formatter.

  • given source interleaving tokens with comments and blank lines
  • when the lexer runs
  • then the token stream carries no comment trivia
  • and comments and blank-line runs are returned alongside as spanned trivia
Flow Flow — TriviaPreservedForFormatter scroll to zoom · drag to pan
FEATURETriviaPreservedForFormatterfor LexerGIVENsource interleaving tokens withcomments and blank linesWHENthe lexer runsTHENthe token stream carries no commenttriviaANDcomments and blank-line runs arereturned alongside as spanned trivia
ConformanceTokenRendering

Token rendering matches the conformance golden form.

  • given a source string
  • when its tokens are rendered
  • then each token prints as `KIND@line:col \"lexeme\"` on its own line
  • and interior backslashes and quotes in the lexeme are escaped
Flow Flow — ConformanceTokenRendering scroll to zoom · drag to pan
FEATUREConformanceTokenRenderingfor LexerGIVENa source stringWHENits tokens are renderedTHENeach token prints as `KIND@line:col\"lexeme\"` on its own lineANDinterior backslashes and quotes in thelexeme are escaped
data

LineComment #

public
syntax::LineComment
Entities Entity diagram scroll to zoom · drag to pan
RECORDLineCommenttextstring
component

LineIndex #

public
syntax::LineIndex

Precomputed newline offsets turning a byte offset into a 1-based (line, col) pair in O(log n). Built once per source and reused for every lookup; column counts bytes from the line start.

Parent

Inbound

Scenarios

OffsetToLineColumn

A byte offset maps to a 1-based line and column.

  • given a line index built over a source string
  • when a byte offset is looked up
  • then the 1-based line and byte column are returned
  • but an offset past the end clamps to the end of the source
Flow Flow — OffsetToLineColumn scroll to zoom · drag to pan
FEATUREOffsetToLineColumnfor LineIndexGIVENa line index built over a sourcestringWHENa byte offset is looked upTHENthe 1-based line and byte column arereturnedBUTan offset past the end clamps to theend of the source
data

List #

public
syntax::List
Entities Entity diagram scroll to zoom · drag to pan
RECORDListargssyntax::MacroArg[]UNIONMacroArgLiteralArgPathArgNestedArg
data

Lit #

public
syntax::Lit
Entities Entity diagram scroll to zoom · drag to pan
RECORDLitvaluesyntax::LiteralUNIONLiteralStringLitNumberLitBoolLit
data

Literal #

public
syntax::Literal

A literal value (ADR-013). `raw` carries the source text (string literals @@ -54,14 +54,15 @@ `Result`, `KwSome`/`KwNone` build an `Option`.

Entities Entity diagram scroll to zoom · drag to pan
UNIONMarkerKindKwOkKwErrKwSomeKwNone
data

Module #

public
syntax::Module

The typed syntax tree of one module (LANG.md §10): module-level inner docs, then declarations and features in source order. Every node carries a `Span`; declarations and statements also carry leading trivia so the formatter can -reproduce layout.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDModuleinnerDocssyntax::InnerDoc[]itemssyntax::Item[]spansyntax::SpanRECORDInnerDoctextstringspansyntax::SpanUNIONItemDeclItemFeatureItemRECORDSpanstartnumberendnumber
data

NameValue #

public
syntax::NameValue
Entities Entity diagram scroll to zoom · drag to pan
RECORDNameValuevaluesyntax::LiteralUNIONLiteralStringLitNumberLitBoolLit
data

NestedArg #

public
syntax::NestedArg
Entities Entity diagram scroll to zoom · drag to pan
RECORDNestedArgmacrosyntax::MacroRECORDMacronamesyntax::Pathargssyntax::MacroArgsspansyntax::Span
data

Node #

public
syntax::Node
Entities Entity diagram scroll to zoom · drag to pan
RECORDNodenodesyntax::NodeDeclRECORDNodeDeclkindsyntax::NodeKindnamestringparentsyntax::Pathbodysyntax::BodyMember[]spansyntax::Span
data

NodeDecl #

public
syntax::NodeDecl

A `person` / `system` / `container` / `component` declaration. `parent` is +reproduce layout.

Entities Entity diagram scroll to zoom · drag to pan
RECORDModuleinnerDocssyntax::InnerDoc[]itemssyntax::Item[]spansyntax::SpanRECORDInnerDoctextstringspansyntax::SpanUNIONItemDeclItemFeatureItemRECORDSpanstartnumberendnumber
data

NameValue #

public
syntax::NameValue
Entities Entity diagram scroll to zoom · drag to pan
RECORDNameValuevaluesyntax::LiteralUNIONLiteralStringLitNumberLitBoolLit
data

NestedArg #

public
syntax::NestedArg
Entities Entity diagram scroll to zoom · drag to pan
RECORDNestedArgmacrosyntax::MacroRECORDMacronamesyntax::Pathargssyntax::MacroArgsspansyntax::Span
data

Node #

public
syntax::Node
Entities Entity diagram scroll to zoom · drag to pan
RECORDNodenodesyntax::NodeDeclRECORDNodeDeclkindsyntax::NodeKindnamestringparentsyntax::Pathbodysyntax::BodyMember[]spansyntax::Span
data

NodeDecl #

public
syntax::NodeDecl

A `person` / `system` / `container` / `component` declaration. `parent` is the `for <Parent>` path, present for container/component. `body` is the -disclosed member list, or absent for a black box (`;`).

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeDeclkindsyntax::NodeKindnamestringparentsyntax::Pathbodysyntax::BodyMember[]spansyntax::SpanUNIONNodeKindPersonSystemContainerComponentRECORDPathsegmentsstring[]spansyntax::SpanUNIONBodyMemberCallableMemberDeclMemberRECORDSpanstartnumberendnumber
data

NodeKind #

public
syntax::NodeKind

The structural keyword class of a node.

Entities Entity diagram scroll to zoom · drag to pan
UNIONNodeKindPersonSystemContainerComponentRECORDContainerofstringRECORDComponentofstring
data

NumberLit #

public
syntax::NumberLit
Entities Entity diagram scroll to zoom · drag to pan
RECORDNumberLitrawstringspansyntax::SpanRECORDSpanstartnumberendnumber
data

Param #

public
syntax::Param

A callable parameter `name: Type`.

Entities Entity diagram scroll to zoom · drag to pan
RECORDParamnamestringtysyntax::Typespansyntax::SpanRECORDTypenamesyntax::Pathgenericssyntax::Type[]isArrayboolspansyntax::SpanRECORDSpanstartnumberendnumber
data

Paren #

public
syntax::Paren
Entities Entity diagram scroll to zoom · drag to pan
RECORDPareninnersyntax::ExprRECORDExprkindsyntax::ExprKindspansyntax::Span
data

Parsed #

public
syntax::Parsed

Parser output: the tree (always present, recovered on error) plus the +disclosed member list, or absent for a black box (`;`).

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeDeclkindsyntax::NodeKindnamestringparentsyntax::Pathbodysyntax::BodyMember[]spansyntax::SpanUNIONNodeKindPersonSystemContainerComponentRECORDPathsegmentsstring[]spansyntax::SpanUNIONBodyMemberCallableMemberDeclMemberRECORDSpanstartnumberendnumber
data

NodeKind #

public
syntax::NodeKind

The structural keyword class of a node.

Entities Entity diagram scroll to zoom · drag to pan
UNIONNodeKindPersonSystemContainerComponentRECORDContainerofstringRECORDComponentofstring
data

NumberLit #

public
syntax::NumberLit
Entities Entity diagram scroll to zoom · drag to pan
RECORDNumberLitrawstringspansyntax::SpanRECORDSpanstartnumberendnumber
data

OwnCall #

public
syntax::OwnCall
Entities Entity diagram scroll to zoom · drag to pan
RECORDOwnCallnamestringargssyntax::Expr[]RECORDExprkindsyntax::ExprKindspansyntax::Span
data

Param #

public
syntax::Param

A callable parameter `name: Type`.

Entities Entity diagram scroll to zoom · drag to pan
RECORDParamnamestringtysyntax::Typespansyntax::SpanRECORDTypenamesyntax::Pathgenericssyntax::Type[]isArrayboolspansyntax::SpanRECORDSpanstartnumberendnumber
data

Paren #

public
syntax::Paren
Entities Entity diagram scroll to zoom · drag to pan
RECORDPareninnersyntax::ExprRECORDExprkindsyntax::ExprKindspansyntax::Span
data

Parsed #

public
syntax::Parsed

Parser output: the tree (always present, recovered on error) plus the diagnostics collected while parsing. Parsing never fails outright.

Entities Entity diagram scroll to zoom · drag to pan
RECORDParsedastsyntax::Modulediagnosticssyntax::Diagnostic[]RECORDModuleinnerDocssyntax::InnerDoc[]itemssyntax::Item[]spansyntax::SpanRECORDDiagnosticseveritysyntax::Severityspansyntax::SpancodestringcodeDescriptionstringmessagestring
component

Parser #

private
syntax::Parser

Recursive-descent parser for the §10 grammar with error recovery: on unexpected input it records a `Diagnostic`, resynchronises to the next statement/declaration boundary, and continues. Always yields a (possibly -partial) `Module`; never panics.

#critical

Parent

Inbound

Outbound

Scenarios

ParsingIsInfallible

Parsing always yields a tree, even on malformed input.

#critical
  • given source text that may contain syntax errors
  • when the parser runs
  • then a syntax tree is returned, recovered around the errors
  • and each error rides alongside as a diagnostic
  • but parsing never throws or aborts
Flow Flow — ParsingIsInfallible scroll to zoom · drag to pan
FEATUREParsingIsInfalliblefor ParserGIVENsource text that may contain syntaxerrorsWHENthe parser runsTHENa syntax tree is returned, recoveredaround the errorsANDeach error rides alongside as adiagnosticBUTparsing never throws or aborts
StrayTokenTerminates

A stray `;` in statement position never spins the block loop (regression).

  • given a callable body with a stray `;` in statement position
  • when the block is parsed
  • then the stray token is reported as a diagnostic
  • and the forward-progress guard advances past it
  • but the surrounding callable still parses
Flow Flow — StrayTokenTerminates scroll to zoom · drag to pan
FEATUREStrayTokenTerminatesfor ParserGIVENa callable body with a stray `;` instatement positionWHENthe block is parsedTHENthe stray token is reported as adiagnosticANDthe forward-progress guard advancespast itBUTthe surrounding callable still parses
MarkerAndFromAreNotOperands

A marker or `from` expression never combines with a binary operator (§7.5).

  • given an expression whose head is a marker or a `Type from ..`
  • when a binary operator follows the head
  • then the operator is reported as a diagnostic
  • and the head expression still parses, with its postfix chain
  • but the operator is left unconsumed for the enclosing context to resync on
Flow Flow — MarkerAndFromAreNotOperands scroll to zoom · drag to pan
FEATUREMarkerAndFromAreNotOperandsfor ParserGIVENan expression whose head is a markeror a `Type from ..`WHENa binary operator follows the headTHENthe operator is reported as adiagnosticANDthe head expression still parses, withits postfix chainBUTthe operator is left unconsumed forthe enclosing context to resync on
DocSummaryBodySplit

A blank `///` line splits the doc block into summary and extended.

  • given a doc block whose lines are separated by one blank `///` line
  • when the doc block is parsed
  • then lines before the blank become the summary
  • and lines after it become the extended description
  • and tags are gathered regardless of which side they sit on
Flow Flow — DocSummaryBodySplit scroll to zoom · drag to pan
FEATUREDocSummaryBodySplitfor ParserGIVENa doc block whose lines are separatedby one blank `///` lineWHENthe doc block is parsedTHENlines before the blank become thesummaryANDlines after it become the extendeddescriptionANDtags are gathered regardless of whichside they sit on
data

Path #

public
syntax::Path

A `::`-separated path of identifiers (§2.2, §10 `Path`); never empty.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPathsegmentsstring[]spansyntax::SpanRECORDSpanstartnumberendnumber
data

PathArg #

public
syntax::PathArg
Entities Entity diagram scroll to zoom · drag to pan
RECORDPathArgpathsyntax::PathRECORDPathsegmentsstring[]spansyntax::Span
data

PathRef #

public
syntax::PathRef
Entities Entity diagram scroll to zoom · drag to pan
RECORDPathRefpathsyntax::PathRECORDPathsegmentsstring[]spansyntax::Span
data

Postfix #

public
syntax::Postfix
Entities Entity diagram scroll to zoom · drag to pan
RECORDPostfixbasesyntax::Exprsegmentssyntax::PostfixSeg[]RECORDExprkindsyntax::ExprKindspansyntax::SpanRECORDPostfixSegnamestringcallArgssyntax::Expr[]spansyntax::Span
data

PostfixSeg #

public
syntax::PostfixSeg

One `.name` or `.name(args)` step in a postfix chain (ADR-007). `callArgs` -present marks a call; absent marks field access.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPostfixSegnamestringcallArgssyntax::Expr[]spansyntax::SpanRECORDExprkindsyntax::ExprKindspansyntax::SpanRECORDSpanstartnumberendnumber
data

Record #

public
syntax::Record
Entities Entity diagram scroll to zoom · drag to pan
RECORDRecordfieldssyntax::Field[]RECORDFieldnamestringtysyntax::Typespansyntax::Span
data

Ref #

public
syntax::Ref
Entities Entity diagram scroll to zoom · drag to pan
RECORDReftargetsyntax::ReferenceUNIONReferenceSelfNodePathRef
data

Reference #

public
syntax::Reference

A reference primary (§10 `Ref`): `self` or an FQN.

Entities Entity diagram scroll to zoom · drag to pan
UNIONReferenceSelfNodePathRefRECORDSelfNodespansyntax::SpanRECORDPathRefpathsyntax::Path
data

Return #

public
syntax::Return
Entities Entity diagram scroll to zoom · drag to pan
RECORDReturnvaluesyntax::ExprRECORDExprkindsyntax::ExprKindspansyntax::Span
data

SelfNode #

public
syntax::SelfNode
Entities Entity diagram scroll to zoom · drag to pan
RECORDSelfNodespansyntax::SpanRECORDSpanstartnumberendnumber
data

Severity #

public
syntax::Severity

Diagnostic severity. `Error` invalidates the input; `Warning`/`Info` advise +partial) `Module`; never panics.

#critical

Parent

Inbound

Outbound

Scenarios

ParsingIsInfallible

Parsing always yields a tree, even on malformed input.

#critical
  • given source text that may contain syntax errors
  • when the parser runs
  • then a syntax tree is returned, recovered around the errors
  • and each error rides alongside as a diagnostic
  • but parsing never throws or aborts
Flow Flow — ParsingIsInfallible scroll to zoom · drag to pan
FEATUREParsingIsInfalliblefor ParserGIVENsource text that may contain syntaxerrorsWHENthe parser runsTHENa syntax tree is returned, recoveredaround the errorsANDeach error rides alongside as adiagnosticBUTparsing never throws or aborts
StrayTokenTerminates

A stray `;` in statement position never spins the block loop (regression).

  • given a callable body with a stray `;` in statement position
  • when the block is parsed
  • then the stray token is reported as a diagnostic
  • and the forward-progress guard advances past it
  • but the surrounding callable still parses
Flow Flow — StrayTokenTerminates scroll to zoom · drag to pan
FEATUREStrayTokenTerminatesfor ParserGIVENa callable body with a stray `;` instatement positionWHENthe block is parsedTHENthe stray token is reported as adiagnosticANDthe forward-progress guard advancespast itBUTthe surrounding callable still parses
MarkerAndFromAreNotOperands

A marker or `from` expression never combines with a binary operator (§7.5).

  • given an expression whose head is a marker or a `Type from ..`
  • when a binary operator follows the head
  • then the operator is reported as a diagnostic
  • and the head expression still parses, with its postfix chain
  • but the operator is left unconsumed for the enclosing context to resync on
Flow Flow — MarkerAndFromAreNotOperands scroll to zoom · drag to pan
FEATUREMarkerAndFromAreNotOperandsfor ParserGIVENan expression whose head is a markeror a `Type from ..`WHENa binary operator follows the headTHENthe operator is reported as adiagnosticANDthe head expression still parses, withits postfix chainBUTthe operator is left unconsumed forthe enclosing context to resync on
DocSummaryBodySplit

A blank `///` line splits the doc block into summary and extended.

  • given a doc block whose lines are separated by one blank `///` line
  • when the doc block is parsed
  • then lines before the blank become the summary
  • and lines after it become the extended description
  • and tags are gathered regardless of which side they sit on
Flow Flow — DocSummaryBodySplit scroll to zoom · drag to pan
FEATUREDocSummaryBodySplitfor ParserGIVENa doc block whose lines are separatedby one blank `///` lineWHENthe doc block is parsedTHENlines before the blank become thesummaryANDlines after it become the extendeddescriptionANDtags are gathered regardless of whichside they sit on
data

Path #

public
syntax::Path

A `::`-separated path of identifiers (§2.2, §10 `Path`); never empty.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPathsegmentsstring[]spansyntax::SpanRECORDSpanstartnumberendnumber
data

PathArg #

public
syntax::PathArg
Entities Entity diagram scroll to zoom · drag to pan
RECORDPathArgpathsyntax::PathRECORDPathsegmentsstring[]spansyntax::Span
data

PathRef #

public
syntax::PathRef
Entities Entity diagram scroll to zoom · drag to pan
RECORDPathRefpathsyntax::PathRECORDPathsegmentsstring[]spansyntax::Span
data

Postfix #

public
syntax::Postfix
Entities Entity diagram scroll to zoom · drag to pan
RECORDPostfixbasesyntax::Exprsegmentssyntax::PostfixSeg[]RECORDExprkindsyntax::ExprKindspansyntax::SpanRECORDPostfixSegnamestringcallArgssyntax::Expr[]spansyntax::Span
data

PostfixSeg #

public
syntax::PostfixSeg

One `.name` or `.name(args)` step in a postfix chain (ADR-007). `callArgs` +present marks a call; absent marks field access.

Entities Entity diagram scroll to zoom · drag to pan
RECORDPostfixSegnamestringcallArgssyntax::Expr[]spansyntax::SpanRECORDExprkindsyntax::ExprKindspansyntax::SpanRECORDSpanstartnumberendnumber
data

Record #

public
syntax::Record
Entities Entity diagram scroll to zoom · drag to pan
RECORDRecordfieldssyntax::Field[]RECORDFieldnamestringtysyntax::Typespansyntax::Span
data

Ref #

public
syntax::Ref
Entities Entity diagram scroll to zoom · drag to pan
RECORDReftargetsyntax::ReferenceUNIONReferencePathRef
data

Reference #

public
syntax::Reference

A reference primary (§10 `Ref`): an FQN. `self` was dropped (ADR-041); a +same-node call is the bare `OwnCall` primary, not a reference.

Entities Entity diagram scroll to zoom · drag to pan
UNIONReferencePathRefRECORDPathRefpathsyntax::Path
data

Return #

public
syntax::Return
Entities Entity diagram scroll to zoom · drag to pan
RECORDReturnvaluesyntax::ExprRECORDExprkindsyntax::ExprKindspansyntax::Span
data

Severity #

public
syntax::Severity

Diagnostic severity. `Error` invalidates the input; `Warning`/`Info` advise and never block compilation.

Entities Entity diagram scroll to zoom · drag to pan
UNIONSeverityErrorWarningInfo
data

Span #

public
syntax::Span

A half-open byte range `[start, end)` into one module's source. Offsets are bytes, not characters; columns derived via `LineIndex` count bytes too, matching the conformance token goldens.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSpanstartnumberendnumber
data

SpannedTrivia #

public
syntax::SpannedTrivia

A trivia element paired with the source span it occupies.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSpannedTriviatriviasyntax::Triviaspansyntax::SpanUNIONTriviaLineCommentBlockCommentBlankLinesRECORDSpanstartnumberendnumber
data

StepKind #

public
syntax::StepKind

The step keyword class of a feature step (§5.2). `And`/`But` continue the @@ -70,7 +71,7 @@ and a typed AST, emitting the shared `Diagnostic`. WASM-safe and I/O-free.

#critical

Inbound

Outbound

  • from cli::string
  • from format::Parsed
  • from model::Parsed
  • call syntax::Lexer · renderTokens
  • call syntax::Parser · parse
Components Component diagram scroll to zoom · drag to pan
PseudoscriptSyntaxCOMPONENTLexerHand-written lexer for LANG.md §2. One passyields the conformance token stream…COMPONENTLineIndexPrecomputed newline offsets turning a byteoffset into a 1-based (line, col) pair in…COMPONENTParserRecursive-descent parser for the §10 grammarwith error recovery: on unexpected input it…CONTAINERCli`crates/pseudoscript` — the binary crate(`pds`). The composition root and only I/O…CONTAINERFormat`crates/pseudoscript-format`. The canonicalformatter: parse, then pretty-print the tre…CONTAINERModel`crates/pseudoscript-model`. AST to oneresolved graph; static checks (resolution,…formatcheckcheckWorkspaceModulesgraphbuildlex
data

Tag #

public
syntax::Tag

A ` ` tag from a doc block, including the leading `#` (§2.4).

#name
Entities Entity diagram scroll to zoom · drag to pan
RECORDTagtextstringspansyntax::SpanRECORDSpanstartnumberendnumber
data

Token #

public
syntax::Token

A lexical token: its kind, source span, and rendered lexeme (LANG.md §2).

For most tokens `text` is the raw source slice. For `Doc`/`InnerDoc` it is the doc text with the marker and surrounding whitespace stripped; for `Tag` -it includes the leading `#`.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDTokenkindsyntax::TokenKindspansyntax::SpantextstringUNIONTokenKindKeywordIdentPunctuationStringLiteralNumberLiteralDocInnerDocTagHashLBracketRECORDSpanstartnumberendnumber
data

TokenKind #

public
syntax::TokenKind

Every lexical token class (LANG.md §2). The `name` rendering (e.g. +it includes the leading `#`.

Entities Entity diagram scroll to zoom · drag to pan
RECORDTokenkindsyntax::TokenKindspansyntax::SpantextstringUNIONTokenKindKeywordIdentPunctuationStringLiteralNumberLiteralDocInnerDocTagHashLBracketRECORDSpanstartnumberendnumber
data

TokenKind #

public
syntax::TokenKind

Every lexical token class (LANG.md §2). The `name` rendering (e.g. `KW_SYSTEM`) is the conformance golden's `KIND`. Primitive type names and `Result` are NOT keywords — they lex as `Ident` and are classified in type position by the model crate.

Entities Entity diagram scroll to zoom · drag to pan
UNIONTokenKindKeywordIdentPunctuationStringLiteralNumberLiteralDocInnerDocTagHashLBracketRECORDInnerDoctextstringspansyntax::SpanRECORDTagtextstringspansyntax::Span
data

Trivia #

public
syntax::Trivia

Non-token source between tokens, preserved for the formatter. Doc comments, diff --git a/model/site/module/universe.html b/model/site/module/universe.html index 8646cedb..5f12c941 100644 --- a/model/site/module/universe.html +++ b/model/site/module/universe.html @@ -11,34 +11,34 @@ -

PseudoScript
Module

universe

component

Adapter #

private
universe::Adapter

Adapts a resolved model into the software graph: keeps only the structural +

PseudoScript
Module

universe

component

Adapter #

private
universe::Adapter

Adapts a resolved model into the software graph: keeps only the structural nodes, hooks each to its structural parent for the containment tree, and lifts every call to the structural level — tallying directed traffic per -caller→callee pair so the edge carries direction.

Parent

Inbound

Outbound

Scenarios

StructuralNodesOnly

Only structural nodes enter the graph; data and callables do not.

  • given a resolved model with systems, containers, components, data, and callables
  • when the model is adapted into the software graph
  • then every system, container, component, and person becomes a graph node
  • but data and callables are not placed
Flow Flow — StructuralNodesOnly scroll to zoom · drag to pan
FEATUREStructuralNodesOnlyfor AdapterGIVENa resolved model with systems,containers, components, data, andcallablesWHENthe model is adapted into the softwaregraphTHENevery system, container, component,and person becomes a graph nodeBUTdata and callables are not placed
CallsLiftAndTallyTraffic

Calls lift to the structural level and tally as directed traffic.

  • given a call from one component's body into another node
  • when relationships are collected
  • then the call lifts to the nearest enclosing structural node on each end
  • and repeated calls between the same pair sum into one directed edge's traffic
  • but a call that lifts to the same node on both ends is dropped
Flow Flow — CallsLiftAndTallyTraffic scroll to zoom · drag to pan
FEATURECallsLiftAndTallyTrafficfor AdapterGIVENa call from one component's body intoanother nodeWHENrelationships are collectedTHENthe call lifts to the nearestenclosing structural node on each endANDrepeated calls between the same pairsum into one directed edge's trafficBUTa call that lifts to the same node onboth ends is dropped
ContainmentTree

Containment is a tree rooted at the top-level systems.

  • given a system holding containers that hold components
  • when the graph is built
  • then each node records its enclosing parent and the ids it contains
  • and the top-level systems are the containment roots, in declaration order
Flow Flow — ContainmentTree scroll to zoom · drag to pan
FEATUREContainmentTreefor AdapterGIVENa system holding containers that holdcomponentsWHENthe graph is builtTHENeach node records its enclosing parentand the ids it containsANDthe top-level systems are thecontainment roots, in declarationorder
data

C4Level #

public
universe::C4Level

The C4 abstraction level a node sits at. Only these four enter the graph; +caller→callee pair so the edge carries direction.

Parent

Inbound

Outbound

Scenarios

StructuralNodesOnly

Only structural nodes enter the graph; data and callables do not.

  • given a resolved model with systems, containers, components, data, and callables
  • when the model is adapted into the software graph
  • then every system, container, component, and person becomes a graph node
  • but data and callables are not placed
Flow Flow — StructuralNodesOnly scroll to zoom · drag to pan
FEATUREStructuralNodesOnlyfor AdapterGIVENa resolved model with systems,containers, components, data, andcallablesWHENthe model is adapted into the softwaregraphTHENevery system, container, component,and person becomes a graph nodeBUTdata and callables are not placed
CallsLiftAndTallyTraffic

Calls lift to the structural level and tally as directed traffic.

  • given a call from one component's body into another node
  • when relationships are collected
  • then the call lifts to the nearest enclosing structural node on each end
  • and repeated calls between the same pair sum into one directed edge's traffic
  • but a call that lifts to the same node on both ends is dropped
Flow Flow — CallsLiftAndTallyTraffic scroll to zoom · drag to pan
FEATURECallsLiftAndTallyTrafficfor AdapterGIVENa call from one component's body intoanother nodeWHENrelationships are collectedTHENthe call lifts to the nearestenclosing structural node on each endANDrepeated calls between the same pairsum into one directed edge's trafficBUTa call that lifts to the same node onboth ends is dropped
ContainmentTree

Containment is a tree rooted at the top-level systems.

  • given a system holding containers that hold components
  • when the graph is built
  • then each node records its enclosing parent and the ids it contains
  • and the top-level systems are the containment roots, in declaration order
Flow Flow — ContainmentTree scroll to zoom · drag to pan
FEATUREContainmentTreefor AdapterGIVENa system holding containers that holdcomponentsWHENthe graph is builtTHENeach node records its enclosing parentand the ids it containsANDthe top-level systems are thecontainment roots, in declarationorder
data

C4Level #

public
universe::C4Level

The C4 abstraction level a node sits at. Only these four enter the graph; data and callables do not — a relationship through them is lifted to the -nearest enclosing structural node.

Entities Entity diagram scroll to zoom · drag to pan
UNIONC4LevelSystemContainerComponentPersonRECORDContainerofstringRECORDComponentofstring
data

EdgeOut #

public
universe::EdgeOut

One relationship in the flat snapshot, with its traffic (call count).

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDEdgeOutsourcestringtostringtrafficnumber
component

Flatten #

private
universe::Flatten

Flattens the software graph into the renderer-facing snapshot: each node's id, +nearest enclosing structural node.

Entities Entity diagram scroll to zoom · drag to pan
UNIONC4LevelSystemContainerComponentPersonRECORDContainerofstringRECORDComponentofstring
data

EdgeOut #

public
universe::EdgeOut

One relationship in the flat snapshot, with its traffic (call count).

Entities Entity diagram scroll to zoom · drag to pan
RECORDEdgeOutsourcestringtostringtrafficnumber
component

Flatten #

private
universe::Flatten

Flattens the software graph into the renderer-facing snapshot: each node's id, level string, and parent id; each relationship's endpoints and traffic. The -boundary the web IDE reads — petgraph indices never cross it.

Parent

Inbound

Outbound

Scenarios

SnapshotIsDeterministic

The snapshot is deterministic and free of engine internals.

  • given the same software graph flattened twice
  • when the snapshots are compared
  • then nodes and edges appear in the same stable order both times
  • but no petgraph index or other engine internal crosses the boundary
Flow Flow — SnapshotIsDeterministic scroll to zoom · drag to pan
FEATURESnapshotIsDeterministicfor FlattenGIVENthe same software graph flattenedtwiceWHENthe snapshots are comparedTHENnodes and edges appear in the samestable order both timesBUTno petgraph index or other engineinternal crosses the boundary
data

FlowDef #

public
universe::FlowDef

One entry-point flow: the entry callable's FQN and simple name, the flow's +boundary the web IDE reads — petgraph indices never cross it.

Parent

Inbound

Scenarios

SnapshotIsDeterministic

The snapshot is deterministic and free of engine internals.

  • given the same software graph flattened twice
  • when the snapshots are compared
  • then nodes and edges appear in the same stable order both times
  • but no petgraph index or other engine internal crosses the boundary
Flow Flow — SnapshotIsDeterministic scroll to zoom · drag to pan
FEATURESnapshotIsDeterministicfor FlattenGIVENthe same software graph flattenedtwiceWHENthe snapshots are comparedTHENnodes and edges appear in the samestable order both timesBUTno petgraph index or other engineinternal crosses the boundary
data

FlowDef #

public
universe::FlowDef

One entry-point flow: the entry callable's FQN and simple name, the flow's palette colour, and its ordered legs. The renderer draws one filament chain per flow and streams its beads in the flow's colour.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDFlowDeffqnstringnamestringcolorstringhopsuniverse::FlowHop[]RECORDFlowHopsourcestringtostringlabelstring
data

FlowHop #

public
universe::FlowHop

One call leg of a flow: the caller and callee as structural node ids (the -same ids the snapshot places), plus the message label shown on the leg.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDFlowHopsourcestringtostringlabelstring
component

FlowTracer #

private
universe::FlowTracer

Traces every entry-point flow: a triggered callable (or a person-owned +same ids the snapshot places), plus the message label shown on the leg.

Entities Entity diagram scroll to zoom · drag to pan
RECORDFlowHopsourcestringtostringlabelstring
component

FlowTracer #

private
universe::FlowTracer

Traces every entry-point flow: a triggered callable (or a person-owned action) starts a flow; its sequence projection is walked call-by-call and each leg's endpoints lift to the nearest placed structural node — the same lift the Adapter applies to relationships, so flows and edges agree. Colours key from the entry's FQN by a stable hash into a fixed palette — the same keying the web IDE paints with — so a flow keeps its hue everywhere it -appears and an unrelated model edit never recolours it.

Parent

Inbound

Outbound

Scenarios

EntryPointsStartFlows

A triggered callable or a person-owned action starts a flow.

  • given a model with trigger-macro callables and person-owned actions
  • when flows are traced
  • then each triggered callable and each person-owned callable yields one flow
  • and entries are ordered by FQN so the set is stable
Flow Flow — EntryPointsStartFlows scroll to zoom · drag to pan
FEATUREEntryPointsStartFlowsfor FlowTracerGIVENa model with trigger-macro callablesand person-owned actionsWHENflows are tracedTHENeach triggered callable and eachperson-owned callable yields one flowANDentries are ordered by FQN so the setis stable
FlowsLiftToStructuralNodes

Flow legs land on the structural nodes the renderer places.

  • given a flow whose calls land on components and callables
  • when its hops are traced
  • then each leg's caller and callee lift to the nearest placed structural node
  • but a leg whose ends lift to the same node, or fail to lift, is dropped
Flow Flow — FlowsLiftToStructuralNodes scroll to zoom · drag to pan
FEATUREFlowsLiftToStructuralNodesfor FlowTracerGIVENa flow whose calls land on componentsand callablesWHENits hops are tracedTHENeach leg's caller and callee lift tothe nearest placed structural nodeBUTa leg whose ends lift to the samenode, or fail to lift, is dropped
FlowsAreDeterministic

Flows are deterministic: same model, same flows, same colours.

  • given the same model traced twice
  • when the flow sets are compared
  • then flows, hops, and colours are identical
  • and a colour keys from its entry's FQN, so an unrelated model edit never recolours a flow
Flow Flow — FlowsAreDeterministic scroll to zoom · drag to pan
FEATUREFlowsAreDeterministicfor FlowTracerGIVENthe same model traced twiceWHENthe flow sets are comparedTHENflows, hops, and colours are identicalANDa colour keys from its entry's FQN, soan unrelated model edit neverrecolours a flow
data

GraphNode #

private
universe::GraphNode

A structural node in the software graph: its stable model FQN, its C4 level, +appears and an unrelated model edit never recolours it.

Parent

Inbound

Outbound

Scenarios

EntryPointsStartFlows

A triggered callable or a person-owned action starts a flow.

  • given a model with trigger-macro callables and person-owned actions
  • when flows are traced
  • then each triggered callable and each person-owned callable yields one flow
  • and entries are ordered by FQN so the set is stable
Flow Flow — EntryPointsStartFlows scroll to zoom · drag to pan
FEATUREEntryPointsStartFlowsfor FlowTracerGIVENa model with trigger-macro callablesand person-owned actionsWHENflows are tracedTHENeach triggered callable and eachperson-owned callable yields one flowANDentries are ordered by FQN so the setis stable
FlowsLiftToStructuralNodes

Flow legs land on the structural nodes the renderer places.

  • given a flow whose calls land on components and callables
  • when its hops are traced
  • then each leg's caller and callee lift to the nearest placed structural node
  • but a leg whose ends lift to the same node, or fail to lift, is dropped
Flow Flow — FlowsLiftToStructuralNodes scroll to zoom · drag to pan
FEATUREFlowsLiftToStructuralNodesfor FlowTracerGIVENa flow whose calls land on componentsand callablesWHENits hops are tracedTHENeach leg's caller and callee lift tothe nearest placed structural nodeBUTa leg whose ends lift to the samenode, or fail to lift, is dropped
FlowsAreDeterministic

Flows are deterministic: same model, same flows, same colours.

  • given the same model traced twice
  • when the flow sets are compared
  • then flows, hops, and colours are identical
  • and a colour keys from its entry's FQN, so an unrelated model edit never recolours a flow
Flow Flow — FlowsAreDeterministic scroll to zoom · drag to pan
FEATUREFlowsAreDeterministicfor FlowTracerGIVENthe same model traced twiceWHENthe flow sets are comparedTHENflows, hops, and colours are identicalANDa colour keys from its entry's FQN, soan unrelated model edit neverrecolours a flow
data

GraphNode #

private
universe::GraphNode

A structural node in the software graph: its stable model FQN, its C4 level, and its place in the containment tree (the enclosing node, empty for a top-level system, and the ids it contains). No position — the renderer -places it.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDGraphNodeidstringleveluniverse::C4Levelparentstringchildrenstring[]UNIONC4LevelSystemContainerComponentPerson
data

NodeOut #

public
universe::NodeOut

One node in the flat snapshot: its id, C4 level as a lowercase string, and the -id of its containment parent (empty for a top-level system).

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeOutidstringlevelstringparentstring
data

Relationship #

private
universe::Relationship

A directed relationship between two structural nodes, weighted by `traffic` — +places it.

Entities Entity diagram scroll to zoom · drag to pan
RECORDGraphNodeidstringleveluniverse::C4Levelparentstringchildrenstring[]UNIONC4LevelSystemContainerComponentPerson
data

NodeOut #

public
universe::NodeOut

One node in the flat snapshot: its id, C4 level as a lowercase string, and the +id of its containment parent (empty for a top-level system).

Entities Entity diagram scroll to zoom · drag to pan
RECORDNodeOutidstringlevelstringparentstring
data

Relationship #

private
universe::Relationship

A directed relationship between two structural nodes, weighted by `traffic` — the number of underlying calls lifted onto the pair. Self-relationships are dropped; the count drives the renderer's flow animation.

Entities Entity diagram scroll to zoom · drag to pan
RECORDRelationshipsourcestringtostringtrafficnumber
data

Snapshot #

public
universe::Snapshot

The flat, serialisable contract the web IDE's `ForceGraph` consumes: nodes and directed weighted edges, with no engine internals (petgraph indices) leaking across the boundary. Positions are not here.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDSnapshotnodesuniverse::NodeOut[]edgesuniverse::EdgeOut[]RECORDNodeOutidstringlevelstringparentstringRECORDEdgeOutsourcestringtostringtrafficnumber
data

SoftwareGraph #

public
universe::SoftwareGraph

The software graph: structural nodes with the containment tree, directed relationship edges weighted by traffic, and the top-level systems (the containment roots, in model-declaration order). The crate's in-memory form, -before flattening to a renderer-facing `Snapshot`.

Entities Entity diagram scroll to zoom · drag to pan
RECORDSoftwareGraphnodesuniverse::GraphNode[]relationshipsuniverse::Relationship[]rootsstring[]RECORDGraphNodeidstringleveluniverse::C4Levelparentstringchildrenstring[]RECORDRelationshipsourcestringtostringtrafficnumber
container

Universe #

public
universe::Universe

`crates/pseudoscript-universe`. Maps the resolved C4 model into the software +before flattening to a renderer-facing `Snapshot`.

Inbound

Entities Entity diagram scroll to zoom · drag to pan
RECORDSoftwareGraphnodesuniverse::GraphNode[]relationshipsuniverse::Relationship[]rootsstring[]RECORDGraphNodeidstringleveluniverse::C4Levelparentstringchildrenstring[]RECORDRelationshipsourcestringtostringtrafficnumber
container

Universe #

public
universe::Universe

`crates/pseudoscript-universe`. Maps the resolved C4 model into the software graph the 3D view renders, then flattens it to a determinstic snapshot.

Inbound

Outbound

Scenarios

PositionsAreTheRenderersJob

Layout is the renderer's job, not this crate's.

  • given a software-graph snapshot
  • when the web IDE renders the 3D view
  • then d3-force-3d assigns every node a position in the browser
  • but the crate carries no coordinates — it is pure structure and traffic
Flow Flow — PositionsAreTheRenderersJob scroll to zoom · drag to pan
FEATUREPositionsAreTheRenderersJobfor UniverseGIVENa software-graph snapshotWHENthe web IDE renders the 3D viewTHENd3-force-3d assigns every node aposition in the browserBUTthe crate carries no coordinates — itis pure structure and traffic
Components Component diagram scroll to zoom · drag to pan
PseudoscriptUniverseCOMPONENTAdapterAdapts a resolved model into the softwaregraph: keeps only the structural nodes,…COMPONENTFlowTracerTraces every entry-point flow: a triggeredcallable (or a person-owned action) starts …COMPONENTFlattenFlattens the software graph into therenderer-facing snapshot: each node's id,…CONTAINERDoc`crates/pseudoscript-doc`. Turns a resolvedgraph into a Svelte-rendered,…CONTAINEREmit`crates/pseudoscript-emit`. Projects a viewinto a `Scene` and renders it to SVG; C4…CONTAINERIdeSession`crates/pseudoscript-ide` — the IDEapplication, Rust compiled to a single wasm…CONTAINERModel`crates/pseudoscript-model`. AST to oneresolved graph; static checks (resolution,…projectprepareDiagnosticscheckcheckWorkspaceModulesgraphgraphproject
Generated by pds doc.
diff --git a/model/site/search-index.js b/model/site/search-index.js index e7e95843..5adb992d 100644 --- a/model/site/search-index.js +++ b/model/site/search-index.js @@ -1 +1 @@ -window.__PDS_SEARCH__=[{"fqn":"health.html","name":"Architecture Health","kind":"page","summary":"Errors, warnings, and principle lints","text":"","module":"","href":"health.html"},{"fqn":"cli","name":"cli","kind":"module","summary":"","text":"","module":"cli","href":"module/cli.html"},{"fqn":"cli::AddOpts","name":"AddOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-AddOpts"},{"fqn":"cli::Args","name":"Args","kind":"component","summary":"Parses argv with clap-derive into the chosen subcommand and its options, then\ndispatches to the matching command handler.","text":"","module":"cli","href":"module/cli.html#cli-Args"},{"fqn":"cli::CheckCmd","name":"CheckCmd","kind":"component","summary":"`pds check` — parse and statically check one file, print each diagnostic as\n`path:line:col: severity: message`, and exit non-zero on any error-severity\ndiagnostic. Emits no diagram.","text":"","module":"cli","href":"module/cli.html#cli-CheckCmd"},{"fqn":"cli::CheckOpts","name":"CheckOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-CheckOpts"},{"fqn":"cli::Cli","name":"Cli","kind":"container","summary":"`crates/pseudoscript` — the binary crate (`pds`). The composition root and\nonly I/O edge; a thin frontend that wires the pure library crates to disk,\nHTTP, and a filesystem watcher.","text":"","module":"cli","href":"module/cli.html#cli-Cli"},{"fqn":"cli::Command","name":"Command","kind":"data","summary":"The subcommand clap parsed from argv, with its resolved options.","text":"","module":"cli","href":"module/cli.html#cli-Command"},{"fqn":"cli::Deps","name":"Deps","kind":"component","summary":"`pds add`/`install`/`update`/`remove`/`list` — git workspace dependency\nmanagement (§8.3). `add` resolves a git URL into `pds.toml` + `pds.lock` and\nvendors it; `install` restores `pds_modules/` from the lock; `update`\nre-pins; `remove` drops one; `list` discovers every workspace under a root.\nResolution delegates to `project`; fetching is the CLI's I/O.","text":"","module":"cli","href":"module/cli.html#cli-Deps"},{"fqn":"cli::DocCmd","name":"DocCmd","kind":"component","summary":"`pds doc` — the headline command (ADR-017). Load the workspace, check it per\nmodule (reported but non-fatal, like `cargo doc` — the same findings feed the\nsite's health page), project the resolved graph, and render a static doc\nsite. The output format is resolved by precedence — an explicit `--format`\nover the manifest `[doc].format`, else HTML — then a fork renders the SSR\nHTML site (every diagram as server SVG, logo copied) or a flat Markdown site\n(SVG assets, no logo). `--serve` hosts the HTML site over HTTP; `--watch`\nadds a filesystem watcher and browser live reload; both are skipped for\nMarkdown output, which only writes.","text":"","module":"cli","href":"module/cli.html#cli-DocCmd"},{"fqn":"cli::DocOpts","name":"DocOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-DocOpts"},{"fqn":"cli::EvalCmd","name":"EvalCmd","kind":"component","summary":"`pds eval` — read a model from stdin and statically check it, printing each\ndiagnostic as `:line:col: severity: message` and exiting non-zero on\nany error. The fileless path: an agent pipes a snippet to check it without\nwriting a file.","text":"","module":"cli","href":"module/cli.html#cli-EvalCmd"},{"fqn":"cli::FmtCmd","name":"FmtCmd","kind":"component","summary":"`pds fmt` — format to canonical PseudoScript; `--write` overwrites the file in\nplace, otherwise prints to stdout. A parse error is reported and the file is\nleft untouched.","text":"","module":"cli","href":"module/cli.html#cli-FmtCmd"},{"fqn":"cli::FmtOpts","name":"FmtOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-FmtOpts"},{"fqn":"cli::HttpServer","name":"HttpServer","kind":"component","summary":"HTTP host for the generated site (`--serve`/`--watch`): `tiny_http` on\n`127.0.0.1:port`, maps `/` to `index.html`, rejects paths escaping the output\ndir, and on watch injects a live-reload poll and answers `/__livereload` with\nthe current version. Blocks until the process is stopped.","text":"","module":"cli","href":"module/cli.html#cli-HttpServer"},{"fqn":"cli::InitCmd","name":"InitCmd","kind":"component","summary":"`pds init` — bootstrap a workspace: write `pds.toml` and a starter `main.pds`.\nRefuses to overwrite an existing manifest; the starter is written only when\nabsent.","text":"","module":"cli","href":"module/cli.html#cli-InitCmd"},{"fqn":"cli::IoError","name":"IoError","kind":"data","summary":"A filesystem, parse-config, or serving failure surfaced to the CLI exit code.","text":"","module":"cli","href":"module/cli.html#cli-IoError"},{"fqn":"cli::ListOpts","name":"ListOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-ListOpts"},{"fqn":"cli::Loader","name":"Loader","kind":"component","summary":"The CLI loader: resolves the project root and reads its modules through the\n`project` crate (the shared filesystem edge), then parses the cli-specific\n`[doc]` table on top. Root-finding and module-walking are `project`'s job;\nthe manifest's doc config is the CLI's.","text":"","module":"cli","href":"module/cli.html#cli-Loader"},{"fqn":"cli::LspHost","name":"LspHost","kind":"component","summary":"`pds lsp` — boot a Tokio runtime and launch the language server over stdio.\nSpawned as a child process by an editor (`context::Editor.openDocument`).","text":"","module":"cli","href":"module/cli.html#cli-LspHost"},{"fqn":"cli::OutlineCmd","name":"OutlineCmd","kind":"component","summary":"`pds outline` — walk the workspace's modules, build its graph, and print the\nsymbol outline as JSON: each node's `fqn`, `name`, `kind`, `parent`, whether it\nis a triggered flow entry, and its declaration site. The structure tree an\neditor draws. Outlining reads the walked modules straight, skipping the manifest\n`[doc]` parse and dependency resolution: the structure tree never depends on\npresentation config or external modules.","text":"","module":"cli","href":"module/cli.html#cli-OutlineCmd"},{"fqn":"cli::OutlineOpts","name":"OutlineOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-OutlineOpts"},{"fqn":"cli::ReferenceCmd","name":"ReferenceCmd","kind":"component","summary":"`pds lang` (alias `spec`) and `pds skill` — print the bundled, version-pinned\nlanguage reference and the authoring skill to stdout. Fed to an LLM to author\n`.pds`; no workspace needed.","text":"","module":"cli","href":"module/cli.html#cli-ReferenceCmd"},{"fqn":"cli::RemoveOpts","name":"RemoveOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-RemoveOpts"},{"fqn":"cli::SvgCmd","name":"SvgCmd","kind":"component","summary":"`pds svg` — render a single diagram to a self-contained SVG on stdout. With\n`--symbol`, draws that symbol's fitting view; otherwise draws `--view` (paired\nwith `--target`) over the whole workspace. The static counterpart of the IDE\ncanvas. Like `pds outline`, it reads the walked modules straight, skipping the\nmanifest `[doc]` parse and dependency resolution: a diagram never depends on\npresentation config or external modules.","text":"","module":"cli","href":"module/cli.html#cli-SvgCmd"},{"fqn":"cli::SvgOpts","name":"SvgOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-SvgOpts"},{"fqn":"cli::TokensCmd","name":"TokensCmd","kind":"component","summary":"`pds tokens` — print the conformance token stream (`KIND@line:col \"lexeme\"`)\nto stdout, for debugging the lexer.","text":"","module":"cli","href":"module/cli.html#cli-TokensCmd"},{"fqn":"cli::TokensOpts","name":"TokensOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-TokensOpts"},{"fqn":"cli::UpgradeCmd","name":"UpgradeCmd","kind":"component","summary":"`pds upgrade` — download a release and install it over the running binary.","text":"","module":"cli","href":"module/cli.html#cli-UpgradeCmd"},{"fqn":"cli::UpgradeOpts","name":"UpgradeOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-UpgradeOpts"},{"fqn":"cli::Watcher","name":"Watcher","kind":"component","summary":"Filesystem watcher (`--watch`, via `notify`): watches the project root\nrecursively, debounces each save burst, and on a relevant change rebuilds the\nsite and bumps the version the browser's live-reload poll reads. Events under\nthe output dir are ignored so writing the site never re-triggers a build.","text":"","module":"cli","href":"module/cli.html#cli-Watcher"},{"fqn":"cli::Workspace","name":"Workspace","kind":"data","summary":"The loaded project: the `[doc]` config, the resolved output directory\n(`/`, default `target/doc`), every module sorted by FQN, the\ndependency-name-prefixed dependency modules (§8.3; empty without a `pds.lock`),\nand the preferred doc output format. Produced from the nearest `pds.toml`.","text":"","module":"cli","href":"module/cli.html#cli-Workspace"},{"fqn":"context","name":"context","kind":"module","summary":"","text":"","module":"context","href":"module/context.html"},{"fqn":"context::Developer","name":"Developer","kind":"person","summary":"Author of architecture models: edits `.pds` files in an IDE and runs the CLI.","text":"","module":"context","href":"module/context.html#context-Developer"},{"fqn":"context::Editor","name":"Editor","kind":"system","summary":"IDEs that speak the Language Server Protocol (VS Code, Neovim, …). The editor\nspawns `pds lsp` as a child process and drives it over stdio: it streams\ndocument changes to the server and renders the diagnostics it publishes.","text":"","module":"context","href":"module/context.html#context-Editor"},{"fqn":"context::Pseudoscript","name":"Pseudoscript","kind":"system","summary":"The PseudoScript CLI — loads a `.pds` workspace into one resolved graph and\ndocuments it as a static site, or serves the language to editors. Cross-system\ncallers couple to these published faces; the containers stay behind them.","text":"","module":"context","href":"module/context.html#context-Pseudoscript"},{"fqn":"doc","name":"doc","kind":"module","summary":"","text":"","module":"doc","href":"module/doc.html"},{"fqn":"doc::Assets","name":"Assets","kind":"component","summary":"Ships the prebuilt presentation bundles, authored in the `web/` Svelte package and\nembedded at build time (LANG.md §9.3). `style.css`, `client.js`, and\n`universe.js` ship once at the site root; `ssr.js` is build-time only — the\nengine (`Doc::Ssr`) evaluates it and it is never written to the site. The\nuniverse bundle is the only one that carries WebGL; the SSR bundle never\ndoes.","text":"","module":"doc","href":"module/doc.html#doc-Assets"},{"fqn":"doc::Call","name":"Call","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-Call"},{"fqn":"doc::Codec","name":"Codec","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-Codec"},{"fqn":"doc::Crumb","name":"Crumb","kind":"data","summary":"One breadcrumb hop: its label and href; the trailing crumb (the current\npage) carries no href.","text":"","module":"doc","href":"module/doc.html#doc-Crumb"},{"fqn":"doc::DiagnosticInput","name":"DiagnosticInput","kind":"data","summary":"One host-prepared diagnostic, positioned in its module's source: severity\nword (`error`/`warning`), the stable rule code and its principle-article URL\n(empty for plain diagnostics), the message, and the 1-based line/column with\nthe byte span. The host computes line/column once — this crate never sees\nmodule sources.","text":"","module":"doc","href":"module/doc.html#doc-DiagnosticInput"},{"fqn":"doc::Diagram","name":"Diagram","kind":"data","summary":"A diagram figure. Every view — C4, sequence, data, feature — ships as\ndeterministic server-rendered SVG, identical to `pds svg` (the client adds\npan/zoom only); `kind` is the view word (`c4`/`sequence`/`entity`/`flow`)\nthe figure exposes as its `data-diagram` hook. `Empty` marks a view that\nfailed to project.","text":"","module":"doc","href":"module/doc.html#doc-Diagram"},{"fqn":"doc::Diagrams","name":"Diagrams","kind":"component","summary":"The bridge into `emit`: project a view into a `Scene` and render it to the\nsame deterministic SVG `pds svg` emits, under the adaptive palette so the\nfigure follows the site theme. A view that fails to project degrades to an\n`Empty` placeholder rather than aborting the build — the cargo-doc stance\nthat a partial model still documents. The client adds pan/zoom around the\nfigure; it never re-lays-out.","text":"","module":"doc","href":"module/doc.html#doc-Diagrams"},{"fqn":"doc::Doc","name":"Doc","kind":"container","summary":"`crates/pseudoscript-doc`. Turns a resolved graph into a Svelte-rendered,\ncargo-doc-style site that fully represents the system: each node's `///`\ndocs and relationships on its page, every diagram as deterministic\nserver-rendered SVG, the 3D universe with its flows, the architecture-health\nreport, and a static full-text search index. Pure: returns in-memory files,\nthe CLI writes.","text":"","module":"doc","href":"module/doc.html#doc-Doc"},{"fqn":"doc::DocBody","name":"DocBody","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-DocBody"},{"fqn":"doc::DocConfig","name":"DocConfig","kind":"data","summary":"Site presentation, filled by the CLI from the `[doc]` table of `pds.toml`.\n`[doc]` tunes presentation only, never what is documented.","text":"","module":"doc","href":"module/doc.html#doc-DocConfig"},{"fqn":"doc::DocGroup","name":"DocGroup","kind":"data","summary":"One `[[doc.sidebar]]` group of authored Markdown pages: a heading and its\nordered pages, rendered above the auto-generated module tree.","text":"","module":"doc","href":"module/doc.html#doc-DocGroup"},{"fqn":"doc::DocPage","name":"DocPage","kind":"data","summary":"One authored Markdown page: its sidebar title, the source path (relative to\n`pds.toml`) from which the host and this crate derive the same stable slug,\nand the raw Markdown the host has already loaded, rendered to HTML at build\ntime. The crate reads `markdown`; `path` only names the page, never loads it.","text":"","module":"doc","href":"module/doc.html#doc-DocPage"},{"fqn":"doc::Empty","name":"Empty","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-Empty"},{"fqn":"doc::Engine","name":"Engine","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-Engine"},{"fqn":"doc::Escape","name":"Escape","kind":"component","summary":"All user text — node names, `///` docs, tags, the site title — is carried raw in\nthe props and escaped on interpolation by the renderer; this escaper guards the\nRust-owned document shell, where the title and theme become HTML attributes.","text":"","module":"doc","href":"module/doc.html#doc-Escape"},{"fqn":"doc::Health","name":"Health","kind":"component","summary":"Positions the host's diagnostics in the documentation: prepares raw\nper-module findings (line/column from sources), attributes each to the node\nwhose section it belongs on, and builds the health page and the per-section\nbadges. Attribution is positional: the node whose declaration starts\nclosest before the diagnostic's span owns it, a callable lifting to its\nowner; a span no node encloses falls back to the module page.","text":"","module":"doc","href":"module/doc.html#doc-Health"},{"fqn":"doc::HealthEntry","name":"HealthEntry","kind":"data","summary":"One architecture-health finding, attributed to the node whose section it\nbelongs on: the diagnostic fields plus the owning node's FQN and the\n(prefixed) href to its section — the module page when no node encloses the\nspan.","text":"","module":"doc","href":"module/doc.html#doc-HealthEntry"},{"fqn":"doc::HealthPage","name":"HealthPage","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-HealthPage"},{"fqn":"doc::Highlight","name":"Highlight","kind":"component","summary":"Highlights fenced code blocks at build time, so the shipped site needs no\nclient highlighter and one deterministic output serves every theme:\nPseudoScript through the toolchain's own lexer, emitting class-based spans\ncoloured by the site stylesheet. Any other language passes through escaped\nand unhighlighted — an embedded grammar set for common languages is a\ndeliberate future extension (it would bloat the IDE wasm today).","text":"","module":"doc","href":"module/doc.html#doc-Highlight"},{"fqn":"doc::IndexPage","name":"IndexPage","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-IndexPage"},{"fqn":"doc::Markdown","name":"Markdown","kind":"component","summary":"Renders an authored `[[doc.sidebar]]` page's Markdown source into the HTML that\nfills its `Doc` page body (LANG.md §9.3). The host has already loaded the\nsource into the page's `markdown`; this crate does no I/O and renders that\ncontent in memory. The rendered HTML carries through unescaped as trusted\nauthored content.","text":"","module":"doc","href":"module/doc.html#doc-Markdown"},{"fqn":"doc::MarkdownSite","name":"MarkdownSite","kind":"component","summary":"The static Markdown output mode: turns the same per-page props the HTML\nrenderer consumes into flat `.md` files, each diagram a standalone `.svg`\nasset referenced as an image. The universe page renders as text lists\n(nodes by level, edges with traffic, each flow's legs); the health page as\na table with article links. No SSR engine, no shared assets, no logo — the\nengine-free site for wikis and code hosts.","text":"","module":"doc","href":"module/doc.html#doc-MarkdownSite"},{"fqn":"doc::ModuleCard","name":"ModuleCard","kind":"data","summary":"A module card on the index: its FQN, the (prefixed) href to its page, and an\n\"N item(s)\" line.","text":"","module":"doc","href":"module/doc.html#doc-ModuleCard"},{"fqn":"doc::ModulePage","name":"ModulePage","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-ModulePage"},{"fqn":"doc::NavLink","name":"NavLink","kind":"data","summary":"One header navigation link, its href prefixed for the page's depth; `badge`\ncarries the Health page's finding count (empty elsewhere).","text":"","module":"doc","href":"module/doc.html#doc-NavLink"},{"fqn":"doc::NodeHref","name":"NodeHref","kind":"data","summary":"A universe node's documentation link: its id and the (prefixed) href to its\nsection, so clicking a sphere can navigate to the docs.","text":"","module":"doc","href":"module/doc.html#doc-NodeHref"},{"fqn":"doc::NodeSection","name":"NodeSection","kind":"data","summary":"One node's documentation section: head fields, `///` docs, tags, relationship\ngroups, scenario cards, embedded diagrams, and the health findings attributed\nto the node (rendered as inline badges).","text":"","module":"doc","href":"module/doc.html#doc-NodeSection"},{"fqn":"doc::NodeUrl","name":"NodeUrl","kind":"data","summary":"A resolved cross-reference target: the module page it lives on and its in-page\nanchor id. A cross-link uses these to build an `href`; an unresolved FQN has no\nentry and renders as plain text.","text":"","module":"doc","href":"module/doc.html#doc-NodeUrl"},{"fqn":"doc::PageBody","name":"PageBody","kind":"data","summary":"The page body: the landing index, one authored doc page, one module's page,\nthe 3D universe, or the architecture-health report.","text":"","module":"doc","href":"module/doc.html#doc-PageBody"},{"fqn":"doc::PageProps","name":"PageProps","kind":"data","summary":"The serialisable model for one page, handed to the Svelte server renderer.\nEvery derived value (resolved hrefs, anchors, edge labels, counts) is\nprecomputed here so the Svelte components do no graph logic. User text is\ncarried raw — the renderer escapes it on interpolation. `docGroups` carries\nthe authored `[[doc.sidebar]]` navigation shown above the module tree;\n`nav` carries the header links (Overview, Universe, Health with its finding\ncount); `crumbs` is the breadcrumb trail. Only the universe page's props are\nalso embedded in the document, for its 3D island — every other page ships no\npage data.","text":"","module":"doc","href":"module/doc.html#doc-PageProps"},{"fqn":"doc::Pages","name":"Pages","kind":"component","summary":"Projects the resolved graph into per-page `PageProps` — pure data, no markup.\nBuilds the index props (title, context diagram, module cards), each module page's\nnode sections, and the depth-prefixed sidebar tree mirroring the C4 hierarchy.","text":"","module":"doc","href":"module/doc.html#doc-Pages"},{"fqn":"doc::RelGroup","name":"RelGroup","kind":"data","summary":"One relationship group (\"Parent\", \"Inbound\", or \"Outbound\") and its endpoints.","text":"","module":"doc","href":"module/doc.html#doc-RelGroup"},{"fqn":"doc::RelItem","name":"RelItem","kind":"data","summary":"One relationship endpoint: the edge-kind word, whether to draw the outbound\narrow, the endpoint FQN, its resolved href (absent → plain text), and any label.","text":"","module":"doc","href":"module/doc.html#doc-RelItem"},{"fqn":"doc::Relationships","name":"Relationships","kind":"component","summary":"Renders a node's relationships (LANG.md §9.3): its `for`/owner parent, inbound\nedges, and outbound edges, skipping the structural `for`-parent edge kind. Each\nendpoint is a cross-link when its FQN resolves, plain text otherwise.","text":"","module":"doc","href":"module/doc.html#doc-Relationships"},{"fqn":"doc::RenderError","name":"RenderError","kind":"data","summary":"Why server-rendering a page failed. Every variant is a defect in the bundle, the\nengine, or the props codec — never user model data; well-formed props always\nrender.","text":"","module":"doc","href":"module/doc.html#doc-RenderError"},{"fqn":"doc::RenderedPage","name":"RenderedPage","kind":"data","summary":"The server-render result for one page: the `` contents and the\nrendered body markup, dropped into the document shell.","text":"","module":"doc","href":"module/doc.html#doc-RenderedPage"},{"fqn":"doc::ScenarioCard","name":"ScenarioCard","kind":"data","summary":"One BDD scenario card: name, `///` docs, tags, the ordered steps, and the\nfeature's flow figure (LANG.md §9.5).","text":"","module":"doc","href":"module/doc.html#doc-ScenarioCard"},{"fqn":"doc::Scenarios","name":"Scenarios","kind":"component","summary":"Renders the BDD scenario cards on a node's page: each `feature` targeting the node\nbecomes its given/when/then steps and its flow figure as a card (LANG.md §5.2,\n§9.3, §9.5). Empty when the node has no features.","text":"","module":"doc","href":"module/doc.html#doc-Scenarios"},{"fqn":"doc::SearchEntry","name":"SearchEntry","kind":"data","summary":"One record in the static full-text search index: the symbol or page's FQN,\ndisplay name, kind word, one-line summary, plain-text body, declaring\nmodule, and root-relative href. Sorted by href then FQN so the index is\ndeterministic.","text":"","module":"doc","href":"module/doc.html#doc-SearchEntry"},{"fqn":"doc::SearchIndex","name":"SearchIndex","kind":"component","summary":"Builds the static full-text search index the client palette queries: one\nrecord per linkable node, module page, and authored doc page, plus the\nuniverse and health pages. Shipped as a classic-script JS assignment\n(`search-index.js` setting a window global) so it loads under `file://` —\nnever fetched.","text":"","module":"doc","href":"module/doc.html#doc-SearchIndex"},{"fqn":"doc::SectionDiagnostic","name":"SectionDiagnostic","kind":"data","summary":"One inline badge on a node's section: the severity, rule code, article URL,\nmessage, and source line of a finding attributed to that node.","text":"","module":"doc","href":"module/doc.html#doc-SectionDiagnostic"},{"fqn":"doc::Shell","name":"Shell","kind":"component","summary":"Owns the document shell that wraps each server-rendered body — the part Rust\nrenders, not Svelte: doctype, ``, font + stylesheet links,\nthe theme pre-paint script, and the classic deferred scripts. Scripts are\nclassic (never `type=\"module\"`) and shared data ships as JS-global files, so\nthe site works opened from disk (`file://`). Only the universe page embeds\nits props (`window.__DATA__`) — for its 3D island; every other page ships no\npage data and the client only progressively enhances.","text":"","module":"doc","href":"module/doc.html#doc-Shell"},{"fqn":"doc::SidebarDocGroup","name":"SidebarDocGroup","kind":"data","summary":"One `[[doc.sidebar]]` group in the authored-doc navigation: a heading and its\npage links, shown above the module tree.","text":"","module":"doc","href":"module/doc.html#doc-SidebarDocGroup"},{"fqn":"doc::SidebarDocItem","name":"SidebarDocItem","kind":"data","summary":"One authored doc-page link in the sidebar: its title and the page href, already\nprefixed for the page's depth.","text":"","module":"doc","href":"module/doc.html#doc-SidebarDocItem"},{"fqn":"doc::SidebarModule","name":"SidebarModule","kind":"data","summary":"One sidebar entry: a module-page link and its node tree, hrefs already prefixed\nfor the page's depth.","text":"","module":"doc","href":"module/doc.html#doc-SidebarModule"},{"fqn":"doc::SidebarNode","name":"SidebarNode","kind":"data","summary":"One node in the sidebar tree, recursing into its same-module children.","text":"","module":"doc","href":"module/doc.html#doc-SidebarNode"},{"fqn":"doc::Site","name":"Site","kind":"data","summary":"The whole generated site as in-memory files, in deterministic order: the index,\none page per authored doc (`[[doc.sidebar]]`, in declaration order), then one\npage per module sorted by FQN, plus the shared assets. The doc crate does no\nI/O; the CLI writes each file under the configured output directory.","text":"","module":"doc","href":"module/doc.html#doc-Site"},{"fqn":"doc::SiteBuilder","name":"SiteBuilder","kind":"component","summary":"Orchestrates the whole site: build the URL map, project each page's props, server\nrender it through the SSR engine, wrap it in the document shell, and ship the\nshared assets plus the search index. Pure: returns in-memory `Site` files,\nwrites none.","text":"","module":"doc","href":"module/doc.html#doc-SiteBuilder"},{"fqn":"doc::SiteFile","name":"SiteFile","kind":"data","summary":"One generated file: a site-root-relative `/`-separated path (`index.html`,\n`module/banking.core.html`, `style.css`, `client.js`) and its complete contents.","text":"","module":"doc","href":"module/doc.html#doc-SiteFile"},{"fqn":"doc::SiteInfo","name":"SiteInfo","kind":"data","summary":"Site-wide presentation, identical across pages: the title, the\n`light`/`dark`/`system` theme word, the optional logo filename, and the\n`../` prefix to the site root for this page's depth.","text":"","module":"doc","href":"module/doc.html#doc-SiteInfo"},{"fqn":"doc::SiteStats","name":"SiteStats","kind":"data","summary":"The index page's stats strip: how many systems, containers, components,\nflows, and health findings the model carries.","text":"","module":"doc","href":"module/doc.html#doc-SiteStats"},{"fqn":"doc::Ssr","name":"Ssr","kind":"component","summary":"The server-side render boundary: a JSON-string-in / JSON-string-out seam to the\nJavaScript renderer. The native build embeds a `QuickJS` engine that evaluates the\nprebuilt `ssr.js` bundle (`Doc::Assets`) in-process; a wasm host implements the\nsame seam against its own JS engine. A defect here is a build-asset fault, never\nuser model data.","text":"","module":"doc","href":"module/doc.html#doc-Ssr"},{"fqn":"doc::Step","name":"Step","kind":"data","summary":"One scenario step: its keyword (given/when/then/and/but) and prose.","text":"","module":"doc","href":"module/doc.html#doc-Step"},{"fqn":"doc::Svg","name":"Svg","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-Svg"},{"fqn":"doc::Theme","name":"Theme","kind":"data","summary":"Doc-site colour scheme, written as the `data-theme` attribute on the root\n`` to select a CSS variable set. `System` (the default) follows the\nOS `prefers-color-scheme`, overridable by the in-page toggle; `Light` and\n`Dark` pin the scheme.","text":"","module":"doc","href":"module/doc.html#doc-Theme"},{"fqn":"doc::UniversePage","name":"UniversePage","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-UniversePage"},{"fqn":"doc::Urls","name":"Urls","kind":"component","summary":"Maps every node FQN to its page path and anchor, and resolves cross-references to\nmodule-page-relative `href`s. A module page is `module/.html`\n(`::` → `.`); a node is the `#` anchor within it. Only an FQN naming a real\nnode produces a link (LANG.md §9.3).","text":"","module":"doc","href":"module/doc.html#doc-Urls"},{"fqn":"dot","name":"dot","kind":"module","summary":"","text":"","module":"dot","href":"module/dot.html"},{"fqn":"dot::Box2","name":"Box2","kind":"data","summary":"An axis-aligned box: top-left corner and extent.","text":"","module":"dot","href":"module/dot.html#dot-Box2"},{"fqn":"dot::Cluster","name":"Cluster","kind":"data","summary":"A cluster (a `subgraph cluster_*`): its id, optional parent cluster, member\nnode ids, the margin kept outside its members, and the header band reserved on\nthe title side.","text":"","module":"dot","href":"module/dot.html#dot-Cluster"},{"fqn":"dot::ClusterBox","name":"ClusterBox","kind":"data","summary":"A laid-out cluster: its id and bounding box.","text":"","module":"dot","href":"module/dot.html#dot-ClusterBox"},{"fqn":"dot::Clusters","name":"Clusters","kind":"component","summary":"Cluster framing: compute each cluster's bounding box bottom-up so a nested\ncluster's box encloses its children (grown by their margins) and its direct\nmembers, with header room on the title side — `dot`'s recursive `rec_bb`.","text":"","module":"dot","href":"module/dot.html#dot-Clusters"},{"fqn":"dot::Dot","name":"Dot","kind":"container","summary":"`crates/pseudoscript-dot`. Runs the four-pass layered pipeline over a `Graph`\nto produce a `Layout`; also offers the experimental grid placer. The layout\nauthority `emit` drives for C4 diagrams.","text":"","module":"dot","href":"module/dot.html#dot-Dot"},{"fqn":"dot::Edge","name":"Edge","kind":"data","summary":"An input edge from `tail` to `head`, with its minimum rank span, its layout\nweight (higher pulls the endpoints into vertical alignment harder), and an\noptional label size that widens the rank gap to fit.","text":"","module":"dot","href":"module/dot.html#dot-Edge"},{"fqn":"dot::EdgeRoute","name":"EdgeRoute","kind":"data","summary":"A routed edge: the Bézier `spline` and its `polyline` approximation between\n`tail` and `head`, plus where its label sits.","text":"","module":"dot","href":"module/dot.html#dot-EdgeRoute"},{"fqn":"dot::Engine","name":"Engine","kind":"component","summary":"The layered engine: runs the faithful four-pass `dot` pipeline — rank, order,\nposition, splines — then frames the clusters and shifts the whole layout to\nthe origin. Deterministic and panic-free.","text":"","module":"dot","href":"module/dot.html#dot-Engine"},{"fqn":"dot::Graph","name":"Graph","kind":"data","summary":"The input graph the engine lays out: nodes, edges, clusters, the rank and node\nseparations, the rank direction, and any same-rank constraints.","text":"","module":"dot","href":"module/dot.html#dot-Graph"},{"fqn":"dot::Grid","name":"Grid","kind":"component","summary":"The experimental grid placer: an alternative to the layered pipeline that\nsnaps nodes to a grid, honouring drag-to-pin cells and searching for a\nplacement that minimises crossings, distance, and against-flow edges.","text":"","module":"dot","href":"module/dot.html#dot-Grid"},{"fqn":"dot::GridMeta","name":"GridMeta","kind":"data","summary":"Grid metadata carried back when the experimental grid placer ran: the grid\ndimensions, the cell pitch (centre-to-centre), the pixel centre of cell (0,0),\nand the width of the drag-room frame wrapping the grid — the geometry the IDE\nreads to map a drag onto a grid cell, recovering the raw cell from `origin`\nthen subtracting `pad`.","text":"","module":"dot","href":"module/dot.html#dot-GridMeta"},{"fqn":"dot::GridParams","name":"GridParams","kind":"data","summary":"Tunable weights for the experimental grid placer: how strongly it penalises\ncrossings, distance, and against-flow edges, the cell spacing, and how hard it\nsearches.","text":"","module":"dot","href":"module/dot.html#dot-GridParams"},{"fqn":"dot::Layout","name":"Layout","kind":"data","summary":"The layout result: the overall bounding box, the placed nodes, the routed\nedges, the cluster boxes, and — only when the grid placer ran — its grid\nmetadata.","text":"","module":"dot","href":"module/dot.html#dot-Layout"},{"fqn":"dot::LayoutState","name":"LayoutState","kind":"data","summary":"The structure that flows through a pipeline: the (possibly pass-mutated)\ninput graph and its current placement. A pass receives one, returns one, and\nthe framework hands it to the next pass; a re-ranking pass may edit `graph`\nand re-run the base layout.","text":"","module":"dot","href":"module/dot.html#dot-LayoutState"},{"fqn":"dot::Node","name":"Node","kind":"data","summary":"An input node: its id and its rendered size (the placer reserves this box).","text":"","module":"dot","href":"module/dot.html#dot-Node"},{"fqn":"dot::NodePos","name":"NodePos","kind":"data","summary":"A placed node: its id, centre point, and reserved size.","text":"","module":"dot","href":"module/dot.html#dot-NodePos"},{"fqn":"dot::Optimize","name":"Optimize","kind":"component","summary":"The `ShortenLongEdges` pass: a bounded, deterministic post-layout refinement.\nGreedily search for same-rank moves that reduce the sum of squared edge\nlengths (never merging across a cluster boundary), then re-lay-out with the\nimproving moves applied.","text":"","module":"dot","href":"module/dot.html#dot-Optimize"},{"fqn":"dot::Oracle","name":"Oracle","kind":"component","summary":"The test oracle: lays the same graph out with the real `dot` binary and\ncompares, so the port stays faithful rather than approximate. Skipped when\n`dot` is not installed.","text":"","module":"dot","href":"module/dot.html#dot-Oracle"},{"fqn":"dot::Order","name":"Order","kind":"component","summary":"Pass 2 — ordering: order nodes within each rank to minimise edge crossings,\ninserting virtual nodes for long edges so they route through thin lanes.","text":"","module":"dot","href":"module/dot.html#dot-Order"},{"fqn":"dot::Pass","name":"Pass","kind":"data","summary":"One stage of the composable post-layout pipeline: same structure in, same\nstructure out, so passes fold freely. `ShortenLongEdges` is the long-edge\noptimiser; `GridPlacement` wraps the grid placer so it drops into a pipeline.\nNew behaviour is added by writing a pass, not by threading flags through the\nengine.","text":"","module":"dot","href":"module/dot.html#dot-Pass"},{"fqn":"dot::Pin","name":"Pin","kind":"data","summary":"A pin fixing a node to a grid cell: the node's index into `graph.nodes` (the\ncaller resolves the FQN) at a row and column. The IDE's drag-to-pin.","text":"","module":"dot","href":"module/dot.html#dot-Pin"},{"fqn":"dot::Pipeline","name":"Pipeline","kind":"component","summary":"The composable post-layout pipeline: the base layout produces the starting\n`LayoutState`; each `Pass` folds it into the next. A pass adjusts geometry\ndirectly or edits the input graph (e.g. adds same-rank hints) and re-runs the\nbase layout.","text":"","module":"dot","href":"module/dot.html#dot-Pipeline"},{"fqn":"dot::Position","name":"Position","kind":"component","summary":"Pass 3 — positioning: assign x-coordinates by network simplex on an auxiliary\ngraph (aligning nodes and straightening long edges), and y-coordinates per\nrank stacked with `ranksep` plus any label and cluster-header room.","text":"","module":"dot","href":"module/dot.html#dot-Position"},{"fqn":"dot::Pt","name":"Pt","kind":"data","summary":"A point in layout coordinates.","text":"","module":"dot","href":"module/dot.html#dot-Pt"},{"fqn":"dot::Rank","name":"Rank","kind":"component","summary":"Pass 1 — rank assignment: give each node an integer rank by network simplex,\nbanding each cluster onto contiguous ranks and honouring same-rank constraints.","text":"","module":"dot","href":"module/dot.html#dot-Rank"},{"fqn":"dot::RankDir","name":"RankDir","kind":"data","summary":"The rank direction: top-to-bottom (the C4 default) or left-to-right.","text":"","module":"dot","href":"module/dot.html#dot-RankDir"},{"fqn":"dot::SearchMode","name":"SearchMode","kind":"data","summary":"How the experimental grid placer searches for a placement: pick automatically\nby size, always the bounded heuristic, or brute-force exhaustive (tiny graphs\nonly).","text":"","module":"dot","href":"module/dot.html#dot-SearchMode"},{"fqn":"dot::Size","name":"Size","kind":"data","summary":"A width/height extent.","text":"","module":"dot","href":"module/dot.html#dot-Size"},{"fqn":"dot::Splines","name":"Splines","kind":"component","summary":"Pass 4 — splines: route each edge as a piecewise Bézier through its\nvirtual-node corridor, clipped to the endpoint borders, with a label point at\nthe polyline midpoint.","text":"","module":"dot","href":"module/dot.html#dot-Splines"},{"fqn":"emit","name":"emit","kind":"module","summary":"","text":"","module":"emit","href":"module/emit.html"},{"fqn":"emit::BoundaryFrame","name":"BoundaryFrame","kind":"data","summary":"An enclosing frame of a boundary view: the boundary node's FQN (so the canvas\ncan drill into it), title, C4 kind (frame accent), and the cluster rectangle.","text":"","module":"emit","href":"module/emit.html#emit-BoundaryFrame"},{"fqn":"emit::C4EdgeKind","name":"C4EdgeKind","kind":"data","summary":"The relationship a routed edge expresses: a body `call`, a synthesised\n`trigger` from an initiator, or a `from` `provenance` link. Maps from\n`model::EdgeKind`, dropping the structural `for`-parent link.","text":"","module":"emit","href":"module/emit.html#emit-C4EdgeKind"},{"fqn":"emit::C4Layout","name":"C4Layout","kind":"data","summary":"A dot-placed C4 view for the interactive canvas and the static SVG: canvas\nsize, placed node cards, routed edges, the enclosing boundary frames\n(outermost first; empty for a context view), and — only under experimental\ngrid placement — the grid geometry for drag-to-pin.","text":"","module":"emit","href":"module/emit.html#emit-C4Layout"},{"fqn":"emit::C4Scene","name":"C4Scene","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-C4Scene"},{"fqn":"emit::C4Tweaks","name":"C4Tweaks","kind":"data","summary":"Per-diagram C4 layout tweaks (the UI's \"Layout\" toggles): run the long-edge\noptimiser, lay out left-to-right instead of top-to-bottom, scale the spacing,\nand — experimentally — place by brute-force grid search instead of the layered\nengine, with its own cost dials and search mode.","text":"","module":"emit","href":"module/emit.html#emit-C4Tweaks"},{"fqn":"emit::C4View","name":"C4View","kind":"data","summary":"Which C4 view a `C4Scene` is — drives the golden header keyword.","text":"","module":"emit","href":"module/emit.html#emit-C4View"},{"fqn":"emit::Component","name":"Component","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-Component"},{"fqn":"emit::Container","name":"Container","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-Container"},{"fqn":"emit::Data","name":"Data","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-Data"},{"fqn":"emit::DataEntity","name":"DataEntity","kind":"data","summary":"One entity in a `data` ER view: its FQN, label, disclosed form, rows, whether\nit is the focal type, and its laid-out card rectangle.","text":"","module":"emit","href":"module/emit.html#emit-DataEntity"},{"fqn":"emit::DataLink","name":"DataLink","kind":"data","summary":"A field-type reference link between two entities in a data view: the\nreferencing and referenced FQNs and the field driving the reference.","text":"","module":"emit","href":"module/emit.html#emit-DataLink"},{"fqn":"emit::DataScene","name":"DataScene","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-DataScene"},{"fqn":"emit::Edges","name":"Edges","kind":"component","summary":"Builds the routed-edge set for a C4 scene from the graph's edges: maps each\n`model::EdgeKind` to a `C4EdgeKind` (dropping `for`-parent links), lifts each\nendpoint to the in-view node it belongs to, drops self-loops and\nout-of-view edges, then sorts and de-duplicates.","text":"","module":"emit","href":"module/emit.html#emit-Edges"},{"fqn":"emit::Emit","name":"Emit","kind":"container","summary":"`crates/pseudoscript-emit`. Projects a view into a `Scene` and renders it to\nSVG; C4 views are placed by the `dot` layered engine, the sequence/data/feature\nviews by the matching `layout` engine.","text":"","module":"emit","href":"module/emit.html#emit-Emit"},{"fqn":"emit::EmitError","name":"EmitError","kind":"data","summary":"Why projecting a view failed: the target FQN names no node, or names a node of\nthe wrong kind for the requested view.","text":"","module":"emit","href":"module/emit.html#emit-EmitError"},{"fqn":"emit::EntityForm","name":"EntityForm","kind":"data","summary":"The disclosed form of a data entity, driving the card's eyebrow and rows: a\nrecord of typed fields, a discriminated union of variants, or a black box.","text":"","module":"emit","href":"module/emit.html#emit-EntityForm"},{"fqn":"emit::EntityRow","name":"EntityRow","kind":"data","summary":"One row of a data entity: the field or variant name, its rendered type (empty\nfor a union variant), and the FQN of the data type this row targets when it\nreferences one.","text":"","module":"emit","href":"module/emit.html#emit-EntityRow"},{"fqn":"emit::Feature","name":"Feature","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-Feature"},{"fqn":"emit::FeatureScene","name":"FeatureScene","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-FeatureScene"},{"fqn":"emit::FeatureStepNode","name":"FeatureStepNode","kind":"data","summary":"One step box in a `feature` flow view: the step keyword, its prose, and the\nlaid-out box rectangle.","text":"","module":"emit","href":"module/emit.html#emit-FeatureStepNode"},{"fqn":"emit::Frame","name":"Frame","kind":"data","summary":"A nestable frame over a body of sequence items, drawn as an enclosing box\nwith its condition label.","text":"","module":"emit","href":"module/emit.html#emit-Frame"},{"fqn":"emit::FrameItem","name":"FrameItem","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-FrameItem"},{"fqn":"emit::FrameKind","name":"FrameKind","kind":"data","summary":"The kind of a sequence frame: `if`/`else` becomes an `alt` frame, `for`/\n`while` becomes a `loop` frame.","text":"","module":"emit","href":"module/emit.html#emit-FrameKind"},{"fqn":"emit::GridInfo","name":"GridInfo","kind":"data","summary":"The grid the experimental placement used, in absolute canvas coordinates:\ncell `(r, c)` is centred at `origin + (c·cellW, r·cellH)`; `pad` is the\ndrag-room frame, so a pixel maps to its pin cell by recovering the raw cell\nthen subtracting `pad`.","text":"","module":"emit","href":"module/emit.html#emit-GridInfo"},{"fqn":"emit::GridPin","name":"GridPin","kind":"data","summary":"A node the user has pinned to a grid cell (drag-to-pin), identified by FQN;\nthe emit layer resolves it to a `dot` node index for the current view. Honoured\nonly under experimental grid placement.","text":"","module":"emit","href":"module/emit.html#emit-GridPin"},{"fqn":"emit::GridSearch","name":"GridSearch","kind":"data","summary":"Which grid search the experimental grid placer runs — the UI's\nheuristic-vs-brute-force toggle. Mirrors `dot::SearchMode` so the IDE need not\ndepend on `dot`.","text":"","module":"emit","href":"module/emit.html#emit-GridSearch"},{"fqn":"emit::LaidOutEdge","name":"LaidOutEdge","kind":"data","summary":"A routed edge for the canvas/SVG: its endpoints (`source` stands for the wire\n`from`), kind, stacked labels, the engine's polyline (at least two points),\nthe label position at the polyline midpoint when labels exist, and whether it\ndraws dashed (a `from`-provenance edge).","text":"","module":"emit","href":"module/emit.html#emit-LaidOutEdge"},{"fqn":"emit::LaidOutNode","name":"LaidOutNode","kind":"data","summary":"A node card placed by the `dot` engine for the canvas/SVG: its FQN, C4 kind\n(accent and eyebrow), display label, optional `///` summary, and rectangle.","text":"","module":"emit","href":"module/emit.html#emit-LaidOutNode"},{"fqn":"emit::Layout","name":"Layout","kind":"component","summary":"Assigns geometry. Projection stamps each C4 scene with simple row-flow\ncoordinates (`solveC4`) — the panic-proof fallback geometry every scene\ncarries. The real placement runs at render/canvas time: `layoutC4` drives the\n`dot` layered engine (rank/order/position/splines), framing boundary children\nas a cluster, tuned by per-diagram `C4Tweaks` and drag-to-pin cells. The\nsequence/data/feature scenes are placed by the matching `layout` engine.","text":"","module":"emit","href":"module/emit.html#emit-Layout"},{"fqn":"emit::Lifeline","name":"Lifeline","kind":"data","summary":"A sequence lifeline (a participant): its FQN, C4 kind for the head card, the\nnode's `///` summary shown dimmed under the name, and the structural ancestry\npath under a container/component name. No coordinate — the sequence layout\nassigns the lifeline x.","text":"","module":"emit","href":"module/emit.html#emit-Lifeline"},{"fqn":"emit::Message","name":"Message","kind":"data","summary":"A message between two lifelines (or a self-message). `label` is the method\nname, or `Ok`/`Err`/empty for a return; `detail` is the type detail shown after\nit — a call's `(params): ret` signature or a return's concrete type.","text":"","module":"emit","href":"module/emit.html#emit-Message"},{"fqn":"emit::MessageItem","name":"MessageItem","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-MessageItem"},{"fqn":"emit::MessageKind","name":"MessageKind","kind":"data","summary":"The kind of a sequence message: a `call` to another lifeline, a `return` to\nthe caller, or a `self`-message on the owner's own lifeline.","text":"","module":"emit","href":"module/emit.html#emit-MessageKind"},{"fqn":"emit::PlacedNode","name":"PlacedNode","kind":"data","summary":"A node placed in a C4 view: its FQN, C4 kind, display label, optional `///`\nsummary (the card's dimmed description), the boundary it sits inside, and the\nlayout rectangle assigned by the placer.","text":"","module":"emit","href":"module/emit.html#emit-PlacedNode"},{"fqn":"emit::PointI","name":"PointI","kind":"data","summary":"An integer point on the canvas (a rounded layout coordinate).","text":"","module":"emit","href":"module/emit.html#emit-PointI"},{"fqn":"emit::Projector","name":"Projector","kind":"component","summary":"Projects a `View` out of the graph (LANG.md §9): dispatches on the view\nvariant, collecting nodes and edges for a C4 scene or tracing a callable body\nfor a sequence scene, then hands off to the placer. Fails with `EmitError`\nwhen the view's target is missing or the wrong kind.","text":"","module":"emit","href":"module/emit.html#emit-Projector"},{"fqn":"emit::Rect","name":"Rect","kind":"data","summary":"An axis-aligned layout rectangle in renderer coordinates.","text":"","module":"emit","href":"module/emit.html#emit-Rect"},{"fqn":"emit::RoutedEdge","name":"RoutedEdge","kind":"data","summary":"An edge routed between two C4 nodes: its endpoints, kind, and the merged call\nlabels (sorted and de-duplicated; empty for a trigger or provenance edge).\nParallel calls between the same pair collapse into one multi-label edge.\n`source` is the wire field `from`, renamed because `from` is a PseudoScript\nkeyword.","text":"","module":"emit","href":"module/emit.html#emit-RoutedEdge"},{"fqn":"emit::Scene","name":"Scene","kind":"data","summary":"A laid-out diagram: the notation-neutral IR the SVG renderer consumes. A C4\nscene carries placed nodes and routed edges; a sequence scene carries\nlifelines and ordered messages with nested frames. This IR is the generation\nconformance surface (ADR-017): coordinates are carried for the renderer only,\nnever serialised to the golden.","text":"","module":"emit","href":"module/emit.html#emit-Scene"},{"fqn":"emit::SeqItem","name":"SeqItem","kind":"data","summary":"One ordered item in a sequence trace: a message or a nested frame.","text":"","module":"emit","href":"module/emit.html#emit-SeqItem"},{"fqn":"emit::Sequence","name":"Sequence","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-Sequence"},{"fqn":"emit::SequenceScene","name":"SequenceScene","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-SequenceScene"},{"fqn":"emit::SvgRenderer","name":"SvgRenderer","kind":"component","summary":"Renders a laid-out `Scene` to standalone SVG markup — string-building, no\ntemplate engine and no headless browser (LANG.md §9.3, ADR-017). Dispatches\non the scene variant; the layout engine is wrapped so no input panics the\nrender, falling back to the scene's own simple coordinates.","text":"","module":"emit","href":"module/emit.html#emit-SvgRenderer"},{"fqn":"emit::Trace","name":"Trace","kind":"component","summary":"Walks a callable's body trace into ordered sequence items, registering each\nparticipant on first appearance (LANG.md §9.2, §7). A real trigger's\ninitiator leads as the first lifeline with a synthesised inbound call;\nabsent a trigger the owner leads. A `call` is a message to the target; a\ndisclosed callee expands inline; a `self.` call is a self-message; a return\nmarks `Ok`/`Err`; `if`/`else` nests an `alt` frame, `for`/`while` a `loop`\nframe.","text":"","module":"emit","href":"module/emit.html#emit-Trace"},{"fqn":"emit::UnknownNode","name":"UnknownNode","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-UnknownNode"},{"fqn":"emit::View","name":"View","kind":"data","summary":"Which view to project from the graph (LANG.md §9). Context is the C1 whole;\nContainer/Component zoom into one boundary; Sequence traces one entry point;\nData draws a `data` type's entity (ER) view (§9.4); Feature draws a `feature`\nscenario's flow (§9.5).","text":"","module":"emit","href":"module/emit.html#emit-View"},{"fqn":"emit::WrongKind","name":"WrongKind","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-WrongKind"},{"fqn":"format","name":"format","kind":"module","summary":"","text":"","module":"format","href":"module/format.html"},{"fqn":"format::Format","name":"Format","kind":"container","summary":"`crates/pseudoscript-format`. The canonical formatter: parse, then\npretty-print the tree to one canonical form. The string-to-string entry the\nCLI and LSP depend on.","text":"","module":"format","href":"module/format.html#format-Format"},{"fqn":"format::FormatError","name":"FormatError","kind":"data","summary":"Why formatting failed. The only failure is unparseable input: `Parse`\ncarries the rendered error messages (one per diagnostic) for context, and\nthe caller keeps its original text.","text":"","module":"format","href":"module/format.html#format-FormatError"},{"fqn":"format::Formatter","name":"Formatter","kind":"component","summary":"Drives the headline flow: parse the source, short-circuit to\n`FormatError::Parse` if any error diagnostic surfaced, else hand the tree to\nthe printer for canonical text.","text":"","module":"format","href":"module/format.html#format-Formatter"},{"fqn":"format::Parse","name":"Parse","kind":"data","summary":"","text":"","module":"format","href":"module/format.html#format-Parse"},{"fqn":"format::Printer","name":"Printer","kind":"component","summary":"The canonical pretty-printer: walks the AST top-down, appending to a buffer,\nand emits one canonical form. Indentation is a level counter (two spaces\neach); leading trivia is normalised — blank-line runs collapse to at most one\nseparator and `//` / `/* */` comments reproduce at the current indent in\nsource order. Internals are black-boxed per construct; the top-level `print`\nflow is disclosed.","text":"","module":"format","href":"module/format.html#format-Printer"},{"fqn":"format::PrinterState","name":"PrinterState","kind":"data","summary":"The running pretty-printer state: the text buffer being built, the current\nnesting depth (two spaces each), and whether the current line has had its\nindentation written yet so further writes append in place.","text":"","module":"format","href":"module/format.html#format-PrinterState"},{"fqn":"ide","name":"ide","kind":"module","summary":"","text":"","module":"ide","href":"module/ide.html"},{"fqn":"ide::Bootstrap","name":"Bootstrap","kind":"component","summary":"Boots the IDE in a browser tab: load the one wasm bundle (IdeSession), then\nrestore the URL hash's workspace — a share link mounts in memory and needs\nno disk — or fall back to the launcher. File System Access support gates\nonly the disk features (open folder, save, watch): without it those actions\nare disabled with a notice, while share links and the bundled examples\nstill open.","text":"","module":"ide","href":"module/ide.html#ide-Bootstrap"},{"fqn":"ide::BrowserWorkspace","name":"BrowserWorkspace","kind":"data","summary":"The in-browser workspace state the session holds: the open `.pds` modules, the\n`pds.toml` manifest text, and the dependency modules resolved from\n`pds_modules/` as externals (LANG.md §8.3). Held in memory — a decoded\nsample/share link or a local directory loaded through the file-system port.\nEdits never leave the tab.","text":"","module":"ide","href":"module/ide.html#ide-BrowserWorkspace"},{"fqn":"ide::CodeMirrorAdapter","name":"CodeMirrorAdapter","kind":"component","summary":"The editor adapter over CodeMirror 6: the text/cursor model, the completion\nsource, the hover tooltip, the diagnostics gutter, and edit events forwarded\ninto the session. The only place CodeMirror specifics live — it drives the\nsession and applies the typed values it gets back.","text":"","module":"ide","href":"module/ide.html#ide-CodeMirrorAdapter"},{"fqn":"ide::Completion","name":"Completion","kind":"data","summary":"A completion candidate offered as the author types: the inserted label, the\ninteger LSP `CompletionItemKind` the editor maps to an icon, and detail text\n(absent for candidates with nothing to add).","text":"","module":"ide","href":"module/ide.html#ide-Completion"},{"fqn":"ide::DiagramCanvas","name":"DiagramCanvas","kind":"component","summary":"The diagram canvas: draws the **JSON** `Scene` interactively (Svelte Flow\npan/zoom over geometry positioned by `emit`'s layout engine for C4, a\ndepth-collapsible timeline for sequences), and exports the current view as\n**PNG or SVG**, both produced in the browser from the drawn diagram. JSON is\nthe draw format; no diagram image crosses the wasm surface. The editor\nport's diagram seam binds to this.","text":"","module":"ide","href":"module/ide.html#ide-DiagramCanvas"},{"fqn":"ide::Diagrams","name":"Diagrams","kind":"component","summary":"Diagram projection: builds the workspace graph and projects a view to the\nJSON `Scene` via `emit::Projector`; the host's canvas draws it. The wasm\nsurface carries only the JSON scene and layout — SVG and PNG are export\nconcerns of the canvas (PNG rasterised in the browser), and the static SVG\nform is a CLI/doc concern of the `emit` crate, not exposed here.","text":"","module":"ide","href":"module/ide.html#ide-Diagrams"},{"fqn":"ide::DocConfig","name":"DocConfig","kind":"data","summary":"The doc-site build input: the manifest plus each authored page's Markdown,\nloaded by the host. Field detail is the wasm boundary's concern.","text":"","module":"ide","href":"module/ide.html#ide-DocConfig"},{"fqn":"ide::Docs","name":"Docs","kind":"component","summary":"The doc-site builder: renders the whole site in-browser through the same\n`pseudoscript-doc` crate `pds doc` runs, driving SSR through a render\ncallback the host supplies (the browser's JS engine is the SSR engine) — so\nthe IDE writes the exact site the CLI ships, health page included. The site\nbuilds to the opened folder's `target/doc/` only; an in-memory workspace is\ntold to open a folder first (no blob-preview path).","text":"","module":"ide","href":"module/ide.html#ide-Docs"},{"fqn":"ide::FimClient","name":"FimClient","kind":"component","summary":"Provider-agnostic completion client: one `complete` contract over\nOpenAI-compatible HTTP, so one client covers Codestral, OpenRouter, Together,\nOllama, and vLLM. Stateless — the caller hands it the settings snapshot and\nowns cancellation (`GhostText` aborts the in-flight request on the next\nkeystroke; the client honours the abort). Every failure comes back as a\nclassified `ProviderError`, so the caller can surface why a request bailed.","text":"","module":"ide","href":"module/ide.html#ide-FimClient"},{"fqn":"ide::FimPrompt","name":"FimPrompt","kind":"data","summary":"The assembled fill-in-the-middle request: the windowed buffer text before and\nafter the caret, and the cached grammar primer that steers a general model to\nemit valid PseudoScript.","text":"","module":"ide","href":"module/ide.html#ide-FimPrompt"},{"fqn":"ide::FsAccessAdapter","name":"FsAccessAdapter","kind":"component","summary":"The file-system adapter over the browser File System Access API: directory\npicker, recursive walk for `.pds` modules, deriving each module's flat FQN\nfrom its path (LANG.md §8.1, ADR-031), `pds.lock` + `pds_modules/` reads for\ndependency externals, and per-file read/write through handles. The only place\nFS-Access specifics live; it pushes what it reads into the session via\n`mount`.","text":"","module":"ide","href":"module/ide.html#ide-FsAccessAdapter"},{"fqn":"ide::GhostText","name":"GhostText","kind":"component","summary":"Inline ghost-text completion over CodeMirror 6, beside the grammar dropdown:\nstructural candidates stay local and deterministic in `CodeMirrorAdapter`;\nthe remote model supplies only multi-token greyed text. CodeMirror has no\nnative inline-suggestion UI, so this is a custom view plugin rendering a\nwidget decoration at the caret — Tab accepts, Esc dismisses, any edit clears.","text":"","module":"ide","href":"module/ide.html#ide-GhostText"},{"fqn":"ide::GridPin","name":"GridPin","kind":"data","summary":"A node the user has dragged onto a grid cell, identified by FQN. Carried inside\n`LayoutTweaks`; honoured only under experimental grid placement.","text":"","module":"ide","href":"module/ide.html#ide-GridPin"},{"fqn":"ide::IdeSession","name":"IdeSession","kind":"container","summary":"`crates/pseudoscript-ide` — the IDE application, Rust compiled to a single\nwasm. It owns the workspace state and is the whole browser API: the host\npushes modules in (`mount`/`setSource`) and pulls every answer out as a\ntyped value — the session never calls the DOM, CodeMirror, or the File\nSystem Access API. It delegates language intelligence to `lsp_core` and\n`model::Checks`, diagrams to `emit::Projector`, the 3D universe to\n`universe::Adapter`, and the doc site to `doc::SiteBuilder` — each a Rust\nlibrary linked in, never a separate wasm. The one outbound seam is the doc\nbuild's SSR render callback the host supplies. Everything here is Rust.","text":"","module":"ide","href":"module/ide.html#ide-IdeSession"},{"fqn":"ide::LanguageSession","name":"LanguageSession","kind":"component","summary":"The language session: diagnostics, completion, hover, go-to, references,\nrename, outline, folding, and semantic tokens over the held workspace, every\nanswer delegated to the pure toolchain logic (`model::Checks` for diagnostics,\n`lsp_core` for cursor features) with the dependency externals bound. Every\nanswer is a typed value the host applies — the session pushes nothing at the\neditor.","text":"","module":"ide","href":"module/ide.html#ide-LanguageSession"},{"fqn":"ide::Launcher","name":"Launcher","kind":"component","summary":"The project launcher — the first surface. Offers the recents, open-a-folder,\nNew Project, and the bundled examples. Examples and share links mount in\nmemory and need no disk; only the disk actions gate on File System Access\nsupport. Dismissible, so the menus, help, and settings stay reachable\nwithout a project.","text":"","module":"ide","href":"module/ide.html#ide-Launcher"},{"fqn":"ide::LayoutTweaks","name":"LayoutTweaks","kind":"data","summary":"The IDE's per-diagram C4 layout toggles, crossing to JS as a typed object: run\nthe long-edge optimiser, the reading `orientation` (`\"tb\"`/`\"lr\"`), a `spacing`\npreset (`\"compact\"`/`\"comfortable\"`/`\"roomy\"`), and the experimental grid dials\nand pins. Translated to `emit::C4Tweaks` inside the session.","text":"","module":"ide","href":"module/ide.html#ide-LayoutTweaks"},{"fqn":"ide::LlmProvider","name":"LlmProvider","kind":"system","summary":"The author's configured OpenAI-compatible completion provider — hosted\n(OpenAI, Codestral, OpenRouter, Together) or local (Ollama, vLLM). External:\nthe IDE only speaks its HTTP API; transport and HTTP failures surface as\nclassified `ProviderError`s.","text":"","module":"ide","href":"module/ide.html#ide-LlmProvider"},{"fqn":"ide::LlmSettings","name":"LlmSettings","kind":"data","summary":"The author's AI-completion provider settings, persisted in the browser: the\nfeature toggle, the chosen preset (`\"ollama\"`, `\"openai\"`, or `\"custom\"` —\nthe presets pin the endpoint and wire shape so setup is pick-and-go), the\nOpenAI-compatible endpoint, the bring-your-own API key (stored in\nlocalStorage, sent only to the configured endpoint), the model id, and the\nwire shape (`\"fim\"` for a native fill-in-the-middle route, `\"chat\"` for the\nchat-completions fallback).","text":"","module":"ide","href":"module/ide.html#ide-LlmSettings"},{"fqn":"ide::LlmSettingsStore","name":"LlmSettingsStore","kind":"component","summary":"The author's AI-completion settings, persisted to browser localStorage and\nedited through the Settings dialog's \"AI Completion\" tab. Bring-your-own key:\nit never leaves the tab except on requests to the configured endpoint.\nSetup is preset-first: picking a provider applies its pinned endpoint and\nwire shape, so only the Custom preset exposes the raw fields. The store also\ncarries the session's last completion failure (never persisted) so the\nstatus chip and the settings tab can show why ghost text is bailing.","text":"","module":"ide","href":"module/ide.html#ide-LlmSettingsStore"},{"fqn":"ide::LocalInput","name":"LocalInput","kind":"data","summary":"One local-source dependency file the host reads for `dependencyModules`: the\ndependency name (ADR-026), its FQN within the dependency workspace, and its\nsource.","text":"","module":"ide","href":"module/ide.html#ide-LocalInput"},{"fqn":"ide::Occurrence","name":"Occurrence","kind":"data","summary":"One occurrence of a symbol: the module it lies in, its line/column span, the\nsource line text with the match offsets within it, and whether it is the\ndeclaration site.","text":"","module":"ide","href":"module/ide.html#ide-Occurrence"},{"fqn":"ide::OutlineNode","name":"OutlineNode","kind":"data","summary":"One row of the workspace outline: a node's FQN, simple name, C4 kind,\ncontainment parent (absent on roots), whether it is a triggered flow entry,\nits `///` summary (absent when undocumented), and its declaration line/column.","text":"","module":"ide","href":"module/ide.html#ide-OutlineNode"},{"fqn":"ide::PickError","name":"PickError","kind":"data","summary":"Why a folder pick yielded no workspace: the picker failed — the API missing,\nblocked by permission policy, or denied by the embedder. Distinct from a\nuser cancel, which is silent: a cancel is the user's choice, a failure is\nthe product's to explain.","text":"","module":"ide","href":"module/ide.html#ide-PickError"},{"fqn":"ide::ProviderError","name":"ProviderError","kind":"data","summary":"A classified completion-provider failure: `kind` is `\"network\"` (endpoint\nunreachable or CORS-blocked), `\"auth\"` (401/403), `\"notFound\"` (404 route or\nmodel), `\"timeout\"`, or `\"http\"` (any other non-OK status); `message` states\nwhat failed and `hint` names the fix for the configured provider (e.g. the\n`OLLAMA_ORIGINS` command for a CORS-blocked local Ollama).","text":"","module":"ide","href":"module/ide.html#ide-ProviderError"},{"fqn":"ide::References","name":"References","kind":"data","summary":"The find-references result for the symbol under the caret: the symbol's FQN, a\nhuman title, and every occurrence across the workspace.","text":"","module":"ide","href":"module/ide.html#ide-References"},{"fqn":"ide::RenameSelection","name":"RenameSelection","kind":"data","summary":"One occurrence the user chose to rename, keyed by the module FQN and the\n1-based line/column an `ide::Occurrence` reported. The rename rewrites only\nthe selected occurrences, not every match.","text":"","module":"ide","href":"module/ide.html#ide-RenameSelection"},{"fqn":"ide::RenamedSource","name":"RenamedSource","kind":"data","summary":"One module rewritten by a rename: its FQN and new source text.","text":"","module":"ide","href":"module/ide.html#ide-RenamedSource"},{"fqn":"ide::RenderedFile","name":"RenderedFile","kind":"data","summary":"One file of the rendered documentation site: its site-relative path and\ncontents.","text":"","module":"ide","href":"module/ide.html#ide-RenderedFile"},{"fqn":"ide::Samples","name":"Samples","kind":"component","summary":"The bundled example catalogue, discovered at build time from the samples\nfolder (one folder per example: meta.json, the `.pds` modules, `pds.toml`,\ndoc pages). Each entry doubles as a New-project template and as an\nin-memory workspace.","text":"","module":"ide","href":"module/ide.html#ide-Samples"},{"fqn":"ide::Universe","name":"Universe","kind":"component","summary":"The 3D universe: builds the software graph from the held workspace and flattens\nit to a snapshot the `ForceGraph` renders, with the entry-point flows traced\nin Rust. Delegates to the `universe` crate; the wasm surface carries only the\nflat snapshot and flows — positions are the renderer's.","text":"","module":"ide","href":"module/ide.html#ide-Universe"},{"fqn":"ide::UniverseEdge","name":"UniverseEdge","kind":"data","summary":"One directed relationship in the 3D graph, weighted by traffic (call count).\nThe wire field is `from`; `from` is a PseudoScript keyword, so the model\nnames it `source`.","text":"","module":"ide","href":"module/ide.html#ide-UniverseEdge"},{"fqn":"ide::UniverseNode","name":"UniverseNode","kind":"data","summary":"One node in the 3D relationship graph the IDE draws: its FQN, C4 level, and\ncontainment parent (absent on roots). The typed boundary form of a\n`universe::NodeOut`.","text":"","module":"ide","href":"module/ide.html#ide-UniverseNode"},{"fqn":"ide::UniverseSnapshot","name":"UniverseSnapshot","kind":"data","summary":"The 3D-view snapshot the session hands the `ForceGraph`: nodes and directed\nweighted edges, no positions (the renderer lays them out). The typed boundary\nform of a `universe::Snapshot`.","text":"","module":"ide","href":"module/ide.html#ide-UniverseSnapshot"},{"fqn":"ide::VendoredInput","name":"VendoredInput","kind":"data","summary":"One vendored git-dependency file the host reads for `dependencyModules`: its\n`pds_modules/` slug, its FQN within the dependency workspace (the host's\npath→FQN derivation, LANG.md §8.1), and its source.","text":"","module":"ide","href":"module/ide.html#ide-VendoredInput"},{"fqn":"ide::WebIde","name":"WebIde","kind":"container","summary":"`web-ide` — the IDE at `ide.pdscript.dev`: a SvelteKit shell on Cloudflare\nWorkers static assets. Pure glue — it loads the one wasm bundle and binds the\nadapters to the session's ports. It holds no language logic.","text":"","module":"ide","href":"module/ide.html#ide-WebIde"},{"fqn":"ide::Workspace","name":"Workspace","kind":"component","summary":"The in-memory project the session holds: the parsed modules keyed by FQN (an\nopen buffer overlays via `setSource`), the dependency externals, and the\nmemoised resolved workspace + graph — invalidated by any mutation, rebuilt\nonce for the query burst between keystrokes. Edits never leave the tab; an\nexplicit save writes through the host's file handles, not this session.","text":"","module":"ide","href":"module/ide.html#ide-Workspace"},{"fqn":"landing","name":"landing","kind":"module","summary":"","text":"","module":"landing","href":"module/landing.html"},{"fqn":"landing::Landing","name":"Landing","kind":"container","summary":"`web-landing` — the marketing site at `pdscript.dev`. A static Svelte build on\nCloudflare Workers static assets; no backend. It routes a visitor to one of\nthree surfaces and otherwise just tells the model-driven story.","text":"","module":"landing","href":"module/landing.html#landing-Landing"},{"fqn":"landing::Visitor","name":"Visitor","kind":"person","summary":"A prospective user reading the landing page.","text":"","module":"landing","href":"module/landing.html#landing-Visitor"},{"fqn":"layout","name":"layout","kind":"module","summary":"","text":"","module":"layout","href":"module/layout.html"},{"fqn":"layout::Activation","name":"Activation","kind":"data","summary":"An execution-activation bar on a lifeline, spanning a participant's first-to-\nlast involvement; `owner` marks the entry's focus lifeline.","text":"","module":"layout","href":"module/layout.html#layout-Activation"},{"fqn":"layout::Core","name":"Core","kind":"component","summary":"The shared geometry/text core: measures label and title widths, wraps\ndescriptions to a width, and accumulates bounding boxes. Every engine builds\non it, so text sizing and bounds math live in exactly one place.","text":"","module":"layout","href":"module/layout.html#layout-Core"},{"fqn":"layout::Diagram","name":"Diagram","kind":"data","summary":"A structural sequence diagram: the participants (lifelines) and the ordered\nitems — messages and nested fragments — traced down them. The sequence\nengine's input.","text":"","module":"layout","href":"module/layout.html#layout-Diagram"},{"fqn":"layout::Divider","name":"Divider","kind":"data","summary":"A horizontal split between two fragment sections: its y and the following\nsection's guard.","text":"","module":"layout","href":"module/layout.html#layout-Divider"},{"fqn":"layout::FragKind","name":"FragKind","kind":"data","summary":"The kind of a fragment frame: an `alt` (if/else) or a `loop` (for/while).","text":"","module":"layout","href":"module/layout.html#layout-FragKind"},{"fqn":"layout::Fragment","name":"Fragment","kind":"data","summary":"A nestable fragment over a body of items, drawn as an enclosing frame: its\nkind and the labelled sections it splits into (an `alt` has several).","text":"","module":"layout","href":"module/layout.html#layout-Fragment"},{"fqn":"layout::FragmentItem","name":"FragmentItem","kind":"data","summary":"","text":"","module":"layout","href":"module/layout.html#layout-FragmentItem"},{"fqn":"layout::Item","name":"Item","kind":"data","summary":"One ordered item down the lifelines: a message or a nested fragment.","text":"","module":"layout","href":"module/layout.html#layout-Item"},{"fqn":"layout::Layout","name":"Layout","kind":"container","summary":"`crates/pseudoscript-layout`. The geometry/text `Core` and the sequence\nengine, each built on the `Projection` contract — turn a structural input\ninto a positioned output under tunable metrics. The sequence engine is the\nonly one implemented here: C4 placement is delegated to `dot` (the layered\nengine), which `emit` drives directly; a flowchart engine is a planned\nextension point on the same core, not yet built.","text":"","module":"layout","href":"module/layout.html#layout-Layout"},{"fqn":"layout::Message","name":"Message","kind":"data","summary":"A message between two lifelines: its source and target ids (equal for a\nself-message), kind, primary label (method name or return marker), and the\ndimmed detail after it (a call signature or return type). `source` is the\nwire field `from`, renamed because `from` is a PseudoScript keyword.","text":"","module":"layout","href":"module/layout.html#layout-Message"},{"fqn":"layout::MessageItem","name":"MessageItem","kind":"data","summary":"","text":"","module":"layout","href":"module/layout.html#layout-MessageItem"},{"fqn":"layout::Metrics","name":"Metrics","kind":"data","summary":"Tunable spacing and font metrics for the sequence engine — head-card sizing,\nrow advance per message kind, fragment padding. Field detail is the engine's\nconcern; the defaults are the one source of truth consumers start from.","text":"","module":"layout","href":"module/layout.html#layout-Metrics"},{"fqn":"layout::MsgKind","name":"MsgKind","kind":"data","summary":"The kind of a sequence message: a call to another lifeline, a return to the\ncaller, or a self-message on the sender's own lifeline.","text":"","module":"layout","href":"module/layout.html#layout-MsgKind"},{"fqn":"layout::Participant","name":"Participant","kind":"data","summary":"One lifeline: its id (FQN), display label, C4 kind for the head card, optional\nsummary, and the ancestry path shown under the name for container/component\nlifelines.","text":"","module":"layout","href":"module/layout.html#layout-Participant"},{"fqn":"layout::PlacedFragment","name":"PlacedFragment","kind":"data","summary":"A placed fragment frame: its box, the operator-tab label (the first section's\nguard), and the dividers splitting later sections.","text":"","module":"layout","href":"module/layout.html#layout-PlacedFragment"},{"fqn":"layout::PlacedMessage","name":"PlacedMessage","kind":"data","summary":"A placed message: its kind, source/target ids, endpoint x-positions, row y,\ndirection (+1 left-to-right, -1 right-to-left), reading-order step, label, and\ndetail.","text":"","module":"layout","href":"module/layout.html#layout-PlacedMessage"},{"fqn":"layout::PlacedParticipant","name":"PlacedParticipant","kind":"data","summary":"A placed lifeline: its id, label, kind, ancestry path, wrapped summary lines,\nhead-card rectangle, the lifeline's centre x, and the y span of its dashed\nline.","text":"","module":"layout","href":"module/layout.html#layout-PlacedParticipant"},{"fqn":"layout::Point","name":"Point","kind":"data","summary":"A point in renderer coordinates.","text":"","module":"layout","href":"module/layout.html#layout-Point"},{"fqn":"layout::Rect","name":"Rect","kind":"data","summary":"An axis-aligned rectangle: its top-left corner and extent.","text":"","module":"layout","href":"module/layout.html#layout-Rect"},{"fqn":"layout::Section","name":"Section","kind":"data","summary":"One compartment of a fragment: its guard label (shown in the operator tab or\nbeside a divider) over a body of items.","text":"","module":"layout","href":"module/layout.html#layout-Section"},{"fqn":"layout::Sequence","name":"Sequence","kind":"component","summary":"The sequence engine: lays a structural `Diagram` out into a `SequenceLayout` —\nlifeline x-positions in first-appearance order, message rows stacked by\nevaluation order, and fragment frames sized to their bodies. The only engine\nimplemented in this crate; C4 placement is `dot`'s job (`emit` drives it), and\na flowchart engine would follow the same `Projection` contract.","text":"","module":"layout","href":"module/layout.html#layout-Sequence"},{"fqn":"layout::SequenceLayout","name":"SequenceLayout","kind":"data","summary":"A laid-out sequence diagram: the canvas size plus every participant, message,\nactivation bar, and fragment placed at absolute coordinates the renderer draws\nverbatim. The sequence engine's output.","text":"","module":"layout","href":"module/layout.html#layout-SequenceLayout"},{"fqn":"layout::Size","name":"Size","kind":"data","summary":"A width/height extent.","text":"","module":"layout","href":"module/layout.html#layout-Size"},{"fqn":"lsp","name":"lsp","kind":"module","summary":"","text":"","module":"lsp","href":"module/lsp.html"},{"fqn":"lsp::DefTarget","name":"DefTarget","kind":"data","summary":"A go-to-definition target straight from `lsp_core`: the FQN of the module the\ndefinition lives in, and its span within that module's source. The server maps\n`fqn` to a file URI — the target file, not the file the request came from.","text":"","module":"lsp","href":"module/lsp.html#lsp-DefTarget"},{"fqn":"lsp::Lsp","name":"Lsp","kind":"container","summary":"`crates/pseudoscript-lsp`. The stdio language server (tower-lsp): workspace\ndiagnostics on change, and hover, definition, completion, references, rename,\nsemantic tokens, symbols, folding, and formatting — every language answer\ndelegated to `lsp_core`. Owns the transport, the document store, and the\nURI↔FQN mapping, nothing more.","text":"","module":"lsp","href":"module/lsp.html#lsp-Lsp"},{"fqn":"lsp::Occurrence","name":"Occurrence","kind":"data","summary":"One resolved editor location: the file URI a target FQN maps back to, and the\nrange within it. The server's transport-level mapping of a `lsp_core` answer.","text":"","module":"lsp","href":"module/lsp.html#lsp-Occurrence"},{"fqn":"lsp::OpenDocument","name":"OpenDocument","kind":"data","summary":"One open editor buffer: its document URI, current source text, and the\neditor's version counter. The buffer overlays on-disk text until closed.","text":"","module":"lsp","href":"module/lsp.html#lsp-OpenDocument"},{"fqn":"lsp::Server","name":"Server","kind":"component","summary":"The tower-lsp `Backend`: owns the stdio transport, the document store, the\nserver lifecycle, and request routing. Holds no language logic — it locks the\nstore, hands the active text and the resolved workspace to a `lsp_core`\nhandler, and maps the result back to file locations.","text":"","module":"lsp","href":"module/lsp.html#lsp-Server"},{"fqn":"lsp::Store","name":"Store","kind":"component","summary":"The workspace document store: the server's own filesystem edge. Discovers the\nproject root (`pds.toml`, §8.1) by walking up from the opened URI, walks the\ntree for every visible `.pds` file, derives each module's FQN from its path,\nand overlays open buffers on disk text. Caches each parse and memoises the\nresolved `model::Workspace` — built from the local parses with the workspace's\ndirect-dependency externals bound (§8.3) — both invalidated on any edit. The\nexternals are cached separately and reload only on a dependency change.","text":"","module":"lsp","href":"module/lsp.html#lsp-Store"},{"fqn":"lsp_core","name":"lsp_core","kind":"module","summary":"","text":"","module":"lsp_core","href":"module/lsp_core.html"},{"fqn":"lsp_core::Analysis","name":"Analysis","kind":"component","summary":"Document-level analysis: parse-and-check diagnostics, Markdown hover for the\nsymbol under the caret (falling back to an inferred type), go-to-definition,\ninlay hints, and the canonical format edit. Resolution is delegated to\n`model`; this maps the result into LSP shapes.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Analysis"},{"fqn":"lsp_core::Complete","name":"Complete","kind":"component","summary":"Completion: the candidates offered at a byte offset, resolved across the\nworkspace and its externals. A `.`/`::` boundary drives member and path\ncompletion.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Complete"},{"fqn":"lsp_core::CompletionItem","name":"CompletionItem","kind":"data","summary":"One completion candidate: the inserted label, the integer LSP\n`CompletionItemKind`, and detail text.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-CompletionItem"},{"fqn":"lsp_core::Convert","name":"Convert","kind":"component","summary":"Position conversion: the single home for mapping between byte offsets and\nUTF-16 line/character positions, so every handler agrees on coordinates.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Convert"},{"fqn":"lsp_core::DefTarget","name":"DefTarget","kind":"data","summary":"A go-to-definition target: the FQN of the module the definition lives in and\nthe byte span of the definition within that module's source. The consuming\nedge maps `fqn` to a file URI and converts `span` to an editor range against\n*that* file's text — `lsp_core` returns the byte span, not a `Range`, because\nit does not hold the target module's source to convert against.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-DefTarget"},{"fqn":"lsp_core::Diagnostic","name":"Diagnostic","kind":"data","summary":"One diagnostic mapped to LSP shape: its range, severity, message, optional\ncode, and the code's article URL (`codeDescription`, the editor's clickable\nlink — empty when the code carries no article). Converted from the\nparser/checker's byte-span diagnostics; the code and its URL pass through\nunchanged.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Diagnostic"},{"fqn":"lsp_core::Hover","name":"Hover","kind":"data","summary":"Markdown hover content the editor renders in a tooltip.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Hover"},{"fqn":"lsp_core::LspCore","name":"LspCore","kind":"container","summary":"`crates/pseudoscript-lsp-core`. The single home for language intelligence as\ntransport-neutral handlers — the same logic the stdio server and the browser\nwasm both call.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-LspCore"},{"fqn":"lsp_core::Position","name":"Position","kind":"data","summary":"A zero-based caret position: line and UTF-16 character offset within the\nline. This and the shapes below model the standalone `lsp_types` vocabulary\nthe crate returns (pinned to the version tower-lsp re-exports, wasm-safe),\nso the server edge passes values through with no conversion.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Position"},{"fqn":"lsp_core::Range","name":"Range","kind":"data","summary":"A half-open text range between two positions.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Range"},{"fqn":"lsp_core::Refs","name":"Refs","kind":"component","summary":"References and rename: every occurrence of the symbol under the caret across\nthe workspace, the document highlights for it, and the workspace-wide rename\nedits.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Refs"},{"fqn":"lsp_core::Semantic","name":"Semantic","kind":"component","summary":"Semantic tokens: AST-aware highlighting that colours each token by its\ndeclared role, plus the legend the editor binds.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Semantic"},{"fqn":"lsp_core::Symbols","name":"Symbols","kind":"component","summary":"Document and workspace structure: the outline symbols, the foldable ranges,\nand the workspace-wide symbol query.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Symbols"},{"fqn":"lsp_core::TextEdit","name":"TextEdit","kind":"data","summary":"A text edit: replace `range` with `newText`. Formatting and rename return\nthese.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-TextEdit"},{"fqn":"model","name":"model","kind":"module","summary":"","text":"","module":"model","href":"module/model.html"},{"fqn":"model::Alt","name":"Alt","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Alt"},{"fqn":"model::ArchRule","name":"ArchRule","kind":"data","summary":"One architectural-principle lint rule (LANG.md §9 — the C4 facade/boundary\nprinciples). `code` is its stable identifier (`PDS-ARCH-001`); `slug` is the\nfilename of the article documenting it under the published principles base\nURL. Every firing is a `Warning` — a violation advises rather than\ninvalidates — so the rule carries no severity of its own.","text":"","module":"model","href":"module/model.html#model-ArchRule"},{"fqn":"model::Architecture","name":"Architecture","kind":"component","summary":"LANG.md §9 — architectural-principle lints over the resolved graph. Beyond §8.2\nvisibility (which a `public` component still satisfies), these advise on C4\nstructure: a cross-module call SHOULD reach a container/system's published face,\nnot an internal `component` (Facade/Gateway); the module dependency graph SHOULD\nstay acyclic; a cross-system call SHOULD couple to the other system's boundary,\nnot its containers. Each firing is a `Warning` stamped with the rule's `code`\nand `codeDescription` (its article URL); the model stays valid.","text":"","module":"model","href":"module/model.html#model-Architecture"},{"fqn":"model::Branch","name":"Branch","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Branch"},{"fqn":"model::Builder","name":"Builder","kind":"component","summary":"Projects the parsed, resolved workspace into the architecture graph: structural\nnodes plus edges from `for` parents, body calls, triggers, and `from`\nprovenance, with a sequence trace recorded per disclosed body. A pure\nprojection — no I/O — so the emit crate and a future salsa/LSP layer can both\nadopt it.","text":"","module":"model","href":"module/model.html#model-Builder"},{"fqn":"model::Call","name":"Call","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Call"},{"fqn":"model::CaretResolve","name":"CaretResolve","kind":"component","summary":"Caret resolution — the engine every cursor feature shares (`lsp_core` hover,\ndefinition, references, rename; the IDE's symbol actions). Tokenizes the\nactive source, finds the identifier under the offset, and resolves it across\nthe workspace and its externals: a node or data FQN, a `self.`/member access,\nor a local reference. Token-level, so it survives a partial parse.","text":"","module":"model","href":"module/model.html#model-CaretResolve"},{"fqn":"model::Checks","name":"Checks","kind":"component","summary":"Static analysis (LANG.md §2.2, §2.3, §2.4, §3.3, §3.4, §3.5, §4, §5.1, §5.2, §6,\n§7, §8): builds the symbol tables, then runs the well-formedness checks. Every\nentry returns a `syntax::Diagnostic[]`; well-formed input yields an empty one.\nChecks are conservative — each rule fires only when the violation is certain.","text":"","module":"model","href":"module/model.html#model-Checks"},{"fqn":"model::Commit","name":"Commit","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Commit"},{"fqn":"model::Completion","name":"Completion","kind":"component","summary":"Context-aware completion (the model-native producer `lsp_core` maps to LSP\nitems): candidates at a caret offset resolved across the workspace and its\nexternals — members after `.`, a module's public symbols after `::`, the\nbuilt-in macros after `#[`, types in type position, else keywords and symbols.","text":"","module":"model","href":"module/model.html#model-Completion"},{"fqn":"model::ConstantType","name":"ConstantType","kind":"data","summary":"One `constant`'s FQN paired with its declared primitive type name (§3.6,\nADR-039), so inference resolves a `module::NAME` reference to its type.","text":"","module":"model","href":"module/model.html#model-ConstantType"},{"fqn":"model::CrossModule","name":"CrossModule","kind":"component","summary":"§8.2 — cross-module visibility resolution. Walks each module's qualified\nreferences — `for` parents, type annotations (field, parameter, return, and\ngeneric-argument types), feature targets, body call targets, value-position\nunion-variant references, and the member named after a call target's FQN — and\nagainst the global FQN index enforces: a reference from module A to a node in\nmodule B resolves only if that node is `public`. A private target or a dangling\ncross-module FQN is a diagnostic. Same-module references are the single-module\nchecks' business and are skipped here.","text":"","module":"model","href":"module/model.html#model-CrossModule"},{"fqn":"model::DataField","name":"DataField","kind":"data","summary":"One field of a `data` record or union-variant record: its name and rendered\ntype.","text":"","module":"model","href":"module/model.html#model-DataField"},{"fqn":"model::DataShape","name":"DataShape","kind":"data","summary":"The disclosed shape of a `data` node (§3.4, §3.5): a record of fields, a\ndiscriminated union of variants, or an undisclosed black box. Carried on a\n`data` `GraphNode` so the entity (ER) view draws the rows without re-reading\nsource.","text":"","module":"model","href":"module/model.html#model-DataShape"},{"fqn":"model::DataVariant","name":"DataVariant","kind":"data","summary":"One variant of a `data` union (§3.5): its name and, for a record variant, its\nfields.","text":"","module":"model","href":"module/model.html#model-DataVariant"},{"fqn":"model::DepError","name":"DepError","kind":"data","summary":"Why parsing a manifest's `[dependencies]` table or a `pds.lock` failed: the\noffending entry and a human-readable message. Malformed TOML, an unknown key,\nor more than one of tag/rev/branch on one dependency (§8.3).","text":"","module":"model","href":"module/model.html#model-DepError"},{"fqn":"model::DepFile","name":"DepFile","kind":"data","summary":"One dependency module's in-memory form: its FQN *within* the dependency\nworkspace (path relative to that workspace root) and its source. The resolver\nprefixes it with the dependency name.","text":"","module":"model","href":"module/model.html#model-DepFile"},{"fqn":"model::DepSpec","name":"DepSpec","kind":"data","summary":"A `[dependencies]` entry as written in `pds.toml` (§8.3): a git URL with at\nmost one of tag/rev/branch and an in-repo sub-path, or a local sibling path.\nEmpty strings stand for the absent options.","text":"","module":"model","href":"module/model.html#model-DepSpec"},{"fqn":"model::Deps","name":"Deps","kind":"component","summary":"§8.3 — the dependency resolver. Parses a manifest's `[dependencies]` and a\n`pds.lock`, then maps each direct dependency to its dependency-name-prefixed\nmodules — the externals the checker binds for cross-workspace resolution. The\nsingle source of the slug→name→`dep::module` mapping, shared by the native\nloader (`project`) and the WASM bridge (`ide`). Pure: fetching and disk are\nthe caller's job.","text":"","module":"model","href":"module/model.html#model-Deps"},{"fqn":"model::Edge","name":"Edge","kind":"data","summary":"A typed, directed relationship between two graph node FQNs. `source` is the\nwire field `from` — `from` is a PseudoScript keyword, so the model renames\nit. Either endpoint may name a node not present as a `GraphNode` — a\nsynthesised trigger initiator, or a target that does not resolve. `label` is\nthe method name for `Call`, otherwise empty. `span` is the source range that\nproduced the edge — the call site for a `Call` — so an architectural lint can\npoint its diagnostic at the offending call; the empty span when the edge is\nsynthesised.","text":"","module":"model","href":"module/model.html#model-Edge"},{"fqn":"model::EdgeKind","name":"EdgeKind","kind":"data","summary":"What relationship a graph edge expresses (LANG.md §9.1): a `for` parent link,\na body call (label = method), a synthesised trigger initiator edge, or a\n`from` provenance link.","text":"","module":"model","href":"module/model.html#model-EdgeKind"},{"fqn":"model::FeatureCheck","name":"FeatureCheck","kind":"component","summary":"§5.2 / §8.1 — feature checks. A feature's `for` target MUST resolve to a node,\nnot a type or module; a target this single-module model cannot see is left to\ncross-module resolution. Feature names occupy their own module namespace, so a\nrepeated name is a collision (checked once per module).","text":"","module":"model","href":"module/model.html#model-FeatureCheck"},{"fqn":"model::FieldlessVariants","name":"FieldlessVariants","kind":"data","summary":"A union's fieldless variant names, keyed by the union's bare name (§3.5).\nFieldless variants do not hoist a symbol — they are addressed\n`module::Union::Variant` (ADR-032) — so they index under their union.","text":"","module":"model","href":"module/model.html#model-FieldlessVariants"},{"fqn":"model::Folding","name":"Folding","kind":"component","summary":"The foldable regions of a module: every multi-line disclosed declaration and\nstatement block, and doc-comment runs.","text":"","module":"model","href":"module/model.html#model-Folding"},{"fqn":"model::Git","name":"Git","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Git"},{"fqn":"model::Graph","name":"Graph","kind":"data","summary":"The resolved architecture graph: every node and typed edge across all C4\nlevels, plus feature scenarios. The single source every view projects from —\nenough to extract any view without re-reading source. Per-callable sequence\ntraces (`Step[]`) are held alongside, keyed by the owning callable's FQN.","text":"","module":"model","href":"module/model.html#model-Graph"},{"fqn":"model::GraphNode","name":"GraphNode","kind":"data","summary":"One node in the resolved graph — a structural declaration, a `data` type, or a\ncallable. Carries its FQN, simple name, kind, enclosing-node FQN (`for` parent,\nowning node, or empty at top level), declaring module, visibility, name span,\nany trigger macros (callables only), the call signature (callables only), the\ndisclosed shape (`data` only), and lifted `///` documentation.","text":"","module":"model","href":"module/model.html#model-GraphNode"},{"fqn":"model::Inference","name":"Inference","kind":"component","summary":"Local-binding type inference, for inlay hints and hover. Assignments are\nuntyped (`x = expr`); this infers each binding's type from its right-hand side\n— a call's return type, a field access, a `from` expression, or a literal.\nBest-effort: an un-typeable expression yields nothing rather than a guess.","text":"","module":"model","href":"module/model.html#model-Inference"},{"fqn":"model::Local","name":"Local","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Local"},{"fqn":"model::Lock","name":"Lock","kind":"data","summary":"A parsed `pds.lock` (§8.4): the pinned dependency graph. A black box here — its\nshape is the lockfile's concern, not the model's.","text":"","module":"model","href":"module/model.html#model-Lock"},{"fqn":"model::Loop","name":"Loop","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Loop"},{"fqn":"model::MacroTargeting","name":"MacroTargeting","kind":"component","summary":"§2.4 / ADR-015 — macro targeting. Every built-in macro (`onevent`, `schedule`,\n`http`, `manual`) targets callables; on any structural declaration it is a\nwrong-target error, and an unrecognised macro is unknown. An `#[onevent(E)]`\nhandler MUST have exactly one parameter whose type matches the event type.","text":"","module":"model","href":"module/model.html#model-MacroTargeting"},{"fqn":"model::Member","name":"Member","kind":"data","summary":"A member reachable through `.` from a node or `data` owner: its name, kind,\ndefinition span, one-line signature detail (`run(name: string): uuid`), value\ntype (a field's type or a callable's return type), the ordered parameter\ntypes (empty for a field — their count is the call arity, ADR-022), the\n`///` summary (absent for a field — the grammar has no field doc), and\nwhether it is `public` (callables only; gates cross-module member completion,\n§8.2). Powers member completion, arity checks, and go-to-definition.","text":"","module":"model","href":"module/model.html#model-Member"},{"fqn":"model::MemberKind","name":"MemberKind","kind":"data","summary":"What kind of member is reachable through `.` from an owner: a node's callable\n(§5.1) or a `data` record field (§3.4).","text":"","module":"model","href":"module/model.html#model-MemberKind"},{"fqn":"model::Model","name":"Model","kind":"container","summary":"`crates/pseudoscript-model`. AST to one resolved graph; static checks\n(resolution, visibility, Result flow, return coverage); the §8.3 dependency\nresolver; and the LSP analysis primitives (completion, folding, semantic\ntokens, inference). WASM-safe.","text":"","module":"model","href":"module/model.html#model-Model"},{"fqn":"model::ModuleDiagnostics","name":"ModuleDiagnostics","kind":"data","summary":"One workspace module's diagnostics, attributed to its FQN. Every span lies in\nthat module's own source, so a tool with the FQN-to-file mapping publishes\neach list against the right file.","text":"","module":"model","href":"module/model.html#model-ModuleDiagnostics"},{"fqn":"model::ModuleEntry","name":"ModuleEntry","kind":"data","summary":"One module's parsed AST, resolved symbol table, and FQN, as held by a\n`Workspace`.","text":"","module":"model","href":"module/model.html#model-ModuleEntry"},{"fqn":"model::NodeDoc","name":"NodeDoc","kind":"data","summary":"A node's documentation, lifted from its `///` block (LANG.md §2.1): the\nsummary (compact-diagram text), the extended description (tooltip text), and\nits tags (`\n`, including the `#`), in source order.","text":"","module":"model","href":"module/model.html#model-NodeDoc"},{"fqn":"model::NodeKind","name":"NodeKind","kind":"data","summary":"What kind of model element a `GraphNode` is. Adds `Callable` to the structural\nkinds: structural declarations, `data` types (including hoisted union\nvariants), and callables all become nodes.","text":"","module":"model","href":"module/model.html#model-NodeKind"},{"fqn":"model::OnEvent","name":"OnEvent","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-OnEvent"},{"fqn":"model::PackageId","name":"PackageId","kind":"data","summary":"A content-addressable package identity: its source, resolved revision, and\nin-repo path. Drives the `pds_modules/` slug a git package materialises into.","text":"","module":"model","href":"module/model.html#model-PackageId"},{"fqn":"model::ParentKind","name":"ParentKind","kind":"component","summary":"§4 / ADR-010 — C4 parent-kind well-formedness. A `for` parent links a node to\nits enclosing level, and the levels MUST nest: a `container` parents a\n`system`, a `component` parents a `container`. A parent of the wrong kind is a\ndiagnostic (`container `X` parent `Y` is not a system`). A bare parent that\nnames no same-module node is unresolved (§8.1); a qualified parent this\nsingle-module model cannot see is left to cross-module resolution.","text":"","module":"model","href":"module/model.html#model-ParentKind"},{"fqn":"model::Private","name":"Private","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Private"},{"fqn":"model::Public","name":"Public","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Public"},{"fqn":"model::Record","name":"Record","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Record"},{"fqn":"model::ReservedNames","name":"ReservedNames","kind":"component","summary":"§2.3 / ADR-012 — reserved-word identifiers. A declared identifier MUST NOT be a\nreserved word: a keyword, a primitive type name, or `Result`/`Option`. Covers\nevery name a module declares — `data`, node, callable, field, parameter,\nvariant, and feature names — so `data string` or a parameter named `Result` is\na diagnostic.","text":"","module":"model","href":"module/model.html#model-ReservedNames"},{"fqn":"model::Resolution","name":"Resolution","kind":"data","summary":"The outcome of resolving a cross-module reference (LANG.md §8.2): the FQN\nnames a public symbol, a private one reached from another module, or nothing.","text":"","module":"model","href":"module/model.html#model-Resolution"},{"fqn":"model::ResolveHit","name":"ResolveHit","kind":"data","summary":"One resolved caret hit: the clicked identifier's span, the defining module\nand the definition's span there, the resolved graph FQN (a member resolves to\n`owner::member` — the key `emit`'s symbol projection uses), a Markdown title\nline, and the doc summary or signature detail shown under it.","text":"","module":"model","href":"module/model.html#model-ResolveHit"},{"fqn":"model::Resolver","name":"Resolver","kind":"component","summary":"Resolves a module's declarations into a symbol table (LANG.md §8). FQNs are\nfile-derived: a single `.pds` file is one module, named by its `//!` path.\nHolds two namespaces — type names and node names share the symbol table, while\nfeature names live in a separate namespace the `Checks` enforce — plus the\nmember, fieldless-variant, and constant-type indexes. The LSP reuses the\ntable for hover and go-to-definition.","text":"","module":"model","href":"module/model.html#model-Resolver"},{"fqn":"model::ResultFlow","name":"ResultFlow","kind":"component","summary":"§6 — flow-sensitive `Result` / `Option` typestate. Each binding carries a\nstate (`Unknown`, `Ok`/`Err` for a `Result`, `Some`/`None` for an `Option`),\nnarrowed on entering the branches of `if (r.isErr)` / `if (r.isOk)` /\n`if (o.isNone)` / `if (o.isSome)`. Reading `.value` while known-`Err` or\nknown-`None`, or `.error` while known-`Ok`, is a model error. The env is\ncloned per branch so narrowing does not leak; an `if` whose guarded arm\ndiverges narrows the fall-through to the inverse state (`Ok`<->`Err`,\n`Some`<->`None`).","text":"","module":"model","href":"module/model.html#model-ResultFlow"},{"fqn":"model::Return","name":"Return","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Return"},{"fqn":"model::ReturnCoverage","name":"ReturnCoverage","kind":"component","summary":"ADR-016 — return coverage. A disclosed non-void callable MUST return on every\npath. A `return` makes a block diverge; an `if`/`else` diverges only when both\narms do; `for`/`while` bodies and bare expressions never guarantee a return.\nAn explicit `void` and a parse-recovered missing return type (ADR-040) both\nmean void.","text":"","module":"model","href":"module/model.html#model-ReturnCoverage"},{"fqn":"model::Rev","name":"Rev","kind":"data","summary":"A resolved revision selector — at most one of these (§8.3); `Default` is the\nremote's default-branch HEAD.","text":"","module":"model","href":"module/model.html#model-Rev"},{"fqn":"model::Scenario","name":"Scenario","kind":"data","summary":"A `feature` BDD scenario attached to its target node (LANG.md §5.2, §9.3):\nits name, the canonicalised target FQN (as written if it does not resolve),\nthe declaring module's FQN, the span of the feature name (a host's\ngo-to-definition target), lifted `///` documentation, and the ordered\ngiven/when/then steps. Steps are prose, not resolved, so a scenario adds no\nedges; it renders as a card on its target node's doc page.","text":"","module":"model","href":"module/model.html#model-Scenario"},{"fqn":"model::ScenarioStep","name":"ScenarioStep","kind":"data","summary":"One step of a `Scenario` (LANG.md §5.2): a keyword (`given`/`when`/`then`/\n`and`/`but`) and its prose, with the string literal's quotes stripped.","text":"","module":"model","href":"module/model.html#model-ScenarioStep"},{"fqn":"model::SelfCall","name":"SelfCall","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-SelfCall"},{"fqn":"model::Semantic","name":"Semantic","kind":"component","summary":"AST-aware semantic colouring: each identifier classified by its declared role\n(node→namespace, `data`→class, callable→method, parameter, call segment), the\nspans `lsp_core` delta-encodes for the editor.","text":"","module":"model","href":"module/model.html#model-Semantic"},{"fqn":"model::SigParam","name":"SigParam","kind":"data","summary":"One parameter of a callable signature: its name and rendered type.","text":"","module":"model","href":"module/model.html#model-SigParam"},{"fqn":"model::Signature","name":"Signature","kind":"data","summary":"A callable node's signature (§5.1): its ordered parameters and rendered return\ntype. Carried on a callable `GraphNode` so the sequence and symbol views can\nshow the call shape without re-reading source.","text":"","module":"model","href":"module/model.html#model-Signature"},{"fqn":"model::Source","name":"Source","kind":"data","summary":"The resolved source of a dependency (ADR-026): a fetched git repository at a\nrevision and in-repo sub-path, or a local sibling workspace read from disk.","text":"","module":"model","href":"module/model.html#model-Source"},{"fqn":"model::Step","name":"Step","kind":"data","summary":"One step in a callable's ordered sequence trace (LANG.md §7, §9.2). Calls in a\nchained expression are emitted left-to-right; control flow becomes `Alt`/\n`Loop` frames whose bodies are nested step lists, in §7 evaluation order.","text":"","module":"model","href":"module/model.html#model-Step"},{"fqn":"model::Symbol","name":"Symbol","kind":"data","summary":"One declared, addressable node in a module namespace: the bare name, its FQN\n(`module::Ledger`), its declaration kind, whether it is `public` (§8.2), and\nthe span of its name.","text":"","module":"model","href":"module/model.html#model-Symbol"},{"fqn":"model::SymbolKind","name":"SymbolKind","kind":"data","summary":"The lowercase declaration keyword a `Symbol` names. Drives the parent-kind and\nmacro-target diagnostics, which embed it as prose. `Constant` is a top-level\nprimitive value (§3.6, ADR-039).","text":"","module":"model","href":"module/model.html#model-SymbolKind"},{"fqn":"model::SymbolTable","name":"SymbolTable","kind":"data","summary":"One module's resolved symbol table (LANG.md §8): its FQN plus the declared\nnodes, the members reachable through `.`, the fieldless-variant index\n(ADR-032), and the constant-type index (ADR-039). The `Resolver` produces it;\nthe `Workspace` indexes and the `Checks` consume it.","text":"","module":"model","href":"module/model.html#model-SymbolTable"},{"fqn":"model::Tag","name":"Tag","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Tag"},{"fqn":"model::Trigger","name":"Trigger","kind":"data","summary":"A trigger macro on a callable (LANG.md §2.4): the macro that makes the\ncallable a diagram entry point and synthesises an inbound initiator edge. Each\nvariant maps to a synthesised initiator node (`event:`, `scheduler`,\n`client`, `caller`).","text":"","module":"model","href":"module/model.html#model-Trigger"},{"fqn":"model::TypeRefs","name":"TypeRefs","kind":"component","summary":"§3.3 / §8.1 — type-reference resolution. Every named type in a declaration — a\nfield, parameter, or return type, and each generic argument — MUST resolve. A\nbare name that resolves to nothing is reported as unresolved; a name that\nresolves to a declared type or node MUST be written by its full FQN\n(`module::Name`), so an unqualified declared-type annotation is rejected.\nBuilt-in types (the primitives, `Result`, `Option`) stay bare.","text":"","module":"model","href":"module/model.html#model-TypeRefs"},{"fqn":"model::Union","name":"Union","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Union"},{"fqn":"model::VariantCollision","name":"VariantCollision","kind":"component","summary":"§3.5 / ADR-006 — union variant collision. A top-level `data Name` followed by\nan inline variant `| Name { ... }` hoisting the same name is a collision. Bare\nvariants reference existing data and never collide.","text":"","module":"model","href":"module/model.html#model-VariantCollision"},{"fqn":"model::Visibility","name":"Visibility","kind":"data","summary":"Whether a node is `public` (cross-module addressable) or module-private\n(LANG.md §8.2).","text":"","module":"model","href":"module/model.html#model-Visibility"},{"fqn":"model::Workspace","name":"Workspace","kind":"component","summary":"The resolved set of modules keyed by FQN (LANG.md §8). Built once per\nworkspace from `(fqn, parsed module)` pairs; owns the per-module models and a\nglobal FQN-to-`Symbol` index for cross-module resolution.","text":"","module":"model","href":"module/model.html#model-Workspace"},{"fqn":"model::WorkspaceModule","name":"WorkspaceModule","kind":"data","summary":"One `.pds` module of a workspace: its caller-supplied FQN (the loader derives\nit from the file path relative to `pds.toml`, LANG.md §8.1) and its source\ntext. This crate stays pure over in-memory modules; it never touches the\nfilesystem.","text":"","module":"model","href":"module/model.html#model-WorkspaceModule"},{"fqn":"project","name":"project","kind":"module","summary":"","text":"","module":"project","href":"module/project.html"},{"fqn":"project::Deps","name":"Deps","kind":"component","summary":"Resolves a workspace's declared dependencies into modules the checker binds as\nexternals (§8.3): reads `pds.lock` + `pds_modules/`, materialises each direct\ndependency's `.pds` modules under its dependency-name prefix, and resolves a\nlocal path dependency's directory.","text":"","module":"project","href":"module/project.html#project-Deps"},{"fqn":"project::IoError","name":"IoError","kind":"data","summary":"A filesystem failure surfaced to the caller: the path or manifest that could\nnot be read, with a human-readable message.","text":"","module":"project","href":"module/project.html#project-IoError"},{"fqn":"project::Loader","name":"Loader","kind":"component","summary":"Resolves and reads a workspace off disk: finds the root by walking up to the\nnearest `pds.toml`, then walks the tree for visible `.pds` files and loads each\nas a `(fqn, source)` module — mapping its path to a flat module FQN (§8.1) and\nskipping `target/` and `pds_modules/`.","text":"","module":"project","href":"module/project.html#project-Loader"},{"fqn":"project::Project","name":"Project","kind":"container","summary":"`crates/pseudoscript-project`. The disk-facing loader: resolves the workspace\nroot, reads its `.pds` modules, and materialises dependency modules from the\nvendor directory. The only place the native toolchain touches the filesystem\nfor project loading.","text":"","module":"project","href":"module/project.html#project-Project"},{"fqn":"syntax","name":"syntax","kind":"module","summary":"","text":"","module":"syntax","href":"module/syntax.html"},{"fqn":"syntax::Assign","name":"Assign","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Assign"},{"fqn":"syntax::BinOp","name":"BinOp","kind":"data","summary":"A binary operator (§7.5): arithmetic, comparison, equality, and boolean.","text":"","module":"syntax","href":"module/syntax.html#syntax-BinOp"},{"fqn":"syntax::Binary","name":"Binary","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Binary"},{"fqn":"syntax::BlankLines","name":"BlankLines","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-BlankLines"},{"fqn":"syntax::Block","name":"Block","kind":"data","summary":"A `{ ... }` statement block (§7): statements in order, plus the brace span.","text":"","module":"syntax","href":"module/syntax.html#syntax-Block"},{"fqn":"syntax::BlockComment","name":"BlockComment","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-BlockComment"},{"fqn":"syntax::BodyMember","name":"BodyMember","kind":"data","summary":"A member of a disclosed node body: a callable, or — under error recovery —\na nested structural declaration the parser flags as illegal (ADR-011).","text":"","module":"syntax","href":"module/syntax.html#syntax-BodyMember"},{"fqn":"syntax::BoolLit","name":"BoolLit","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-BoolLit"},{"fqn":"syntax::CallableDecl","name":"CallableDecl","kind":"data","summary":"A callable (implicit operation) declared inside a disclosed node (§5.1).\n`returnTy` is required (ADR-040) — a missing return type is a syntax error,\nrecovered as `void`; `body` absent means a black box (`;`).","text":"","module":"syntax","href":"module/syntax.html#syntax-CallableDecl"},{"fqn":"syntax::CallableMember","name":"CallableMember","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-CallableMember"},{"fqn":"syntax::Compose","name":"Compose","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Compose"},{"fqn":"syntax::Constant","name":"Constant","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Constant"},{"fqn":"syntax::ConstantDecl","name":"ConstantDecl","kind":"data","summary":"A `constant NAME = Literal` declaration (§3.6, ADR-039): a top-level primitive\nvalue. `public` lives on the enclosing `Declaration`, mirroring `data`.","text":"","module":"syntax","href":"module/syntax.html#syntax-ConstantDecl"},{"fqn":"syntax::Convert","name":"Convert","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Convert"},{"fqn":"syntax::Data","name":"Data","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Data"},{"fqn":"syntax::DataBody","name":"DataBody","kind":"data","summary":"The three `data` forms. A bare `| Name` variant is an enum case; a\n`| Name { .. }` variant carries an inline record (§3.5, ADR-006).","text":"","module":"syntax","href":"module/syntax.html#syntax-DataBody"},{"fqn":"syntax::DataDecl","name":"DataDecl","kind":"data","summary":"A `data` declaration: a record, a union, or a black box (§3.4, §3.5).","text":"","module":"syntax","href":"module/syntax.html#syntax-DataDecl"},{"fqn":"syntax::DeclItem","name":"DeclItem","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-DeclItem"},{"fqn":"syntax::DeclKind","name":"DeclKind","kind":"data","summary":"The structural payload of a declaration: a node (person/system/container/\ncomponent), a `data` type, or a top-level `constant` (§3.6, ADR-039).","text":"","module":"syntax","href":"module/syntax.html#syntax-DeclKind"},{"fqn":"syntax::DeclMember","name":"DeclMember","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-DeclMember"},{"fqn":"syntax::Declaration","name":"Declaration","kind":"data","summary":"A documented, annotated structural declaration (§4): the shared\n`doc → macros → public` prefix plus the structural payload.","text":"","module":"syntax","href":"module/syntax.html#syntax-Declaration"},{"fqn":"syntax::Diagnostic","name":"Diagnostic","kind":"data","summary":"One message about a span of source. The single diagnostic type every crate\nemits, so a driver collects lexer, parser, and checker output into one\nordered list. `code` is an optional stable identifier (e.g. `E0001`) for\ntooling; the human-facing text is `message`. `codeDescription` is an optional\nURL the code resolves to — an article explaining the rule — which the LSP\nsurfaces as the diagnostic's clickable link. Empty when the code carries no\narticle.","text":"","module":"syntax","href":"module/syntax.html#syntax-Diagnostic"},{"fqn":"syntax::DocBlock","name":"DocBlock","kind":"data","summary":"A `///` doc block split into summary and extended on the first blank `///`\nline (ADR-009): summary feeds compact diagrams, extended feeds tooltips,\ntags carry the `\n` markers.","text":"","module":"syntax","href":"module/syntax.html#syntax-DocBlock"},{"fqn":"syntax::Expr","name":"Expr","kind":"data","summary":"An expression (§7, §10): its form and source span.","text":"","module":"syntax","href":"module/syntax.html#syntax-Expr"},{"fqn":"syntax::ExprKind","name":"ExprKind","kind":"data","summary":"The expression forms (§10). `Marker` is a built-in generic constructor —\n`Ok`/`Err` (`Result`) or `Some`/`None` (`Option`); `From` names a target\ntype and its `source`, whose `FromSource` form is the §7.2 vs §7.4 split;\n`Postfix` is a `.name`/`.name(..)` access chain (ADR-007); `Unary`/`Binary`\nare the §7.5 operator forms. A `Marker` or `From` head is never a binary\noperand (§7.5).","text":"","module":"syntax","href":"module/syntax.html#syntax-ExprKind"},{"fqn":"syntax::ExprStmt","name":"ExprStmt","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-ExprStmt"},{"fqn":"syntax::FeatureDecl","name":"FeatureDecl","kind":"data","summary":"A `feature Name for Path { given* when+ then+ }` BDD scenario (§5.2). Steps\nare prose, not resolved against the model; the strict given→when→then order\nis enforced by the parser. A feature takes no macros and no `public`.","text":"","module":"syntax","href":"module/syntax.html#syntax-FeatureDecl"},{"fqn":"syntax::FeatureItem","name":"FeatureItem","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-FeatureItem"},{"fqn":"syntax::FeatureStep","name":"FeatureStep","kind":"data","summary":"One step line in a feature flow: a step keyword and its prose string (§5.2).","text":"","module":"syntax","href":"module/syntax.html#syntax-FeatureStep"},{"fqn":"syntax::Field","name":"Field","kind":"data","summary":"A record field `name: Type`.","text":"","module":"syntax","href":"module/syntax.html#syntax-Field"},{"fqn":"syntax::For","name":"For","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-For"},{"fqn":"syntax::From","name":"From","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-From"},{"fqn":"syntax::FromSource","name":"FromSource","kind":"data","summary":"The source of a `from` expression (§7.2, §7.4, ADR-035) — the parser\nbranches on the leading token, so the two forms are distinct: a `{`\nopens `Compose`, a set the target `data` record/variant is built from;\nanything else is `Convert`, carrying the target type onto one value.","text":"","module":"syntax","href":"module/syntax.html#syntax-FromSource"},{"fqn":"syntax::If","name":"If","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-If"},{"fqn":"syntax::InnerDoc","name":"InnerDoc","kind":"data","summary":"One `//!` inner-doc line documenting the module (§2.1).","text":"","module":"syntax","href":"module/syntax.html#syntax-InnerDoc"},{"fqn":"syntax::Item","name":"Item","kind":"data","summary":"A top-level item: a documented structural declaration, or a `feature` BDD\nscenario.","text":"","module":"syntax","href":"module/syntax.html#syntax-Item"},{"fqn":"syntax::Lexed","name":"Lexed","kind":"data","summary":"The full result of lexing: the conformance token stream plus interleaved\ntrivia for the formatter.","text":"","module":"syntax","href":"module/syntax.html#syntax-Lexed"},{"fqn":"syntax::Lexer","name":"Lexer","kind":"component","summary":"Hand-written lexer for LANG.md §2. One pass yields the conformance token\nstream (comments discarded, `///`→`Doc`+`Tag`, `//!`→`InnerDoc`) and\nfull-fidelity trivia for the formatter. Infallible: lexical anomalies surface\nas parser diagnostics, never a failed lex.","text":"","module":"syntax","href":"module/syntax.html#syntax-Lexer"},{"fqn":"syntax::LineComment","name":"LineComment","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-LineComment"},{"fqn":"syntax::LineIndex","name":"LineIndex","kind":"component","summary":"Precomputed newline offsets turning a byte offset into a 1-based (line, col)\npair in O(log n). Built once per source and reused for every lookup; column\ncounts bytes from the line start.","text":"","module":"syntax","href":"module/syntax.html#syntax-LineIndex"},{"fqn":"syntax::List","name":"List","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-List"},{"fqn":"syntax::Lit","name":"Lit","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Lit"},{"fqn":"syntax::Literal","name":"Literal","kind":"data","summary":"A literal value (ADR-013). `raw` carries the source text (string literals\nkeep their quotes).","text":"","module":"syntax","href":"module/syntax.html#syntax-Literal"},{"fqn":"syntax::LiteralArg","name":"LiteralArg","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-LiteralArg"},{"fqn":"syntax::Macro","name":"Macro","kind":"data","summary":"A `#[..]` macro (outer attribute) on a callable (§2.4): its name path and\nargument form.","text":"","module":"syntax","href":"module/syntax.html#syntax-Macro"},{"fqn":"syntax::MacroArg","name":"MacroArg","kind":"data","summary":"One argument inside a macro's `( .. )` list (§10 `MetaArg`).","text":"","module":"syntax","href":"module/syntax.html#syntax-MacroArg"},{"fqn":"syntax::MacroArgs","name":"MacroArgs","kind":"data","summary":"The three macro argument forms (§2.4): `#[manual]` word, `#[onevent(Path)]`/\n`#[http(\"..\")]` list, `#[schedule = \"..]` name=value.","text":"","module":"syntax","href":"module/syntax.html#syntax-MacroArgs"},{"fqn":"syntax::Marker","name":"Marker","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Marker"},{"fqn":"syntax::MarkerKind","name":"MarkerKind","kind":"data","summary":"The built-in generic constructor a `Marker` names (§6, ADR-019). The\nvariants mirror the four marker keywords (their names carry the `Kw` prefix\nbecause `Ok`/`Err`/`Some`/`None` are reserved words): `KwOk`/`KwErr` build a\n`Result`, `KwSome`/`KwNone` build an `Option`.","text":"","module":"syntax","href":"module/syntax.html#syntax-MarkerKind"},{"fqn":"syntax::Module","name":"Module","kind":"data","summary":"The typed syntax tree of one module (LANG.md §10): module-level inner docs,\nthen declarations and features in source order. Every node carries a `Span`;\ndeclarations and statements also carry leading trivia so the formatter can\nreproduce layout.","text":"","module":"syntax","href":"module/syntax.html#syntax-Module"},{"fqn":"syntax::NameValue","name":"NameValue","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-NameValue"},{"fqn":"syntax::NestedArg","name":"NestedArg","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-NestedArg"},{"fqn":"syntax::Node","name":"Node","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Node"},{"fqn":"syntax::NodeDecl","name":"NodeDecl","kind":"data","summary":"A `person` / `system` / `container` / `component` declaration. `parent` is\nthe `for ` path, present for container/component. `body` is the\ndisclosed member list, or absent for a black box (`;`).","text":"","module":"syntax","href":"module/syntax.html#syntax-NodeDecl"},{"fqn":"syntax::NodeKind","name":"NodeKind","kind":"data","summary":"The structural keyword class of a node.","text":"","module":"syntax","href":"module/syntax.html#syntax-NodeKind"},{"fqn":"syntax::NumberLit","name":"NumberLit","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-NumberLit"},{"fqn":"syntax::Param","name":"Param","kind":"data","summary":"A callable parameter `name: Type`.","text":"","module":"syntax","href":"module/syntax.html#syntax-Param"},{"fqn":"syntax::Paren","name":"Paren","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Paren"},{"fqn":"syntax::Parsed","name":"Parsed","kind":"data","summary":"Parser output: the tree (always present, recovered on error) plus the\ndiagnostics collected while parsing. Parsing never fails outright.","text":"","module":"syntax","href":"module/syntax.html#syntax-Parsed"},{"fqn":"syntax::Parser","name":"Parser","kind":"component","summary":"Recursive-descent parser for the §10 grammar with error recovery: on\nunexpected input it records a `Diagnostic`, resynchronises to the next\nstatement/declaration boundary, and continues. Always yields a (possibly\npartial) `Module`; never panics.","text":"","module":"syntax","href":"module/syntax.html#syntax-Parser"},{"fqn":"syntax::Path","name":"Path","kind":"data","summary":"A `::`-separated path of identifiers (§2.2, §10 `Path`); never empty.","text":"","module":"syntax","href":"module/syntax.html#syntax-Path"},{"fqn":"syntax::PathArg","name":"PathArg","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-PathArg"},{"fqn":"syntax::PathRef","name":"PathRef","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-PathRef"},{"fqn":"syntax::Postfix","name":"Postfix","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Postfix"},{"fqn":"syntax::PostfixSeg","name":"PostfixSeg","kind":"data","summary":"One `.name` or `.name(args)` step in a postfix chain (ADR-007). `callArgs`\npresent marks a call; absent marks field access.","text":"","module":"syntax","href":"module/syntax.html#syntax-PostfixSeg"},{"fqn":"syntax::Record","name":"Record","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Record"},{"fqn":"syntax::Ref","name":"Ref","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Ref"},{"fqn":"syntax::Reference","name":"Reference","kind":"data","summary":"A reference primary (§10 `Ref`): `self` or an FQN.","text":"","module":"syntax","href":"module/syntax.html#syntax-Reference"},{"fqn":"syntax::Return","name":"Return","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Return"},{"fqn":"syntax::SelfNode","name":"SelfNode","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-SelfNode"},{"fqn":"syntax::Severity","name":"Severity","kind":"data","summary":"Diagnostic severity. `Error` invalidates the input; `Warning`/`Info` advise\nand never block compilation.","text":"","module":"syntax","href":"module/syntax.html#syntax-Severity"},{"fqn":"syntax::Span","name":"Span","kind":"data","summary":"A half-open byte range `[start, end)` into one module's source. Offsets are\nbytes, not characters; columns derived via `LineIndex` count bytes too,\nmatching the conformance token goldens.","text":"","module":"syntax","href":"module/syntax.html#syntax-Span"},{"fqn":"syntax::SpannedTrivia","name":"SpannedTrivia","kind":"data","summary":"A trivia element paired with the source span it occupies.","text":"","module":"syntax","href":"module/syntax.html#syntax-SpannedTrivia"},{"fqn":"syntax::StepKind","name":"StepKind","kind":"data","summary":"The step keyword class of a feature step (§5.2). `And`/`But` continue the\npreceding step's flow phase.","text":"","module":"syntax","href":"module/syntax.html#syntax-StepKind"},{"fqn":"syntax::Stmt","name":"Stmt","kind":"data","summary":"A statement valid inside a callable body (§7), with the leading trivia that\npreceded it.","text":"","module":"syntax","href":"module/syntax.html#syntax-Stmt"},{"fqn":"syntax::StmtKind","name":"StmtKind","kind":"data","summary":"The statement forms (§7).","text":"","module":"syntax","href":"module/syntax.html#syntax-StmtKind"},{"fqn":"syntax::StringLit","name":"StringLit","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-StringLit"},{"fqn":"syntax::Syntax","name":"Syntax","kind":"container","summary":"`crates/pseudoscript-syntax`. The foundation crate: source text to tokens\nand a typed AST, emitting the shared `Diagnostic`. WASM-safe and I/O-free.","text":"","module":"syntax","href":"module/syntax.html#syntax-Syntax"},{"fqn":"syntax::Tag","name":"Tag","kind":"data","summary":"A `\n` tag from a doc block, including the leading `#` (§2.4).","text":"","module":"syntax","href":"module/syntax.html#syntax-Tag"},{"fqn":"syntax::Token","name":"Token","kind":"data","summary":"A lexical token: its kind, source span, and rendered lexeme (LANG.md §2).","text":"For most tokens `text` is the raw source slice. For `Doc`/`InnerDoc` it is\nthe doc text with the marker and surrounding whitespace stripped; for `Tag`\nit includes the leading `#`.","module":"syntax","href":"module/syntax.html#syntax-Token"},{"fqn":"syntax::TokenKind","name":"TokenKind","kind":"data","summary":"Every lexical token class (LANG.md §2). The `name` rendering (e.g.\n`KW_SYSTEM`) is the conformance golden's `KIND`. Primitive type names and\n`Result` are NOT keywords — they lex as `Ident` and are classified in type\nposition by the model crate.","text":"","module":"syntax","href":"module/syntax.html#syntax-TokenKind"},{"fqn":"syntax::Trivia","name":"Trivia","kind":"data","summary":"Non-token source between tokens, preserved for the formatter. Doc comments,\ntags, macros, and modifiers are first-class tokens/AST data, not trivia —\nonly discarded comments and blank-line gaps live here.","text":"","module":"syntax","href":"module/syntax.html#syntax-Trivia"},{"fqn":"syntax::Type","name":"Type","kind":"data","summary":"A type expression: a named base with optional generic arguments and an\noptional trailing `[]` array suffix (§3.3, ADR-008 — no optionality marker).","text":"","module":"syntax","href":"module/syntax.html#syntax-Type"},{"fqn":"syntax::Unary","name":"Unary","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Unary"},{"fqn":"syntax::UnaryOp","name":"UnaryOp","kind":"data","summary":"A unary operator (§7.5): boolean `!` or numeric `-`.","text":"","module":"syntax","href":"module/syntax.html#syntax-UnaryOp"},{"fqn":"syntax::Union","name":"Union","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Union"},{"fqn":"syntax::Variant","name":"Variant","kind":"data","summary":"One union variant: its name and an optional inline record body.","text":"","module":"syntax","href":"module/syntax.html#syntax-Variant"},{"fqn":"syntax::While","name":"While","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-While"},{"fqn":"universe","name":"universe","kind":"module","summary":"","text":"","module":"universe","href":"module/universe.html"},{"fqn":"universe::Adapter","name":"Adapter","kind":"component","summary":"Adapts a resolved model into the software graph: keeps only the structural\nnodes, hooks each to its structural parent for the containment tree, and lifts\nevery call to the structural level — tallying directed traffic per\ncaller→callee pair so the edge carries direction.","text":"","module":"universe","href":"module/universe.html#universe-Adapter"},{"fqn":"universe::C4Level","name":"C4Level","kind":"data","summary":"The C4 abstraction level a node sits at. Only these four enter the graph;\ndata and callables do not — a relationship through them is lifted to the\nnearest enclosing structural node.","text":"","module":"universe","href":"module/universe.html#universe-C4Level"},{"fqn":"universe::EdgeOut","name":"EdgeOut","kind":"data","summary":"One relationship in the flat snapshot, with its traffic (call count).","text":"","module":"universe","href":"module/universe.html#universe-EdgeOut"},{"fqn":"universe::Flatten","name":"Flatten","kind":"component","summary":"Flattens the software graph into the renderer-facing snapshot: each node's id,\nlevel string, and parent id; each relationship's endpoints and traffic. The\nboundary the web IDE reads — petgraph indices never cross it.","text":"","module":"universe","href":"module/universe.html#universe-Flatten"},{"fqn":"universe::FlowDef","name":"FlowDef","kind":"data","summary":"One entry-point flow: the entry callable's FQN and simple name, the flow's\npalette colour, and its ordered legs. The renderer draws one filament chain\nper flow and streams its beads in the flow's colour.","text":"","module":"universe","href":"module/universe.html#universe-FlowDef"},{"fqn":"universe::FlowHop","name":"FlowHop","kind":"data","summary":"One call leg of a flow: the caller and callee as structural node ids (the\nsame ids the snapshot places), plus the message label shown on the leg.","text":"","module":"universe","href":"module/universe.html#universe-FlowHop"},{"fqn":"universe::FlowTracer","name":"FlowTracer","kind":"component","summary":"Traces every entry-point flow: a triggered callable (or a person-owned\naction) starts a flow; its sequence projection is walked call-by-call and\neach leg's endpoints lift to the nearest placed structural node — the same\nlift the Adapter applies to relationships, so flows and edges agree. Colours\nkey from the entry's FQN by a stable hash into a fixed palette — the same\nkeying the web IDE paints with — so a flow keeps its hue everywhere it\nappears and an unrelated model edit never recolours it.","text":"","module":"universe","href":"module/universe.html#universe-FlowTracer"},{"fqn":"universe::GraphNode","name":"GraphNode","kind":"data","summary":"A structural node in the software graph: its stable model FQN, its C4 level,\nand its place in the containment tree (the enclosing node, empty for a\ntop-level system, and the ids it contains). No position — the renderer\nplaces it.","text":"","module":"universe","href":"module/universe.html#universe-GraphNode"},{"fqn":"universe::NodeOut","name":"NodeOut","kind":"data","summary":"One node in the flat snapshot: its id, C4 level as a lowercase string, and the\nid of its containment parent (empty for a top-level system).","text":"","module":"universe","href":"module/universe.html#universe-NodeOut"},{"fqn":"universe::Relationship","name":"Relationship","kind":"data","summary":"A directed relationship between two structural nodes, weighted by `traffic` —\nthe number of underlying calls lifted onto the pair. Self-relationships are\ndropped; the count drives the renderer's flow animation.","text":"","module":"universe","href":"module/universe.html#universe-Relationship"},{"fqn":"universe::Snapshot","name":"Snapshot","kind":"data","summary":"The flat, serialisable contract the web IDE's `ForceGraph` consumes: nodes\nand directed weighted edges, with no engine internals (petgraph indices)\nleaking across the boundary. Positions are not here.","text":"","module":"universe","href":"module/universe.html#universe-Snapshot"},{"fqn":"universe::SoftwareGraph","name":"SoftwareGraph","kind":"data","summary":"The software graph: structural nodes with the containment tree, directed\nrelationship edges weighted by traffic, and the top-level systems (the\ncontainment roots, in model-declaration order). The crate's in-memory form,\nbefore flattening to a renderer-facing `Snapshot`.","text":"","module":"universe","href":"module/universe.html#universe-SoftwareGraph"},{"fqn":"universe::Universe","name":"Universe","kind":"container","summary":"`crates/pseudoscript-universe`. Maps the resolved C4 model into the software\ngraph the 3D view renders, then flattens it to a determinstic snapshot.","text":"","module":"universe","href":"module/universe.html#universe-Universe"},{"fqn":"universe.html","name":"3D Universe","kind":"page","summary":"The model in 3D — structure and flows","text":"","module":"","href":"universe.html"}]; \ No newline at end of file +window.__PDS_SEARCH__=[{"fqn":"health.html","name":"Architecture Health","kind":"page","summary":"Errors, warnings, and principle lints","text":"","module":"","href":"health.html"},{"fqn":"cli","name":"cli","kind":"module","summary":"","text":"","module":"cli","href":"module/cli.html"},{"fqn":"cli::AddOpts","name":"AddOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-AddOpts"},{"fqn":"cli::Args","name":"Args","kind":"component","summary":"Parses argv with clap-derive into the chosen subcommand and its options, then\ndispatches to the matching command handler.","text":"","module":"cli","href":"module/cli.html#cli-Args"},{"fqn":"cli::CheckCmd","name":"CheckCmd","kind":"component","summary":"`pds check` — parse and statically check one file, print each diagnostic as\n`path:line:col: severity: message`, and exit non-zero on any error-severity\ndiagnostic. Emits no diagram.","text":"","module":"cli","href":"module/cli.html#cli-CheckCmd"},{"fqn":"cli::CheckOpts","name":"CheckOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-CheckOpts"},{"fqn":"cli::Cli","name":"Cli","kind":"container","summary":"`crates/pseudoscript` — the binary crate (`pds`). The composition root and\nonly I/O edge; a thin frontend that wires the pure library crates to disk,\nHTTP, and a filesystem watcher.","text":"","module":"cli","href":"module/cli.html#cli-Cli"},{"fqn":"cli::Command","name":"Command","kind":"data","summary":"The subcommand clap parsed from argv, with its resolved options.","text":"","module":"cli","href":"module/cli.html#cli-Command"},{"fqn":"cli::Deps","name":"Deps","kind":"component","summary":"`pds add`/`install`/`update`/`remove`/`list` — git workspace dependency\nmanagement (§8.3). `add` resolves a git URL into `pds.toml` + `pds.lock` and\nvendors it; `install` restores `pds_modules/` from the lock; `update`\nre-pins; `remove` drops one; `list` discovers every workspace under a root.\nResolution delegates to `project`; fetching is the CLI's I/O.","text":"","module":"cli","href":"module/cli.html#cli-Deps"},{"fqn":"cli::DocCmd","name":"DocCmd","kind":"component","summary":"`pds doc` — the headline command (ADR-017). Load the workspace, check it per\nmodule (reported but non-fatal, like `cargo doc` — the same findings feed the\nsite's health page), project the resolved graph, and render a static doc\nsite. The output format is resolved by precedence — an explicit `--format`\nover the manifest `[doc].format`, else HTML — then a fork renders the SSR\nHTML site (every diagram as server SVG, logo copied) or a flat Markdown site\n(SVG assets, no logo). `--serve` hosts the HTML site over HTTP; `--watch`\nadds a filesystem watcher and browser live reload; both are skipped for\nMarkdown output, which only writes.","text":"","module":"cli","href":"module/cli.html#cli-DocCmd"},{"fqn":"cli::DocOpts","name":"DocOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-DocOpts"},{"fqn":"cli::EvalCmd","name":"EvalCmd","kind":"component","summary":"`pds eval` — read a model from stdin and statically check it, printing each\ndiagnostic as `:line:col: severity: message` and exiting non-zero on\nany error. The fileless path: an agent pipes a snippet to check it without\nwriting a file.","text":"","module":"cli","href":"module/cli.html#cli-EvalCmd"},{"fqn":"cli::FmtCmd","name":"FmtCmd","kind":"component","summary":"`pds fmt` — format to canonical PseudoScript; `--write` overwrites the file in\nplace, otherwise prints to stdout. A parse error is reported and the file is\nleft untouched.","text":"","module":"cli","href":"module/cli.html#cli-FmtCmd"},{"fqn":"cli::FmtOpts","name":"FmtOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-FmtOpts"},{"fqn":"cli::HttpServer","name":"HttpServer","kind":"component","summary":"HTTP host for the generated site (`--serve`/`--watch`): `tiny_http` on\n`127.0.0.1:port`, maps `/` to `index.html`, rejects paths escaping the output\ndir, and on watch injects a live-reload poll and answers `/__livereload` with\nthe current version. Blocks until the process is stopped.","text":"","module":"cli","href":"module/cli.html#cli-HttpServer"},{"fqn":"cli::InitCmd","name":"InitCmd","kind":"component","summary":"`pds init` — bootstrap a workspace: write `pds.toml` and a starter `main.pds`.\nRefuses to overwrite an existing manifest; the starter is written only when\nabsent.","text":"","module":"cli","href":"module/cli.html#cli-InitCmd"},{"fqn":"cli::IoError","name":"IoError","kind":"data","summary":"A filesystem, parse-config, or serving failure surfaced to the CLI exit code.","text":"","module":"cli","href":"module/cli.html#cli-IoError"},{"fqn":"cli::ListOpts","name":"ListOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-ListOpts"},{"fqn":"cli::Loader","name":"Loader","kind":"component","summary":"The CLI loader: resolves the project root and reads its modules through the\n`project` crate (the shared filesystem edge), then parses the cli-specific\n`[doc]` table on top. Root-finding and module-walking are `project`'s job;\nthe manifest's doc config is the CLI's.","text":"","module":"cli","href":"module/cli.html#cli-Loader"},{"fqn":"cli::LspHost","name":"LspHost","kind":"component","summary":"`pds lsp` — boot a Tokio runtime and launch the language server over stdio.\nSpawned as a child process by an editor (`context::Editor.openDocument`).","text":"","module":"cli","href":"module/cli.html#cli-LspHost"},{"fqn":"cli::OutlineCmd","name":"OutlineCmd","kind":"component","summary":"`pds outline` — walk the workspace's modules, build its graph, and print the\nsymbol outline as JSON: each node's `fqn`, `name`, `kind`, `parent`, whether it\nis a triggered flow entry, and its declaration site. The structure tree an\neditor draws. Outlining reads the walked modules straight, skipping the manifest\n`[doc]` parse and dependency resolution: the structure tree never depends on\npresentation config or external modules.","text":"","module":"cli","href":"module/cli.html#cli-OutlineCmd"},{"fqn":"cli::OutlineOpts","name":"OutlineOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-OutlineOpts"},{"fqn":"cli::ReferenceCmd","name":"ReferenceCmd","kind":"component","summary":"`pds lang` (alias `spec`) and `pds skill` — print the bundled, version-pinned\nlanguage reference and the authoring skill to stdout. Fed to an LLM to author\n`.pds`; no workspace needed.","text":"","module":"cli","href":"module/cli.html#cli-ReferenceCmd"},{"fqn":"cli::RemoveOpts","name":"RemoveOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-RemoveOpts"},{"fqn":"cli::SvgCmd","name":"SvgCmd","kind":"component","summary":"`pds svg` — render a single diagram to a self-contained SVG on stdout. With\n`--symbol`, draws that symbol's fitting view; otherwise draws `--view` (paired\nwith `--target`) over the whole workspace. The static counterpart of the IDE\ncanvas. Like `pds outline`, it reads the walked modules straight, skipping the\nmanifest `[doc]` parse and dependency resolution: a diagram never depends on\npresentation config or external modules.","text":"","module":"cli","href":"module/cli.html#cli-SvgCmd"},{"fqn":"cli::SvgOpts","name":"SvgOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-SvgOpts"},{"fqn":"cli::TokensCmd","name":"TokensCmd","kind":"component","summary":"`pds tokens` — print the conformance token stream (`KIND@line:col \"lexeme\"`)\nto stdout, for debugging the lexer.","text":"","module":"cli","href":"module/cli.html#cli-TokensCmd"},{"fqn":"cli::TokensOpts","name":"TokensOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-TokensOpts"},{"fqn":"cli::UpgradeCmd","name":"UpgradeCmd","kind":"component","summary":"`pds upgrade` — download a release and install it over the running binary.","text":"","module":"cli","href":"module/cli.html#cli-UpgradeCmd"},{"fqn":"cli::UpgradeOpts","name":"UpgradeOpts","kind":"data","summary":"","text":"","module":"cli","href":"module/cli.html#cli-UpgradeOpts"},{"fqn":"cli::Watcher","name":"Watcher","kind":"component","summary":"Filesystem watcher (`--watch`, via `notify`): watches the project root\nrecursively, debounces each save burst, and on a relevant change rebuilds the\nsite and bumps the version the browser's live-reload poll reads. Events under\nthe output dir are ignored so writing the site never re-triggers a build.","text":"","module":"cli","href":"module/cli.html#cli-Watcher"},{"fqn":"cli::Workspace","name":"Workspace","kind":"data","summary":"The loaded project: the `[doc]` config, the resolved output directory\n(`/`, default `target/doc`), every module sorted by FQN, the\ndependency-name-prefixed dependency modules (§8.3; empty without a `pds.lock`),\nand the preferred doc output format. Produced from the nearest `pds.toml`.","text":"","module":"cli","href":"module/cli.html#cli-Workspace"},{"fqn":"context","name":"context","kind":"module","summary":"","text":"","module":"context","href":"module/context.html"},{"fqn":"context::Developer","name":"Developer","kind":"person","summary":"Author of architecture models: edits `.pds` files in an IDE and runs the CLI.","text":"","module":"context","href":"module/context.html#context-Developer"},{"fqn":"context::Editor","name":"Editor","kind":"system","summary":"IDEs that speak the Language Server Protocol (VS Code, Neovim, …). The editor\nspawns `pds lsp` as a child process and drives it over stdio: it streams\ndocument changes to the server and renders the diagnostics it publishes.","text":"","module":"context","href":"module/context.html#context-Editor"},{"fqn":"context::Pseudoscript","name":"Pseudoscript","kind":"system","summary":"The PseudoScript CLI — loads a `.pds` workspace into one resolved graph and\ndocuments it as a static site, or serves the language to editors. Cross-system\ncallers couple to these published faces; the containers stay behind them.","text":"","module":"context","href":"module/context.html#context-Pseudoscript"},{"fqn":"doc","name":"doc","kind":"module","summary":"","text":"","module":"doc","href":"module/doc.html"},{"fqn":"doc::Assets","name":"Assets","kind":"component","summary":"Ships the prebuilt presentation bundles, authored in the `web/` Svelte package and\nembedded at build time (LANG.md §9.3). `style.css`, `client.js`, and\n`universe.js` ship once at the site root; `ssr.js` is build-time only — the\nengine (`Doc::Ssr`) evaluates it and it is never written to the site. The\nuniverse bundle is the only one that carries WebGL; the SSR bundle never\ndoes.","text":"","module":"doc","href":"module/doc.html#doc-Assets"},{"fqn":"doc::Call","name":"Call","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-Call"},{"fqn":"doc::Codec","name":"Codec","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-Codec"},{"fqn":"doc::Crumb","name":"Crumb","kind":"data","summary":"One breadcrumb hop: its label and href; the trailing crumb (the current\npage) carries no href.","text":"","module":"doc","href":"module/doc.html#doc-Crumb"},{"fqn":"doc::DiagnosticInput","name":"DiagnosticInput","kind":"data","summary":"One host-prepared diagnostic, positioned in its module's source: severity\nword (`error`/`warning`), the stable rule code and its principle-article URL\n(empty for plain diagnostics), the message, and the 1-based line/column with\nthe byte span. The host computes line/column once — this crate never sees\nmodule sources.","text":"","module":"doc","href":"module/doc.html#doc-DiagnosticInput"},{"fqn":"doc::Diagram","name":"Diagram","kind":"data","summary":"A diagram figure. Every view — C4, sequence, data, feature — ships as\ndeterministic server-rendered SVG, identical to `pds svg` (the client adds\npan/zoom only); `kind` is the view word (`c4`/`sequence`/`entity`/`flow`)\nthe figure exposes as its `data-diagram` hook. `Empty` marks a view that\nfailed to project.","text":"","module":"doc","href":"module/doc.html#doc-Diagram"},{"fqn":"doc::Diagrams","name":"Diagrams","kind":"component","summary":"The bridge into `emit`: project a view into a `Scene` and render it to the\nsame deterministic SVG `pds svg` emits, under the adaptive palette so the\nfigure follows the site theme. A view that fails to project degrades to an\n`Empty` placeholder rather than aborting the build — the cargo-doc stance\nthat a partial model still documents. The client adds pan/zoom around the\nfigure; it never re-lays-out.","text":"","module":"doc","href":"module/doc.html#doc-Diagrams"},{"fqn":"doc::Doc","name":"Doc","kind":"container","summary":"`crates/pseudoscript-doc`. Turns a resolved graph into a Svelte-rendered,\ncargo-doc-style site that fully represents the system: each node's `///`\ndocs and relationships on its page, every diagram as deterministic\nserver-rendered SVG, the 3D universe with its flows, the architecture-health\nreport, and a static full-text search index. Pure: returns in-memory files,\nthe CLI writes.","text":"","module":"doc","href":"module/doc.html#doc-Doc"},{"fqn":"doc::DocBody","name":"DocBody","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-DocBody"},{"fqn":"doc::DocConfig","name":"DocConfig","kind":"data","summary":"Site presentation, filled by the CLI from the `[doc]` table of `pds.toml`.\n`[doc]` tunes presentation only, never what is documented.","text":"","module":"doc","href":"module/doc.html#doc-DocConfig"},{"fqn":"doc::DocGroup","name":"DocGroup","kind":"data","summary":"One `[[doc.sidebar]]` group of authored Markdown pages: a heading and its\nordered pages, rendered above the auto-generated module tree.","text":"","module":"doc","href":"module/doc.html#doc-DocGroup"},{"fqn":"doc::DocPage","name":"DocPage","kind":"data","summary":"One authored Markdown page: its sidebar title, the source path (relative to\n`pds.toml`) from which the host and this crate derive the same stable slug,\nand the raw Markdown the host has already loaded, rendered to HTML at build\ntime. The crate reads `markdown`; `path` only names the page, never loads it.","text":"","module":"doc","href":"module/doc.html#doc-DocPage"},{"fqn":"doc::Empty","name":"Empty","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-Empty"},{"fqn":"doc::Engine","name":"Engine","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-Engine"},{"fqn":"doc::Escape","name":"Escape","kind":"component","summary":"All user text — node names, `///` docs, tags, the site title — is carried raw in\nthe props and escaped on interpolation by the renderer; this escaper guards the\nRust-owned document shell, where the title and theme become HTML attributes.","text":"","module":"doc","href":"module/doc.html#doc-Escape"},{"fqn":"doc::Health","name":"Health","kind":"component","summary":"Positions the host's diagnostics in the documentation: prepares raw\nper-module findings (line/column from sources), attributes each to the node\nwhose section it belongs on, and builds the health page and the per-section\nbadges. Attribution is positional: the node whose declaration starts\nclosest before the diagnostic's span owns it, a callable lifting to its\nowner; a span no node encloses falls back to the module page.","text":"","module":"doc","href":"module/doc.html#doc-Health"},{"fqn":"doc::HealthEntry","name":"HealthEntry","kind":"data","summary":"One architecture-health finding, attributed to the node whose section it\nbelongs on: the diagnostic fields plus the owning node's FQN and the\n(prefixed) href to its section — the module page when no node encloses the\nspan.","text":"","module":"doc","href":"module/doc.html#doc-HealthEntry"},{"fqn":"doc::HealthPage","name":"HealthPage","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-HealthPage"},{"fqn":"doc::Highlight","name":"Highlight","kind":"component","summary":"Highlights fenced code blocks at build time, so the shipped site needs no\nclient highlighter and one deterministic output serves every theme:\nPseudoScript through the toolchain's own lexer, emitting class-based spans\ncoloured by the site stylesheet. Any other language passes through escaped\nand unhighlighted — an embedded grammar set for common languages is a\ndeliberate future extension (it would bloat the IDE wasm today).","text":"","module":"doc","href":"module/doc.html#doc-Highlight"},{"fqn":"doc::IndexPage","name":"IndexPage","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-IndexPage"},{"fqn":"doc::Markdown","name":"Markdown","kind":"component","summary":"Renders an authored `[[doc.sidebar]]` page's Markdown source into the HTML that\nfills its `Doc` page body (LANG.md §9.3). The host has already loaded the\nsource into the page's `markdown`; this crate does no I/O and renders that\ncontent in memory. The rendered HTML carries through unescaped as trusted\nauthored content.","text":"","module":"doc","href":"module/doc.html#doc-Markdown"},{"fqn":"doc::MarkdownSite","name":"MarkdownSite","kind":"component","summary":"The static Markdown output mode: turns the same per-page props the HTML\nrenderer consumes into flat `.md` files, each diagram a standalone `.svg`\nasset referenced as an image. The universe page renders as text lists\n(nodes by level, edges with traffic, each flow's legs); the health page as\na table with article links. No SSR engine, no shared assets, no logo — the\nengine-free site for wikis and code hosts.","text":"","module":"doc","href":"module/doc.html#doc-MarkdownSite"},{"fqn":"doc::ModuleCard","name":"ModuleCard","kind":"data","summary":"A module card on the index: its FQN, the (prefixed) href to its page, and an\n\"N item(s)\" line.","text":"","module":"doc","href":"module/doc.html#doc-ModuleCard"},{"fqn":"doc::ModulePage","name":"ModulePage","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-ModulePage"},{"fqn":"doc::NavLink","name":"NavLink","kind":"data","summary":"One header navigation link, its href prefixed for the page's depth; `badge`\ncarries the Health page's finding count (empty elsewhere).","text":"","module":"doc","href":"module/doc.html#doc-NavLink"},{"fqn":"doc::NodeHref","name":"NodeHref","kind":"data","summary":"A universe node's documentation link: its id and the (prefixed) href to its\nsection, so clicking a sphere can navigate to the docs.","text":"","module":"doc","href":"module/doc.html#doc-NodeHref"},{"fqn":"doc::NodeSection","name":"NodeSection","kind":"data","summary":"One node's documentation section: head fields, `///` docs, tags, relationship\ngroups, scenario cards, embedded diagrams, and the health findings attributed\nto the node (rendered as inline badges).","text":"","module":"doc","href":"module/doc.html#doc-NodeSection"},{"fqn":"doc::NodeUrl","name":"NodeUrl","kind":"data","summary":"A resolved cross-reference target: the module page it lives on and its in-page\nanchor id. A cross-link uses these to build an `href`; an unresolved FQN has no\nentry and renders as plain text.","text":"","module":"doc","href":"module/doc.html#doc-NodeUrl"},{"fqn":"doc::PageBody","name":"PageBody","kind":"data","summary":"The page body: the landing index, one authored doc page, one module's page,\nthe 3D universe, or the architecture-health report.","text":"","module":"doc","href":"module/doc.html#doc-PageBody"},{"fqn":"doc::PageProps","name":"PageProps","kind":"data","summary":"The serialisable model for one page, handed to the Svelte server renderer.\nEvery derived value (resolved hrefs, anchors, edge labels, counts) is\nprecomputed here so the Svelte components do no graph logic. User text is\ncarried raw — the renderer escapes it on interpolation. `docGroups` carries\nthe authored `[[doc.sidebar]]` navigation shown above the module tree;\n`nav` carries the header links (Overview, Universe, Health with its finding\ncount); `crumbs` is the breadcrumb trail. Only the universe page's props are\nalso embedded in the document, for its 3D island — every other page ships no\npage data.","text":"","module":"doc","href":"module/doc.html#doc-PageProps"},{"fqn":"doc::Pages","name":"Pages","kind":"component","summary":"Projects the resolved graph into per-page `PageProps` — pure data, no markup.\nBuilds the index props (title, context diagram, module cards), each module page's\nnode sections, and the depth-prefixed sidebar tree mirroring the C4 hierarchy.","text":"","module":"doc","href":"module/doc.html#doc-Pages"},{"fqn":"doc::RelGroup","name":"RelGroup","kind":"data","summary":"One relationship group (\"Parent\", \"Inbound\", or \"Outbound\") and its endpoints.","text":"","module":"doc","href":"module/doc.html#doc-RelGroup"},{"fqn":"doc::RelItem","name":"RelItem","kind":"data","summary":"One relationship endpoint: the edge-kind word, whether to draw the outbound\narrow, the endpoint FQN, its resolved href (absent → plain text), and any label.","text":"","module":"doc","href":"module/doc.html#doc-RelItem"},{"fqn":"doc::Relationships","name":"Relationships","kind":"component","summary":"Renders a node's relationships (LANG.md §9.3): its `for`/owner parent, inbound\nedges, and outbound edges, skipping the structural `for`-parent edge kind. Each\nendpoint is a cross-link when its FQN resolves, plain text otherwise.","text":"","module":"doc","href":"module/doc.html#doc-Relationships"},{"fqn":"doc::RenderError","name":"RenderError","kind":"data","summary":"Why server-rendering a page failed. Every variant is a defect in the bundle, the\nengine, or the props codec — never user model data; well-formed props always\nrender.","text":"","module":"doc","href":"module/doc.html#doc-RenderError"},{"fqn":"doc::RenderedPage","name":"RenderedPage","kind":"data","summary":"The server-render result for one page: the `` contents and the\nrendered body markup, dropped into the document shell.","text":"","module":"doc","href":"module/doc.html#doc-RenderedPage"},{"fqn":"doc::ScenarioCard","name":"ScenarioCard","kind":"data","summary":"One BDD scenario card: name, `///` docs, tags, the ordered steps, and the\nfeature's flow figure (LANG.md §9.5).","text":"","module":"doc","href":"module/doc.html#doc-ScenarioCard"},{"fqn":"doc::Scenarios","name":"Scenarios","kind":"component","summary":"Renders the BDD scenario cards on a node's page: each `feature` targeting the node\nbecomes its given/when/then steps and its flow figure as a card (LANG.md §5.2,\n§9.3, §9.5). Empty when the node has no features.","text":"","module":"doc","href":"module/doc.html#doc-Scenarios"},{"fqn":"doc::SearchEntry","name":"SearchEntry","kind":"data","summary":"One record in the static full-text search index: the symbol or page's FQN,\ndisplay name, kind word, one-line summary, plain-text body, declaring\nmodule, and root-relative href. Sorted by href then FQN so the index is\ndeterministic.","text":"","module":"doc","href":"module/doc.html#doc-SearchEntry"},{"fqn":"doc::SearchIndex","name":"SearchIndex","kind":"component","summary":"Builds the static full-text search index the client palette queries: one\nrecord per linkable node, module page, and authored doc page, plus the\nuniverse and health pages. Shipped as a classic-script JS assignment\n(`search-index.js` setting a window global) so it loads under `file://` —\nnever fetched.","text":"","module":"doc","href":"module/doc.html#doc-SearchIndex"},{"fqn":"doc::SectionDiagnostic","name":"SectionDiagnostic","kind":"data","summary":"One inline badge on a node's section: the severity, rule code, article URL,\nmessage, and source line of a finding attributed to that node.","text":"","module":"doc","href":"module/doc.html#doc-SectionDiagnostic"},{"fqn":"doc::Shell","name":"Shell","kind":"component","summary":"Owns the document shell that wraps each server-rendered body — the part Rust\nrenders, not Svelte: doctype, ``, font + stylesheet links,\nthe theme pre-paint script, and the classic deferred scripts. Scripts are\nclassic (never `type=\"module\"`) and shared data ships as JS-global files, so\nthe site works opened from disk (`file://`). Only the universe page embeds\nits props (`window.__DATA__`) — for its 3D island; every other page ships no\npage data and the client only progressively enhances.","text":"","module":"doc","href":"module/doc.html#doc-Shell"},{"fqn":"doc::SidebarDocGroup","name":"SidebarDocGroup","kind":"data","summary":"One `[[doc.sidebar]]` group in the authored-doc navigation: a heading and its\npage links, shown above the module tree.","text":"","module":"doc","href":"module/doc.html#doc-SidebarDocGroup"},{"fqn":"doc::SidebarDocItem","name":"SidebarDocItem","kind":"data","summary":"One authored doc-page link in the sidebar: its title and the page href, already\nprefixed for the page's depth.","text":"","module":"doc","href":"module/doc.html#doc-SidebarDocItem"},{"fqn":"doc::SidebarModule","name":"SidebarModule","kind":"data","summary":"One sidebar entry: a module-page link and its node tree, hrefs already prefixed\nfor the page's depth.","text":"","module":"doc","href":"module/doc.html#doc-SidebarModule"},{"fqn":"doc::SidebarNode","name":"SidebarNode","kind":"data","summary":"One node in the sidebar tree, recursing into its same-module children.","text":"","module":"doc","href":"module/doc.html#doc-SidebarNode"},{"fqn":"doc::Site","name":"Site","kind":"data","summary":"The whole generated site as in-memory files, in deterministic order: the index,\none page per authored doc (`[[doc.sidebar]]`, in declaration order), then one\npage per module sorted by FQN, plus the shared assets. The doc crate does no\nI/O; the CLI writes each file under the configured output directory.","text":"","module":"doc","href":"module/doc.html#doc-Site"},{"fqn":"doc::SiteBuilder","name":"SiteBuilder","kind":"component","summary":"Orchestrates the whole site: build the URL map, project each page's props, server\nrender it through the SSR engine, wrap it in the document shell, and ship the\nshared assets plus the search index. Pure: returns in-memory `Site` files,\nwrites none.","text":"","module":"doc","href":"module/doc.html#doc-SiteBuilder"},{"fqn":"doc::SiteFile","name":"SiteFile","kind":"data","summary":"One generated file: a site-root-relative `/`-separated path (`index.html`,\n`module/banking.core.html`, `style.css`, `client.js`) and its complete contents.","text":"","module":"doc","href":"module/doc.html#doc-SiteFile"},{"fqn":"doc::SiteInfo","name":"SiteInfo","kind":"data","summary":"Site-wide presentation, identical across pages: the title, the\n`light`/`dark`/`system` theme word, the optional logo filename, and the\n`../` prefix to the site root for this page's depth.","text":"","module":"doc","href":"module/doc.html#doc-SiteInfo"},{"fqn":"doc::SiteStats","name":"SiteStats","kind":"data","summary":"The index page's stats strip: how many systems, containers, components,\nflows, and health findings the model carries.","text":"","module":"doc","href":"module/doc.html#doc-SiteStats"},{"fqn":"doc::Ssr","name":"Ssr","kind":"component","summary":"The server-side render boundary: a JSON-string-in / JSON-string-out seam to the\nJavaScript renderer. The native build embeds a `QuickJS` engine that evaluates the\nprebuilt `ssr.js` bundle (`Doc::Assets`) in-process; a wasm host implements the\nsame seam against its own JS engine. A defect here is a build-asset fault, never\nuser model data.","text":"","module":"doc","href":"module/doc.html#doc-Ssr"},{"fqn":"doc::Step","name":"Step","kind":"data","summary":"One scenario step: its keyword (given/when/then/and/but) and prose.","text":"","module":"doc","href":"module/doc.html#doc-Step"},{"fqn":"doc::Svg","name":"Svg","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-Svg"},{"fqn":"doc::Theme","name":"Theme","kind":"data","summary":"Doc-site colour scheme, written as the `data-theme` attribute on the root\n`` to select a CSS variable set. `System` (the default) follows the\nOS `prefers-color-scheme`, overridable by the in-page toggle; `Light` and\n`Dark` pin the scheme.","text":"","module":"doc","href":"module/doc.html#doc-Theme"},{"fqn":"doc::UniversePage","name":"UniversePage","kind":"data","summary":"","text":"","module":"doc","href":"module/doc.html#doc-UniversePage"},{"fqn":"doc::Urls","name":"Urls","kind":"component","summary":"Maps every node FQN to its page path and anchor, and resolves cross-references to\nmodule-page-relative `href`s. A module page is `module/.html`\n(`::` → `.`); a node is the `#` anchor within it. Only an FQN naming a real\nnode produces a link (LANG.md §9.3).","text":"","module":"doc","href":"module/doc.html#doc-Urls"},{"fqn":"dot","name":"dot","kind":"module","summary":"","text":"","module":"dot","href":"module/dot.html"},{"fqn":"dot::Box2","name":"Box2","kind":"data","summary":"An axis-aligned box: top-left corner and extent.","text":"","module":"dot","href":"module/dot.html#dot-Box2"},{"fqn":"dot::Cluster","name":"Cluster","kind":"data","summary":"A cluster (a `subgraph cluster_*`): its id, optional parent cluster, member\nnode ids, the margin kept outside its members, and the header band reserved on\nthe title side.","text":"","module":"dot","href":"module/dot.html#dot-Cluster"},{"fqn":"dot::ClusterBox","name":"ClusterBox","kind":"data","summary":"A laid-out cluster: its id and bounding box.","text":"","module":"dot","href":"module/dot.html#dot-ClusterBox"},{"fqn":"dot::Clusters","name":"Clusters","kind":"component","summary":"Cluster framing: compute each cluster's bounding box bottom-up so a nested\ncluster's box encloses its children (grown by their margins) and its direct\nmembers, with header room on the title side — `dot`'s recursive `rec_bb`.","text":"","module":"dot","href":"module/dot.html#dot-Clusters"},{"fqn":"dot::Dot","name":"Dot","kind":"container","summary":"`crates/pseudoscript-dot`. Runs the four-pass layered pipeline over a `Graph`\nto produce a `Layout`; also offers the experimental grid placer. The layout\nauthority `emit` drives for C4 diagrams.","text":"","module":"dot","href":"module/dot.html#dot-Dot"},{"fqn":"dot::Edge","name":"Edge","kind":"data","summary":"An input edge from `tail` to `head`, with its minimum rank span, its layout\nweight (higher pulls the endpoints into vertical alignment harder), and an\noptional label size that widens the rank gap to fit.","text":"","module":"dot","href":"module/dot.html#dot-Edge"},{"fqn":"dot::EdgeRoute","name":"EdgeRoute","kind":"data","summary":"A routed edge: the Bézier `spline` and its `polyline` approximation between\n`tail` and `head`, plus where its label sits.","text":"","module":"dot","href":"module/dot.html#dot-EdgeRoute"},{"fqn":"dot::Engine","name":"Engine","kind":"component","summary":"The layered engine: runs the faithful four-pass `dot` pipeline — rank, order,\nposition, splines — then frames the clusters and shifts the whole layout to\nthe origin. Deterministic and panic-free.","text":"","module":"dot","href":"module/dot.html#dot-Engine"},{"fqn":"dot::Graph","name":"Graph","kind":"data","summary":"The input graph the engine lays out: nodes, edges, clusters, the rank and node\nseparations, the rank direction, and any same-rank constraints.","text":"","module":"dot","href":"module/dot.html#dot-Graph"},{"fqn":"dot::Grid","name":"Grid","kind":"component","summary":"The experimental grid placer: an alternative to the layered pipeline that\nsnaps nodes to a grid, honouring drag-to-pin cells and searching for a\nplacement that minimises crossings, distance, and against-flow edges.","text":"","module":"dot","href":"module/dot.html#dot-Grid"},{"fqn":"dot::GridMeta","name":"GridMeta","kind":"data","summary":"Grid metadata carried back when the experimental grid placer ran: the grid\ndimensions, the cell pitch (centre-to-centre), the pixel centre of cell (0,0),\nand the width of the drag-room frame wrapping the grid — the geometry the IDE\nreads to map a drag onto a grid cell, recovering the raw cell from `origin`\nthen subtracting `pad`.","text":"","module":"dot","href":"module/dot.html#dot-GridMeta"},{"fqn":"dot::GridParams","name":"GridParams","kind":"data","summary":"Tunable weights for the experimental grid placer: how strongly it penalises\ncrossings, distance, and against-flow edges, the cell spacing, and how hard it\nsearches.","text":"","module":"dot","href":"module/dot.html#dot-GridParams"},{"fqn":"dot::Layout","name":"Layout","kind":"data","summary":"The layout result: the overall bounding box, the placed nodes, the routed\nedges, the cluster boxes, and — only when the grid placer ran — its grid\nmetadata.","text":"","module":"dot","href":"module/dot.html#dot-Layout"},{"fqn":"dot::LayoutState","name":"LayoutState","kind":"data","summary":"The structure that flows through a pipeline: the (possibly pass-mutated)\ninput graph and its current placement. A pass receives one, returns one, and\nthe framework hands it to the next pass; a re-ranking pass may edit `graph`\nand re-run the base layout.","text":"","module":"dot","href":"module/dot.html#dot-LayoutState"},{"fqn":"dot::Node","name":"Node","kind":"data","summary":"An input node: its id and its rendered size (the placer reserves this box).","text":"","module":"dot","href":"module/dot.html#dot-Node"},{"fqn":"dot::NodePos","name":"NodePos","kind":"data","summary":"A placed node: its id, centre point, and reserved size.","text":"","module":"dot","href":"module/dot.html#dot-NodePos"},{"fqn":"dot::Optimize","name":"Optimize","kind":"component","summary":"The `ShortenLongEdges` pass: a bounded, deterministic post-layout refinement.\nGreedily search for same-rank moves that reduce the sum of squared edge\nlengths (never merging across a cluster boundary), then re-lay-out with the\nimproving moves applied.","text":"","module":"dot","href":"module/dot.html#dot-Optimize"},{"fqn":"dot::Oracle","name":"Oracle","kind":"component","summary":"The test oracle: lays the same graph out with the real `dot` binary and\ncompares, so the port stays faithful rather than approximate. Skipped when\n`dot` is not installed.","text":"","module":"dot","href":"module/dot.html#dot-Oracle"},{"fqn":"dot::Order","name":"Order","kind":"component","summary":"Pass 2 — ordering: order nodes within each rank to minimise edge crossings,\ninserting virtual nodes for long edges so they route through thin lanes.","text":"","module":"dot","href":"module/dot.html#dot-Order"},{"fqn":"dot::Pass","name":"Pass","kind":"data","summary":"One stage of the composable post-layout pipeline: same structure in, same\nstructure out, so passes fold freely. `ShortenLongEdges` is the long-edge\noptimiser; `GridPlacement` wraps the grid placer so it drops into a pipeline.\nNew behaviour is added by writing a pass, not by threading flags through the\nengine.","text":"","module":"dot","href":"module/dot.html#dot-Pass"},{"fqn":"dot::Pin","name":"Pin","kind":"data","summary":"A pin fixing a node to a grid cell: the node's index into `graph.nodes` (the\ncaller resolves the FQN) at a row and column. The IDE's drag-to-pin.","text":"","module":"dot","href":"module/dot.html#dot-Pin"},{"fqn":"dot::Pipeline","name":"Pipeline","kind":"component","summary":"The composable post-layout pipeline: the base layout produces the starting\n`LayoutState`; each `Pass` folds it into the next. A pass adjusts geometry\ndirectly or edits the input graph (e.g. adds same-rank hints) and re-runs the\nbase layout.","text":"","module":"dot","href":"module/dot.html#dot-Pipeline"},{"fqn":"dot::Position","name":"Position","kind":"component","summary":"Pass 3 — positioning: assign x-coordinates by network simplex on an auxiliary\ngraph (aligning nodes and straightening long edges), and y-coordinates per\nrank stacked with `ranksep` plus any label and cluster-header room.","text":"","module":"dot","href":"module/dot.html#dot-Position"},{"fqn":"dot::Pt","name":"Pt","kind":"data","summary":"A point in layout coordinates.","text":"","module":"dot","href":"module/dot.html#dot-Pt"},{"fqn":"dot::Rank","name":"Rank","kind":"component","summary":"Pass 1 — rank assignment: give each node an integer rank by network simplex,\nbanding each cluster onto contiguous ranks and honouring same-rank constraints.","text":"","module":"dot","href":"module/dot.html#dot-Rank"},{"fqn":"dot::RankDir","name":"RankDir","kind":"data","summary":"The rank direction: top-to-bottom (the C4 default) or left-to-right.","text":"","module":"dot","href":"module/dot.html#dot-RankDir"},{"fqn":"dot::SearchMode","name":"SearchMode","kind":"data","summary":"How the experimental grid placer searches for a placement: pick automatically\nby size, always the bounded heuristic, or brute-force exhaustive (tiny graphs\nonly).","text":"","module":"dot","href":"module/dot.html#dot-SearchMode"},{"fqn":"dot::Size","name":"Size","kind":"data","summary":"A width/height extent.","text":"","module":"dot","href":"module/dot.html#dot-Size"},{"fqn":"dot::Splines","name":"Splines","kind":"component","summary":"Pass 4 — splines: route each edge as a piecewise Bézier through its\nvirtual-node corridor, clipped to the endpoint borders, with a label point at\nthe polyline midpoint.","text":"","module":"dot","href":"module/dot.html#dot-Splines"},{"fqn":"emit","name":"emit","kind":"module","summary":"","text":"","module":"emit","href":"module/emit.html"},{"fqn":"emit::BoundaryFrame","name":"BoundaryFrame","kind":"data","summary":"An enclosing frame of a boundary view: the boundary node's FQN (so the canvas\ncan drill into it), title, C4 kind (frame accent), and the cluster rectangle.","text":"","module":"emit","href":"module/emit.html#emit-BoundaryFrame"},{"fqn":"emit::C4EdgeKind","name":"C4EdgeKind","kind":"data","summary":"The relationship a routed edge expresses: a body `call`, a synthesised\n`trigger` from an initiator, or a `from` `provenance` link. Maps from\n`model::EdgeKind`, dropping the structural `for`-parent link.","text":"","module":"emit","href":"module/emit.html#emit-C4EdgeKind"},{"fqn":"emit::C4Layout","name":"C4Layout","kind":"data","summary":"A dot-placed C4 view for the interactive canvas and the static SVG: canvas\nsize, placed node cards, routed edges, the enclosing boundary frames\n(outermost first; empty for a context view), and — only under experimental\ngrid placement — the grid geometry for drag-to-pin.","text":"","module":"emit","href":"module/emit.html#emit-C4Layout"},{"fqn":"emit::C4Scene","name":"C4Scene","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-C4Scene"},{"fqn":"emit::C4Tweaks","name":"C4Tweaks","kind":"data","summary":"Per-diagram C4 layout tweaks (the UI's \"Layout\" toggles): run the long-edge\noptimiser, lay out left-to-right instead of top-to-bottom, scale the spacing,\nand — experimentally — place by brute-force grid search instead of the layered\nengine, with its own cost dials and search mode.","text":"","module":"emit","href":"module/emit.html#emit-C4Tweaks"},{"fqn":"emit::C4View","name":"C4View","kind":"data","summary":"Which C4 view a `C4Scene` is — drives the golden header keyword.","text":"","module":"emit","href":"module/emit.html#emit-C4View"},{"fqn":"emit::Component","name":"Component","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-Component"},{"fqn":"emit::Container","name":"Container","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-Container"},{"fqn":"emit::Data","name":"Data","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-Data"},{"fqn":"emit::DataEntity","name":"DataEntity","kind":"data","summary":"One entity in a `data` ER view: its FQN, label, disclosed form, rows, whether\nit is the focal type, and its laid-out card rectangle.","text":"","module":"emit","href":"module/emit.html#emit-DataEntity"},{"fqn":"emit::DataLink","name":"DataLink","kind":"data","summary":"A field-type reference link between two entities in a data view: the\nreferencing and referenced FQNs and the field driving the reference.","text":"","module":"emit","href":"module/emit.html#emit-DataLink"},{"fqn":"emit::DataScene","name":"DataScene","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-DataScene"},{"fqn":"emit::Edges","name":"Edges","kind":"component","summary":"Builds the routed-edge set for a C4 scene from the graph's edges: maps each\n`model::EdgeKind` to a `C4EdgeKind` (dropping `for`-parent links), lifts each\nendpoint to the in-view node it belongs to, drops self-loops and\nout-of-view edges, then sorts and de-duplicates.","text":"","module":"emit","href":"module/emit.html#emit-Edges"},{"fqn":"emit::Emit","name":"Emit","kind":"container","summary":"`crates/pseudoscript-emit`. Projects a view into a `Scene` and renders it to\nSVG; C4 views are placed by the `dot` layered engine, the sequence/data/feature\nviews by the matching `layout` engine.","text":"","module":"emit","href":"module/emit.html#emit-Emit"},{"fqn":"emit::EmitError","name":"EmitError","kind":"data","summary":"Why projecting a view failed: the target FQN names no node, or names a node of\nthe wrong kind for the requested view.","text":"","module":"emit","href":"module/emit.html#emit-EmitError"},{"fqn":"emit::EntityForm","name":"EntityForm","kind":"data","summary":"The disclosed form of a data entity, driving the card's eyebrow and rows: a\nrecord of typed fields, a discriminated union of variants, or a black box.","text":"","module":"emit","href":"module/emit.html#emit-EntityForm"},{"fqn":"emit::EntityRow","name":"EntityRow","kind":"data","summary":"One row of a data entity: the field or variant name, its rendered type (empty\nfor a union variant), and the FQN of the data type this row targets when it\nreferences one.","text":"","module":"emit","href":"module/emit.html#emit-EntityRow"},{"fqn":"emit::Feature","name":"Feature","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-Feature"},{"fqn":"emit::FeatureScene","name":"FeatureScene","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-FeatureScene"},{"fqn":"emit::FeatureStepNode","name":"FeatureStepNode","kind":"data","summary":"One step box in a `feature` flow view: the step keyword, its prose, and the\nlaid-out box rectangle.","text":"","module":"emit","href":"module/emit.html#emit-FeatureStepNode"},{"fqn":"emit::Frame","name":"Frame","kind":"data","summary":"A nestable frame over a body of sequence items, drawn as an enclosing box\nwith its condition label.","text":"","module":"emit","href":"module/emit.html#emit-Frame"},{"fqn":"emit::FrameItem","name":"FrameItem","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-FrameItem"},{"fqn":"emit::FrameKind","name":"FrameKind","kind":"data","summary":"The kind of a sequence frame: `if`/`else` becomes an `alt` frame, `for`/\n`while` becomes a `loop` frame.","text":"","module":"emit","href":"module/emit.html#emit-FrameKind"},{"fqn":"emit::GridInfo","name":"GridInfo","kind":"data","summary":"The grid the experimental placement used, in absolute canvas coordinates:\ncell `(r, c)` is centred at `origin + (c·cellW, r·cellH)`; `pad` is the\ndrag-room frame, so a pixel maps to its pin cell by recovering the raw cell\nthen subtracting `pad`.","text":"","module":"emit","href":"module/emit.html#emit-GridInfo"},{"fqn":"emit::GridPin","name":"GridPin","kind":"data","summary":"A node the user has pinned to a grid cell (drag-to-pin), identified by FQN;\nthe emit layer resolves it to a `dot` node index for the current view. Honoured\nonly under experimental grid placement.","text":"","module":"emit","href":"module/emit.html#emit-GridPin"},{"fqn":"emit::GridSearch","name":"GridSearch","kind":"data","summary":"Which grid search the experimental grid placer runs — the UI's\nheuristic-vs-brute-force toggle. Mirrors `dot::SearchMode` so the IDE need not\ndepend on `dot`.","text":"","module":"emit","href":"module/emit.html#emit-GridSearch"},{"fqn":"emit::LaidOutEdge","name":"LaidOutEdge","kind":"data","summary":"A routed edge for the canvas/SVG: its endpoints (`source` stands for the wire\n`from`), kind, stacked labels, the engine's polyline (at least two points),\nthe label position at the polyline midpoint when labels exist, and whether it\ndraws dashed (a `from`-provenance edge).","text":"","module":"emit","href":"module/emit.html#emit-LaidOutEdge"},{"fqn":"emit::LaidOutNode","name":"LaidOutNode","kind":"data","summary":"A node card placed by the `dot` engine for the canvas/SVG: its FQN, C4 kind\n(accent and eyebrow), display label, optional `///` summary, and rectangle.","text":"","module":"emit","href":"module/emit.html#emit-LaidOutNode"},{"fqn":"emit::Layout","name":"Layout","kind":"component","summary":"Assigns geometry. Projection stamps each C4 scene with simple row-flow\ncoordinates (`solveC4`) — the panic-proof fallback geometry every scene\ncarries. The real placement runs at render/canvas time: `layoutC4` drives the\n`dot` layered engine (rank/order/position/splines), framing boundary children\nas a cluster, tuned by per-diagram `C4Tweaks` and drag-to-pin cells. The\nsequence/data/feature scenes are placed by the matching `layout` engine.","text":"","module":"emit","href":"module/emit.html#emit-Layout"},{"fqn":"emit::Lifeline","name":"Lifeline","kind":"data","summary":"A sequence lifeline (a participant): its FQN, C4 kind for the head card, the\nnode's `///` summary shown dimmed under the name, and the structural ancestry\npath under a container/component name. No coordinate — the sequence layout\nassigns the lifeline x.","text":"","module":"emit","href":"module/emit.html#emit-Lifeline"},{"fqn":"emit::Message","name":"Message","kind":"data","summary":"A message between two lifelines (or a self-message). `label` is the method\nname, or `Ok`/`Err`/empty for a return; `detail` is the type detail shown after\nit — a call's `(params): ret` signature or a return's concrete type.","text":"","module":"emit","href":"module/emit.html#emit-Message"},{"fqn":"emit::MessageItem","name":"MessageItem","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-MessageItem"},{"fqn":"emit::MessageKind","name":"MessageKind","kind":"data","summary":"The kind of a sequence message: a `call` to another lifeline, a `return` to\nthe caller, or a `self`-message on the owner's own lifeline.","text":"","module":"emit","href":"module/emit.html#emit-MessageKind"},{"fqn":"emit::PlacedNode","name":"PlacedNode","kind":"data","summary":"A node placed in a C4 view: its FQN, C4 kind, display label, optional `///`\nsummary (the card's dimmed description), the boundary it sits inside, and the\nlayout rectangle assigned by the placer.","text":"","module":"emit","href":"module/emit.html#emit-PlacedNode"},{"fqn":"emit::PointI","name":"PointI","kind":"data","summary":"An integer point on the canvas (a rounded layout coordinate).","text":"","module":"emit","href":"module/emit.html#emit-PointI"},{"fqn":"emit::Projector","name":"Projector","kind":"component","summary":"Projects a `View` out of the graph (LANG.md §9): dispatches on the view\nvariant, collecting nodes and edges for a C4 scene or tracing a callable body\nfor a sequence scene, then hands off to the placer. Fails with `EmitError`\nwhen the view's target is missing or the wrong kind.","text":"","module":"emit","href":"module/emit.html#emit-Projector"},{"fqn":"emit::Rect","name":"Rect","kind":"data","summary":"An axis-aligned layout rectangle in renderer coordinates.","text":"","module":"emit","href":"module/emit.html#emit-Rect"},{"fqn":"emit::RoutedEdge","name":"RoutedEdge","kind":"data","summary":"An edge routed between two C4 nodes: its endpoints, kind, and the merged call\nlabels (sorted and de-duplicated; empty for a trigger or provenance edge).\nParallel calls between the same pair collapse into one multi-label edge.\n`source` is the wire field `from`, renamed because `from` is a PseudoScript\nkeyword.","text":"","module":"emit","href":"module/emit.html#emit-RoutedEdge"},{"fqn":"emit::Scene","name":"Scene","kind":"data","summary":"A laid-out diagram: the notation-neutral IR the SVG renderer consumes. A C4\nscene carries placed nodes and routed edges; a sequence scene carries\nlifelines and ordered messages with nested frames. This IR is the generation\nconformance surface (ADR-017): coordinates are carried for the renderer only,\nnever serialised to the golden.","text":"","module":"emit","href":"module/emit.html#emit-Scene"},{"fqn":"emit::SeqItem","name":"SeqItem","kind":"data","summary":"One ordered item in a sequence trace: a message or a nested frame.","text":"","module":"emit","href":"module/emit.html#emit-SeqItem"},{"fqn":"emit::Sequence","name":"Sequence","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-Sequence"},{"fqn":"emit::SequenceScene","name":"SequenceScene","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-SequenceScene"},{"fqn":"emit::SvgRenderer","name":"SvgRenderer","kind":"component","summary":"Renders a laid-out `Scene` to standalone SVG markup — string-building, no\ntemplate engine and no headless browser (LANG.md §9.3, ADR-017). Dispatches\non the scene variant; the layout engine is wrapped so no input panics the\nrender, falling back to the scene's own simple coordinates.","text":"","module":"emit","href":"module/emit.html#emit-SvgRenderer"},{"fqn":"emit::Trace","name":"Trace","kind":"component","summary":"Walks a callable's body trace into ordered sequence items, registering each\nparticipant on first appearance (LANG.md §9.2, §7). A real trigger's\ninitiator leads as the first lifeline with a synthesised inbound call;\nabsent a trigger the owner leads. A `call` is a message to the target; a\ndisclosed callee expands inline; a same-node call is a self-message that\nexpands its disclosed body inline too; a return\nmarks `Ok`/`Err`; `if`/`else` nests an `alt` frame, `for`/`while` a `loop`\nframe.","text":"","module":"emit","href":"module/emit.html#emit-Trace"},{"fqn":"emit::UnknownNode","name":"UnknownNode","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-UnknownNode"},{"fqn":"emit::View","name":"View","kind":"data","summary":"Which view to project from the graph (LANG.md §9). Context is the C1 whole;\nContainer/Component zoom into one boundary; Sequence traces one entry point;\nData draws a `data` type's entity (ER) view (§9.4); Feature draws a `feature`\nscenario's flow (§9.5).","text":"","module":"emit","href":"module/emit.html#emit-View"},{"fqn":"emit::WrongKind","name":"WrongKind","kind":"data","summary":"","text":"","module":"emit","href":"module/emit.html#emit-WrongKind"},{"fqn":"format","name":"format","kind":"module","summary":"","text":"","module":"format","href":"module/format.html"},{"fqn":"format::Format","name":"Format","kind":"container","summary":"`crates/pseudoscript-format`. The canonical formatter: parse, then\npretty-print the tree to one canonical form. The string-to-string entry the\nCLI and LSP depend on.","text":"","module":"format","href":"module/format.html#format-Format"},{"fqn":"format::FormatError","name":"FormatError","kind":"data","summary":"Why formatting failed. The only failure is unparseable input: `Parse`\ncarries the rendered error messages (one per diagnostic) for context, and\nthe caller keeps its original text.","text":"","module":"format","href":"module/format.html#format-FormatError"},{"fqn":"format::Formatter","name":"Formatter","kind":"component","summary":"Drives the headline flow: parse the source, short-circuit to\n`FormatError::Parse` if any error diagnostic surfaced, else hand the tree to\nthe printer for canonical text.","text":"","module":"format","href":"module/format.html#format-Formatter"},{"fqn":"format::Parse","name":"Parse","kind":"data","summary":"","text":"","module":"format","href":"module/format.html#format-Parse"},{"fqn":"format::Printer","name":"Printer","kind":"component","summary":"The canonical pretty-printer: walks the AST top-down, appending to a buffer,\nand emits one canonical form. Indentation is a level counter (two spaces\neach); leading trivia is normalised — blank-line runs collapse to at most one\nseparator and `//` / `/* */` comments reproduce at the current indent in\nsource order. Internals are black-boxed per construct; the top-level `print`\nflow is disclosed.","text":"","module":"format","href":"module/format.html#format-Printer"},{"fqn":"format::PrinterState","name":"PrinterState","kind":"data","summary":"The running pretty-printer state: the text buffer being built, the current\nnesting depth (two spaces each), and whether the current line has had its\nindentation written yet so further writes append in place.","text":"","module":"format","href":"module/format.html#format-PrinterState"},{"fqn":"ide","name":"ide","kind":"module","summary":"","text":"","module":"ide","href":"module/ide.html"},{"fqn":"ide::Bootstrap","name":"Bootstrap","kind":"component","summary":"Boots the IDE in a browser tab: load the one wasm bundle (IdeSession), then\nrestore the URL hash's workspace — a share link mounts in memory and needs\nno disk — or fall back to the launcher. File System Access support gates\nonly the disk features (open folder, save, watch): without it those actions\nare disabled with a notice, while share links and the bundled examples\nstill open.","text":"","module":"ide","href":"module/ide.html#ide-Bootstrap"},{"fqn":"ide::BrowserWorkspace","name":"BrowserWorkspace","kind":"data","summary":"The in-browser workspace state the session holds: the open `.pds` modules, the\n`pds.toml` manifest text, and the dependency modules resolved from\n`pds_modules/` as externals (LANG.md §8.3). Held in memory — a decoded\nsample/share link or a local directory loaded through the file-system port.\nEdits never leave the tab.","text":"","module":"ide","href":"module/ide.html#ide-BrowserWorkspace"},{"fqn":"ide::CodeMirrorAdapter","name":"CodeMirrorAdapter","kind":"component","summary":"The editor adapter over CodeMirror 6: the text/cursor model, the completion\nsource, the hover tooltip, the diagnostics gutter, and edit events forwarded\ninto the session. The only place CodeMirror specifics live — it drives the\nsession and applies the typed values it gets back.","text":"","module":"ide","href":"module/ide.html#ide-CodeMirrorAdapter"},{"fqn":"ide::Completion","name":"Completion","kind":"data","summary":"A completion candidate offered as the author types: the inserted label, the\ninteger LSP `CompletionItemKind` the editor maps to an icon, and detail text\n(absent for candidates with nothing to add).","text":"","module":"ide","href":"module/ide.html#ide-Completion"},{"fqn":"ide::DiagramCanvas","name":"DiagramCanvas","kind":"component","summary":"The diagram canvas: draws the **JSON** `Scene` interactively (Svelte Flow\npan/zoom over geometry positioned by `emit`'s layout engine for C4, a\ndepth-collapsible timeline for sequences), and exports the current view as\n**PNG or SVG**, both produced in the browser from the drawn diagram. JSON is\nthe draw format; no diagram image crosses the wasm surface. The editor\nport's diagram seam binds to this.","text":"","module":"ide","href":"module/ide.html#ide-DiagramCanvas"},{"fqn":"ide::Diagrams","name":"Diagrams","kind":"component","summary":"Diagram projection: builds the workspace graph and projects a view to the\nJSON `Scene` via `emit::Projector`; the host's canvas draws it. The wasm\nsurface carries only the JSON scene and layout — SVG and PNG are export\nconcerns of the canvas (PNG rasterised in the browser), and the static SVG\nform is a CLI/doc concern of the `emit` crate, not exposed here.","text":"","module":"ide","href":"module/ide.html#ide-Diagrams"},{"fqn":"ide::DocConfig","name":"DocConfig","kind":"data","summary":"The doc-site build input: the manifest plus each authored page's Markdown,\nloaded by the host. Field detail is the wasm boundary's concern.","text":"","module":"ide","href":"module/ide.html#ide-DocConfig"},{"fqn":"ide::Docs","name":"Docs","kind":"component","summary":"The doc-site builder: renders the whole site in-browser through the same\n`pseudoscript-doc` crate `pds doc` runs, driving SSR through a render\ncallback the host supplies (the browser's JS engine is the SSR engine) — so\nthe IDE writes the exact site the CLI ships, health page included. The site\nbuilds to the opened folder's `target/doc/` only; an in-memory workspace is\ntold to open a folder first (no blob-preview path).","text":"","module":"ide","href":"module/ide.html#ide-Docs"},{"fqn":"ide::FimClient","name":"FimClient","kind":"component","summary":"Provider-agnostic completion client: one `complete` contract over\nOpenAI-compatible HTTP, so one client covers Codestral, OpenRouter, Together,\nOllama, and vLLM. Stateless — the caller hands it the settings snapshot and\nowns cancellation (`GhostText` aborts the in-flight request on the next\nkeystroke; the client honours the abort). Every failure comes back as a\nclassified `ProviderError`, so the caller can surface why a request bailed.","text":"","module":"ide","href":"module/ide.html#ide-FimClient"},{"fqn":"ide::FimPrompt","name":"FimPrompt","kind":"data","summary":"The assembled fill-in-the-middle request: the windowed buffer text before and\nafter the caret, and the cached grammar primer that steers a general model to\nemit valid PseudoScript.","text":"","module":"ide","href":"module/ide.html#ide-FimPrompt"},{"fqn":"ide::FsAccessAdapter","name":"FsAccessAdapter","kind":"component","summary":"The file-system adapter over the browser File System Access API: directory\npicker, recursive walk for `.pds` modules, deriving each module's flat FQN\nfrom its path (LANG.md §8.1, ADR-031), `pds.lock` + `pds_modules/` reads for\ndependency externals, and per-file read/write through handles. The only place\nFS-Access specifics live; it pushes what it reads into the session via\n`mount`.","text":"","module":"ide","href":"module/ide.html#ide-FsAccessAdapter"},{"fqn":"ide::GhostText","name":"GhostText","kind":"component","summary":"Inline ghost-text completion over CodeMirror 6, beside the grammar dropdown:\nstructural candidates stay local and deterministic in `CodeMirrorAdapter`;\nthe remote model supplies only multi-token greyed text. CodeMirror has no\nnative inline-suggestion UI, so this is a custom view plugin rendering a\nwidget decoration at the caret — Tab accepts, Esc dismisses, any edit clears.","text":"","module":"ide","href":"module/ide.html#ide-GhostText"},{"fqn":"ide::GridPin","name":"GridPin","kind":"data","summary":"A node the user has dragged onto a grid cell, identified by FQN. Carried inside\n`LayoutTweaks`; honoured only under experimental grid placement.","text":"","module":"ide","href":"module/ide.html#ide-GridPin"},{"fqn":"ide::IdeSession","name":"IdeSession","kind":"container","summary":"`crates/pseudoscript-ide` — the IDE application, Rust compiled to a single\nwasm. It owns the workspace state and is the whole browser API: the host\npushes modules in (`mount`/`setSource`) and pulls every answer out as a\ntyped value — the session never calls the DOM, CodeMirror, or the File\nSystem Access API. It delegates language intelligence to `lsp_core` and\n`model::Checks`, diagrams to `emit::Projector`, the 3D universe to\n`universe::Adapter`, and the doc site to `doc::SiteBuilder` — each a Rust\nlibrary linked in, never a separate wasm. The one outbound seam is the doc\nbuild's SSR render callback the host supplies. Everything here is Rust.","text":"","module":"ide","href":"module/ide.html#ide-IdeSession"},{"fqn":"ide::LanguageSession","name":"LanguageSession","kind":"component","summary":"The language session: diagnostics, completion, hover, go-to, references,\nrename, outline, folding, and semantic tokens over the held workspace, every\nanswer delegated to the pure toolchain logic (`model::Checks` for diagnostics,\n`lsp_core` for cursor features) with the dependency externals bound. Every\nanswer is a typed value the host applies — the session pushes nothing at the\neditor.","text":"","module":"ide","href":"module/ide.html#ide-LanguageSession"},{"fqn":"ide::Launcher","name":"Launcher","kind":"component","summary":"The project launcher — the first surface. Offers the recents, open-a-folder,\nNew Project, and the bundled examples. Examples and share links mount in\nmemory and need no disk; only the disk actions gate on File System Access\nsupport. Dismissible, so the menus, help, and settings stay reachable\nwithout a project.","text":"","module":"ide","href":"module/ide.html#ide-Launcher"},{"fqn":"ide::LayoutTweaks","name":"LayoutTweaks","kind":"data","summary":"The IDE's per-diagram C4 layout toggles, crossing to JS as a typed object: run\nthe long-edge optimiser, the reading `orientation` (`\"tb\"`/`\"lr\"`), a `spacing`\npreset (`\"compact\"`/`\"comfortable\"`/`\"roomy\"`), and the experimental grid dials\nand pins. Translated to `emit::C4Tweaks` inside the session.","text":"","module":"ide","href":"module/ide.html#ide-LayoutTweaks"},{"fqn":"ide::LlmProvider","name":"LlmProvider","kind":"system","summary":"The author's configured OpenAI-compatible completion provider — hosted\n(OpenAI, Codestral, OpenRouter, Together) or local (Ollama, vLLM). External:\nthe IDE only speaks its HTTP API; transport and HTTP failures surface as\nclassified `ProviderError`s.","text":"","module":"ide","href":"module/ide.html#ide-LlmProvider"},{"fqn":"ide::LlmSettings","name":"LlmSettings","kind":"data","summary":"The author's AI-completion provider settings, persisted in the browser: the\nfeature toggle, the chosen preset (`\"ollama\"`, `\"openai\"`, or `\"custom\"` —\nthe presets pin the endpoint and wire shape so setup is pick-and-go), the\nOpenAI-compatible endpoint, the bring-your-own API key (stored in\nlocalStorage, sent only to the configured endpoint), the model id, and the\nwire shape (`\"fim\"` for a native fill-in-the-middle route, `\"chat\"` for the\nchat-completions fallback).","text":"","module":"ide","href":"module/ide.html#ide-LlmSettings"},{"fqn":"ide::LlmSettingsStore","name":"LlmSettingsStore","kind":"component","summary":"The author's AI-completion settings, persisted to browser localStorage and\nedited through the Settings dialog's \"AI Completion\" tab. Bring-your-own key:\nit never leaves the tab except on requests to the configured endpoint.\nSetup is preset-first: picking a provider applies its pinned endpoint and\nwire shape, so only the Custom preset exposes the raw fields. The store also\ncarries the session's last completion failure (never persisted) so the\nstatus chip and the settings tab can show why ghost text is bailing.","text":"","module":"ide","href":"module/ide.html#ide-LlmSettingsStore"},{"fqn":"ide::LocalInput","name":"LocalInput","kind":"data","summary":"One local-source dependency file the host reads for `dependencyModules`: the\ndependency name (ADR-026), its FQN within the dependency workspace, and its\nsource.","text":"","module":"ide","href":"module/ide.html#ide-LocalInput"},{"fqn":"ide::Occurrence","name":"Occurrence","kind":"data","summary":"One occurrence of a symbol: the module it lies in, its line/column span, the\nsource line text with the match offsets within it, and whether it is the\ndeclaration site.","text":"","module":"ide","href":"module/ide.html#ide-Occurrence"},{"fqn":"ide::OutlineNode","name":"OutlineNode","kind":"data","summary":"One row of the workspace outline: a node's FQN, simple name, C4 kind,\ncontainment parent (absent on roots), whether it is a triggered flow entry,\nits `///` summary (absent when undocumented), and its declaration line/column.","text":"","module":"ide","href":"module/ide.html#ide-OutlineNode"},{"fqn":"ide::PickError","name":"PickError","kind":"data","summary":"Why a folder pick yielded no workspace: the picker failed — the API missing,\nblocked by permission policy, or denied by the embedder. Distinct from a\nuser cancel, which is silent: a cancel is the user's choice, a failure is\nthe product's to explain.","text":"","module":"ide","href":"module/ide.html#ide-PickError"},{"fqn":"ide::ProviderError","name":"ProviderError","kind":"data","summary":"A classified completion-provider failure: `kind` is `\"network\"` (endpoint\nunreachable or CORS-blocked), `\"auth\"` (401/403), `\"notFound\"` (404 route or\nmodel), `\"timeout\"`, or `\"http\"` (any other non-OK status); `message` states\nwhat failed and `hint` names the fix for the configured provider (e.g. the\n`OLLAMA_ORIGINS` command for a CORS-blocked local Ollama).","text":"","module":"ide","href":"module/ide.html#ide-ProviderError"},{"fqn":"ide::References","name":"References","kind":"data","summary":"The find-references result for the symbol under the caret: the symbol's FQN, a\nhuman title, and every occurrence across the workspace.","text":"","module":"ide","href":"module/ide.html#ide-References"},{"fqn":"ide::RenameSelection","name":"RenameSelection","kind":"data","summary":"One occurrence the user chose to rename, keyed by the module FQN and the\n1-based line/column an `ide::Occurrence` reported. The rename rewrites only\nthe selected occurrences, not every match.","text":"","module":"ide","href":"module/ide.html#ide-RenameSelection"},{"fqn":"ide::RenamedSource","name":"RenamedSource","kind":"data","summary":"One module rewritten by a rename: its FQN and new source text.","text":"","module":"ide","href":"module/ide.html#ide-RenamedSource"},{"fqn":"ide::RenderedFile","name":"RenderedFile","kind":"data","summary":"One file of the rendered documentation site: its site-relative path and\ncontents.","text":"","module":"ide","href":"module/ide.html#ide-RenderedFile"},{"fqn":"ide::Samples","name":"Samples","kind":"component","summary":"The bundled example catalogue, discovered at build time from the samples\nfolder (one folder per example: meta.json, the `.pds` modules, `pds.toml`,\ndoc pages). Each entry doubles as a New-project template and as an\nin-memory workspace.","text":"","module":"ide","href":"module/ide.html#ide-Samples"},{"fqn":"ide::Universe","name":"Universe","kind":"component","summary":"The 3D universe: builds the software graph from the held workspace and flattens\nit to a snapshot the `ForceGraph` renders, with the entry-point flows traced\nin Rust. Delegates to the `universe` crate; the wasm surface carries only the\nflat snapshot and flows — positions are the renderer's.","text":"","module":"ide","href":"module/ide.html#ide-Universe"},{"fqn":"ide::UniverseEdge","name":"UniverseEdge","kind":"data","summary":"One directed relationship in the 3D graph, weighted by traffic (call count).\nThe wire field is `from`; `from` is a PseudoScript keyword, so the model\nnames it `source`.","text":"","module":"ide","href":"module/ide.html#ide-UniverseEdge"},{"fqn":"ide::UniverseNode","name":"UniverseNode","kind":"data","summary":"One node in the 3D relationship graph the IDE draws: its FQN, C4 level, and\ncontainment parent (absent on roots). The typed boundary form of a\n`universe::NodeOut`.","text":"","module":"ide","href":"module/ide.html#ide-UniverseNode"},{"fqn":"ide::UniverseSnapshot","name":"UniverseSnapshot","kind":"data","summary":"The 3D-view snapshot the session hands the `ForceGraph`: nodes and directed\nweighted edges, no positions (the renderer lays them out). The typed boundary\nform of a `universe::Snapshot`.","text":"","module":"ide","href":"module/ide.html#ide-UniverseSnapshot"},{"fqn":"ide::VendoredInput","name":"VendoredInput","kind":"data","summary":"One vendored git-dependency file the host reads for `dependencyModules`: its\n`pds_modules/` slug, its FQN within the dependency workspace (the host's\npath→FQN derivation, LANG.md §8.1), and its source.","text":"","module":"ide","href":"module/ide.html#ide-VendoredInput"},{"fqn":"ide::WebIde","name":"WebIde","kind":"container","summary":"`web-ide` — the IDE at `ide.pdscript.dev`: a SvelteKit shell on Cloudflare\nWorkers static assets. Pure glue — it loads the one wasm bundle and binds the\nadapters to the session's ports. It holds no language logic.","text":"","module":"ide","href":"module/ide.html#ide-WebIde"},{"fqn":"ide::Workspace","name":"Workspace","kind":"component","summary":"The in-memory project the session holds: the parsed modules keyed by FQN (an\nopen buffer overlays via `setSource`), the dependency externals, and the\nmemoised resolved workspace + graph — invalidated by any mutation, rebuilt\nonce for the query burst between keystrokes. Edits never leave the tab; an\nexplicit save writes through the host's file handles, not this session.","text":"","module":"ide","href":"module/ide.html#ide-Workspace"},{"fqn":"landing","name":"landing","kind":"module","summary":"","text":"","module":"landing","href":"module/landing.html"},{"fqn":"landing::Landing","name":"Landing","kind":"container","summary":"`web-landing` — the marketing site at `pdscript.dev`. A static Svelte build on\nCloudflare Workers static assets; no backend. It routes a visitor to one of\nthree surfaces and otherwise just tells the model-driven story.","text":"","module":"landing","href":"module/landing.html#landing-Landing"},{"fqn":"landing::Visitor","name":"Visitor","kind":"person","summary":"A prospective user reading the landing page.","text":"","module":"landing","href":"module/landing.html#landing-Visitor"},{"fqn":"layout","name":"layout","kind":"module","summary":"","text":"","module":"layout","href":"module/layout.html"},{"fqn":"layout::Activation","name":"Activation","kind":"data","summary":"An execution-activation bar on a lifeline, spanning a participant's first-to-\nlast involvement; `owner` marks the entry's focus lifeline.","text":"","module":"layout","href":"module/layout.html#layout-Activation"},{"fqn":"layout::Core","name":"Core","kind":"component","summary":"The shared geometry/text core: measures label and title widths, wraps\ndescriptions to a width, and accumulates bounding boxes. Every engine builds\non it, so text sizing and bounds math live in exactly one place.","text":"","module":"layout","href":"module/layout.html#layout-Core"},{"fqn":"layout::Diagram","name":"Diagram","kind":"data","summary":"A structural sequence diagram: the participants (lifelines) and the ordered\nitems — messages and nested fragments — traced down them. The sequence\nengine's input.","text":"","module":"layout","href":"module/layout.html#layout-Diagram"},{"fqn":"layout::Divider","name":"Divider","kind":"data","summary":"A horizontal split between two fragment sections: its y and the following\nsection's guard.","text":"","module":"layout","href":"module/layout.html#layout-Divider"},{"fqn":"layout::FragKind","name":"FragKind","kind":"data","summary":"The kind of a fragment frame: an `alt` (if/else) or a `loop` (for/while).","text":"","module":"layout","href":"module/layout.html#layout-FragKind"},{"fqn":"layout::Fragment","name":"Fragment","kind":"data","summary":"A nestable fragment over a body of items, drawn as an enclosing frame: its\nkind and the labelled sections it splits into (an `alt` has several).","text":"","module":"layout","href":"module/layout.html#layout-Fragment"},{"fqn":"layout::FragmentItem","name":"FragmentItem","kind":"data","summary":"","text":"","module":"layout","href":"module/layout.html#layout-FragmentItem"},{"fqn":"layout::Item","name":"Item","kind":"data","summary":"One ordered item down the lifelines: a message or a nested fragment.","text":"","module":"layout","href":"module/layout.html#layout-Item"},{"fqn":"layout::Layout","name":"Layout","kind":"container","summary":"`crates/pseudoscript-layout`. The geometry/text `Core` and the sequence\nengine, each built on the `Projection` contract — turn a structural input\ninto a positioned output under tunable metrics. The sequence engine is the\nonly one implemented here: C4 placement is delegated to `dot` (the layered\nengine), which `emit` drives directly; a flowchart engine is a planned\nextension point on the same core, not yet built.","text":"","module":"layout","href":"module/layout.html#layout-Layout"},{"fqn":"layout::Message","name":"Message","kind":"data","summary":"A message between two lifelines: its source and target ids (equal for a\nself-message), kind, primary label (method name or return marker), and the\ndimmed detail after it (a call signature or return type). `source` is the\nwire field `from`, renamed because `from` is a PseudoScript keyword.","text":"","module":"layout","href":"module/layout.html#layout-Message"},{"fqn":"layout::MessageItem","name":"MessageItem","kind":"data","summary":"","text":"","module":"layout","href":"module/layout.html#layout-MessageItem"},{"fqn":"layout::Metrics","name":"Metrics","kind":"data","summary":"Tunable spacing and font metrics for the sequence engine — head-card sizing,\nrow advance per message kind, fragment padding. Field detail is the engine's\nconcern; the defaults are the one source of truth consumers start from.","text":"","module":"layout","href":"module/layout.html#layout-Metrics"},{"fqn":"layout::MsgKind","name":"MsgKind","kind":"data","summary":"The kind of a sequence message: a call to another lifeline, a return to the\ncaller, or a self-message on the sender's own lifeline.","text":"","module":"layout","href":"module/layout.html#layout-MsgKind"},{"fqn":"layout::Participant","name":"Participant","kind":"data","summary":"One lifeline: its id (FQN), display label, C4 kind for the head card, optional\nsummary, and the ancestry path shown under the name for container/component\nlifelines.","text":"","module":"layout","href":"module/layout.html#layout-Participant"},{"fqn":"layout::PlacedFragment","name":"PlacedFragment","kind":"data","summary":"A placed fragment frame: its box, the operator-tab label (the first section's\nguard), and the dividers splitting later sections.","text":"","module":"layout","href":"module/layout.html#layout-PlacedFragment"},{"fqn":"layout::PlacedMessage","name":"PlacedMessage","kind":"data","summary":"A placed message: its kind, source/target ids, endpoint x-positions, row y,\ndirection (+1 left-to-right, -1 right-to-left), reading-order step, label, and\ndetail.","text":"","module":"layout","href":"module/layout.html#layout-PlacedMessage"},{"fqn":"layout::PlacedParticipant","name":"PlacedParticipant","kind":"data","summary":"A placed lifeline: its id, label, kind, ancestry path, wrapped summary lines,\nhead-card rectangle, the lifeline's centre x, and the y span of its dashed\nline.","text":"","module":"layout","href":"module/layout.html#layout-PlacedParticipant"},{"fqn":"layout::Point","name":"Point","kind":"data","summary":"A point in renderer coordinates.","text":"","module":"layout","href":"module/layout.html#layout-Point"},{"fqn":"layout::Rect","name":"Rect","kind":"data","summary":"An axis-aligned rectangle: its top-left corner and extent.","text":"","module":"layout","href":"module/layout.html#layout-Rect"},{"fqn":"layout::Section","name":"Section","kind":"data","summary":"One compartment of a fragment: its guard label (shown in the operator tab or\nbeside a divider) over a body of items.","text":"","module":"layout","href":"module/layout.html#layout-Section"},{"fqn":"layout::Sequence","name":"Sequence","kind":"component","summary":"The sequence engine: lays a structural `Diagram` out into a `SequenceLayout` —\nlifeline x-positions in first-appearance order, message rows stacked by\nevaluation order, and fragment frames sized to their bodies. The only engine\nimplemented in this crate; C4 placement is `dot`'s job (`emit` drives it), and\na flowchart engine would follow the same `Projection` contract.","text":"","module":"layout","href":"module/layout.html#layout-Sequence"},{"fqn":"layout::SequenceLayout","name":"SequenceLayout","kind":"data","summary":"A laid-out sequence diagram: the canvas size plus every participant, message,\nactivation bar, and fragment placed at absolute coordinates the renderer draws\nverbatim. The sequence engine's output.","text":"","module":"layout","href":"module/layout.html#layout-SequenceLayout"},{"fqn":"layout::Size","name":"Size","kind":"data","summary":"A width/height extent.","text":"","module":"layout","href":"module/layout.html#layout-Size"},{"fqn":"lsp","name":"lsp","kind":"module","summary":"","text":"","module":"lsp","href":"module/lsp.html"},{"fqn":"lsp::DefTarget","name":"DefTarget","kind":"data","summary":"A go-to-definition target straight from `lsp_core`: the FQN of the module the\ndefinition lives in, and its span within that module's source. The server maps\n`fqn` to a file URI — the target file, not the file the request came from.","text":"","module":"lsp","href":"module/lsp.html#lsp-DefTarget"},{"fqn":"lsp::Lsp","name":"Lsp","kind":"container","summary":"`crates/pseudoscript-lsp`. The stdio language server (tower-lsp): workspace\ndiagnostics on change, and hover, definition, completion, references, rename,\nsemantic tokens, symbols, folding, and formatting — every language answer\ndelegated to `lsp_core`. Owns the transport, the document store, and the\nURI↔FQN mapping, nothing more.","text":"","module":"lsp","href":"module/lsp.html#lsp-Lsp"},{"fqn":"lsp::Occurrence","name":"Occurrence","kind":"data","summary":"One resolved editor location: the file URI a target FQN maps back to, and the\nrange within it. The server's transport-level mapping of a `lsp_core` answer.","text":"","module":"lsp","href":"module/lsp.html#lsp-Occurrence"},{"fqn":"lsp::OpenDocument","name":"OpenDocument","kind":"data","summary":"One open editor buffer: its document URI, current source text, and the\neditor's version counter. The buffer overlays on-disk text until closed.","text":"","module":"lsp","href":"module/lsp.html#lsp-OpenDocument"},{"fqn":"lsp::Server","name":"Server","kind":"component","summary":"The tower-lsp `Backend`: owns the stdio transport, the document store, the\nserver lifecycle, and request routing. Holds no language logic — it locks the\nstore, hands the active text and the resolved workspace to a `lsp_core`\nhandler, and maps the result back to file locations.","text":"","module":"lsp","href":"module/lsp.html#lsp-Server"},{"fqn":"lsp::Store","name":"Store","kind":"component","summary":"The workspace document store: the server's own filesystem edge. Discovers the\nproject root (`pds.toml`, §8.1) by walking up from the opened URI, walks the\ntree for every visible `.pds` file, derives each module's FQN from its path,\nand overlays open buffers on disk text. Caches each parse and memoises the\nresolved `model::Workspace` — built from the local parses with the workspace's\ndirect-dependency externals bound (§8.3) — both invalidated on any edit. The\nexternals are cached separately and reload only on a dependency change.","text":"","module":"lsp","href":"module/lsp.html#lsp-Store"},{"fqn":"lsp_core","name":"lsp_core","kind":"module","summary":"","text":"","module":"lsp_core","href":"module/lsp_core.html"},{"fqn":"lsp_core::Analysis","name":"Analysis","kind":"component","summary":"Document-level analysis: parse-and-check diagnostics, Markdown hover for the\nsymbol under the caret (falling back to an inferred type), go-to-definition,\ninlay hints, and the canonical format edit. Resolution is delegated to\n`model`; this maps the result into LSP shapes.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Analysis"},{"fqn":"lsp_core::Complete","name":"Complete","kind":"component","summary":"Completion: the candidates offered at a byte offset, resolved across the\nworkspace and its externals. A `.`/`::` boundary drives member and path\ncompletion.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Complete"},{"fqn":"lsp_core::CompletionItem","name":"CompletionItem","kind":"data","summary":"One completion candidate: the inserted label, the integer LSP\n`CompletionItemKind`, and detail text.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-CompletionItem"},{"fqn":"lsp_core::Convert","name":"Convert","kind":"component","summary":"Position conversion: the single home for mapping between byte offsets and\nUTF-16 line/character positions, so every handler agrees on coordinates.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Convert"},{"fqn":"lsp_core::DefTarget","name":"DefTarget","kind":"data","summary":"A go-to-definition target: the FQN of the module the definition lives in and\nthe byte span of the definition within that module's source. The consuming\nedge maps `fqn` to a file URI and converts `span` to an editor range against\n*that* file's text — `lsp_core` returns the byte span, not a `Range`, because\nit does not hold the target module's source to convert against.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-DefTarget"},{"fqn":"lsp_core::Diagnostic","name":"Diagnostic","kind":"data","summary":"One diagnostic mapped to LSP shape: its range, severity, message, optional\ncode, and the code's article URL (`codeDescription`, the editor's clickable\nlink — empty when the code carries no article). Converted from the\nparser/checker's byte-span diagnostics; the code and its URL pass through\nunchanged.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Diagnostic"},{"fqn":"lsp_core::Hover","name":"Hover","kind":"data","summary":"Markdown hover content the editor renders in a tooltip.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Hover"},{"fqn":"lsp_core::LspCore","name":"LspCore","kind":"container","summary":"`crates/pseudoscript-lsp-core`. The single home for language intelligence as\ntransport-neutral handlers — the same logic the stdio server and the browser\nwasm both call.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-LspCore"},{"fqn":"lsp_core::Position","name":"Position","kind":"data","summary":"A zero-based caret position: line and UTF-16 character offset within the\nline. This and the shapes below model the standalone `lsp_types` vocabulary\nthe crate returns (pinned to the version tower-lsp re-exports, wasm-safe),\nso the server edge passes values through with no conversion.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Position"},{"fqn":"lsp_core::Range","name":"Range","kind":"data","summary":"A half-open text range between two positions.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Range"},{"fqn":"lsp_core::Refs","name":"Refs","kind":"component","summary":"References and rename: every occurrence of the symbol under the caret across\nthe workspace, the document highlights for it, and the workspace-wide rename\nedits.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Refs"},{"fqn":"lsp_core::Semantic","name":"Semantic","kind":"component","summary":"Semantic tokens: AST-aware highlighting that colours each token by its\ndeclared role, plus the legend the editor binds.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Semantic"},{"fqn":"lsp_core::Symbols","name":"Symbols","kind":"component","summary":"Document and workspace structure: the outline symbols, the foldable ranges,\nand the workspace-wide symbol query.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-Symbols"},{"fqn":"lsp_core::TextEdit","name":"TextEdit","kind":"data","summary":"A text edit: replace `range` with `newText`. Formatting and rename return\nthese.","text":"","module":"lsp_core","href":"module/lsp_core.html#lsp-core-TextEdit"},{"fqn":"model","name":"model","kind":"module","summary":"","text":"","module":"model","href":"module/model.html"},{"fqn":"model::Alt","name":"Alt","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Alt"},{"fqn":"model::ArchRule","name":"ArchRule","kind":"data","summary":"One architectural-principle lint rule (LANG.md §9 — the C4 facade/boundary\nprinciples). `code` is its stable identifier (`PDS-ARCH-001`); `slug` is the\nfilename of the article documenting it under the published principles base\nURL. Every firing is a `Warning` — a violation advises rather than\ninvalidates — so the rule carries no severity of its own.","text":"","module":"model","href":"module/model.html#model-ArchRule"},{"fqn":"model::Architecture","name":"Architecture","kind":"component","summary":"LANG.md §9 — architectural-principle lints over the resolved graph. Beyond §8.2\nvisibility (which a `public` component still satisfies), these advise on C4\nstructure: a cross-module call SHOULD reach a container/system's published face,\nnot an internal `component` (Facade/Gateway); the module dependency graph SHOULD\nstay acyclic; a cross-system call SHOULD couple to the other system's boundary,\nnot its containers. Each firing is a `Warning` stamped with the rule's `code`\nand `codeDescription` (its article URL); the model stays valid.","text":"","module":"model","href":"module/model.html#model-Architecture"},{"fqn":"model::Branch","name":"Branch","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Branch"},{"fqn":"model::Builder","name":"Builder","kind":"component","summary":"Projects the parsed, resolved workspace into the architecture graph: structural\nnodes plus edges from `for` parents, body calls, triggers, and `from`\nprovenance, with a sequence trace recorded per disclosed body. A pure\nprojection — no I/O — so the emit crate and a future salsa/LSP layer can both\nadopt it.","text":"","module":"model","href":"module/model.html#model-Builder"},{"fqn":"model::Call","name":"Call","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Call"},{"fqn":"model::CaretResolve","name":"CaretResolve","kind":"component","summary":"Caret resolution — the engine every cursor feature shares (`lsp_core` hover,\ndefinition, references, rename; the IDE's symbol actions). Tokenizes the\nactive source, finds the identifier under the offset, and resolves it across\nthe workspace and its externals: a node or data FQN, a same-node call or member access,\nor a local reference. Token-level, so it survives a partial parse.","text":"","module":"model","href":"module/model.html#model-CaretResolve"},{"fqn":"model::Checks","name":"Checks","kind":"component","summary":"Static analysis (LANG.md §2.2, §2.3, §2.4, §3.3, §3.4, §3.5, §4, §5.1, §5.2, §6,\n§7, §8): builds the symbol tables, then runs the well-formedness checks. Every\nentry returns a `syntax::Diagnostic[]`; well-formed input yields an empty one.\nChecks are conservative — each rule fires only when the violation is certain.","text":"","module":"model","href":"module/model.html#model-Checks"},{"fqn":"model::Commit","name":"Commit","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Commit"},{"fqn":"model::Completion","name":"Completion","kind":"component","summary":"Context-aware completion (the model-native producer `lsp_core` maps to LSP\nitems): candidates at a caret offset resolved across the workspace and its\nexternals — members after `.`, a module's public symbols after `::`, the\nbuilt-in macros after `#[`, types in type position, else keywords and symbols.","text":"","module":"model","href":"module/model.html#model-Completion"},{"fqn":"model::ConstantType","name":"ConstantType","kind":"data","summary":"One `constant`'s FQN paired with its declared primitive type name (§3.6,\nADR-039), so inference resolves a `module::NAME` reference to its type.","text":"","module":"model","href":"module/model.html#model-ConstantType"},{"fqn":"model::CrossModule","name":"CrossModule","kind":"component","summary":"§8.2 — cross-module visibility resolution. Walks each module's qualified\nreferences — `for` parents, type annotations (field, parameter, return, and\ngeneric-argument types), feature targets, body call targets, value-position\nunion-variant references, and the member named after a call target's FQN — and\nagainst the global FQN index enforces: a reference from module A to a node in\nmodule B resolves only if that node is `public`. A private target or a dangling\ncross-module FQN is a diagnostic. Same-module references are the single-module\nchecks' business and are skipped here.","text":"","module":"model","href":"module/model.html#model-CrossModule"},{"fqn":"model::DataField","name":"DataField","kind":"data","summary":"One field of a `data` record or union-variant record: its name and rendered\ntype.","text":"","module":"model","href":"module/model.html#model-DataField"},{"fqn":"model::DataShape","name":"DataShape","kind":"data","summary":"The disclosed shape of a `data` node (§3.4, §3.5): a record of fields, a\ndiscriminated union of variants, or an undisclosed black box. Carried on a\n`data` `GraphNode` so the entity (ER) view draws the rows without re-reading\nsource.","text":"","module":"model","href":"module/model.html#model-DataShape"},{"fqn":"model::DataVariant","name":"DataVariant","kind":"data","summary":"One variant of a `data` union (§3.5): its name and, for a record variant, its\nfields.","text":"","module":"model","href":"module/model.html#model-DataVariant"},{"fqn":"model::DepError","name":"DepError","kind":"data","summary":"Why parsing a manifest's `[dependencies]` table or a `pds.lock` failed: the\noffending entry and a human-readable message. Malformed TOML, an unknown key,\nor more than one of tag/rev/branch on one dependency (§8.3).","text":"","module":"model","href":"module/model.html#model-DepError"},{"fqn":"model::DepFile","name":"DepFile","kind":"data","summary":"One dependency module's in-memory form: its FQN *within* the dependency\nworkspace (path relative to that workspace root) and its source. The resolver\nprefixes it with the dependency name.","text":"","module":"model","href":"module/model.html#model-DepFile"},{"fqn":"model::DepSpec","name":"DepSpec","kind":"data","summary":"A `[dependencies]` entry as written in `pds.toml` (§8.3): a git URL with at\nmost one of tag/rev/branch and an in-repo sub-path, or a local sibling path.\nEmpty strings stand for the absent options.","text":"","module":"model","href":"module/model.html#model-DepSpec"},{"fqn":"model::Deps","name":"Deps","kind":"component","summary":"§8.3 — the dependency resolver. Parses a manifest's `[dependencies]` and a\n`pds.lock`, then maps each direct dependency to its dependency-name-prefixed\nmodules — the externals the checker binds for cross-workspace resolution. The\nsingle source of the slug→name→`dep::module` mapping, shared by the native\nloader (`project`) and the WASM bridge (`ide`). Pure: fetching and disk are\nthe caller's job.","text":"","module":"model","href":"module/model.html#model-Deps"},{"fqn":"model::Edge","name":"Edge","kind":"data","summary":"A typed, directed relationship between two graph node FQNs. `source` is the\nwire field `from` — `from` is a PseudoScript keyword, so the model renames\nit. Either endpoint may name a node not present as a `GraphNode` — a\nsynthesised trigger initiator, or a target that does not resolve. `label` is\nthe method name for `Call`, otherwise empty. `span` is the source range that\nproduced the edge — the call site for a `Call` — so an architectural lint can\npoint its diagnostic at the offending call; the empty span when the edge is\nsynthesised.","text":"","module":"model","href":"module/model.html#model-Edge"},{"fqn":"model::EdgeKind","name":"EdgeKind","kind":"data","summary":"What relationship a graph edge expresses (LANG.md §9.1): a `for` parent link,\na body call (label = method), a synthesised trigger initiator edge, or a\n`from` provenance link.","text":"","module":"model","href":"module/model.html#model-EdgeKind"},{"fqn":"model::FeatureCheck","name":"FeatureCheck","kind":"component","summary":"§5.2 / §8.1 — feature checks. A feature's `for` target MUST resolve to a node,\nnot a type or module; a target this single-module model cannot see is left to\ncross-module resolution. Feature names occupy their own module namespace, so a\nrepeated name is a collision (checked once per module).","text":"","module":"model","href":"module/model.html#model-FeatureCheck"},{"fqn":"model::FieldlessVariants","name":"FieldlessVariants","kind":"data","summary":"A union's fieldless variant names, keyed by the union's bare name (§3.5).\nFieldless variants do not hoist a symbol — they are addressed\n`module::Union::Variant` (ADR-032) — so they index under their union.","text":"","module":"model","href":"module/model.html#model-FieldlessVariants"},{"fqn":"model::Folding","name":"Folding","kind":"component","summary":"The foldable regions of a module: every multi-line disclosed declaration and\nstatement block, and doc-comment runs.","text":"","module":"model","href":"module/model.html#model-Folding"},{"fqn":"model::Git","name":"Git","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Git"},{"fqn":"model::Graph","name":"Graph","kind":"data","summary":"The resolved architecture graph: every node and typed edge across all C4\nlevels, plus feature scenarios. The single source every view projects from —\nenough to extract any view without re-reading source. Per-callable sequence\ntraces (`Step[]`) are held alongside, keyed by the owning callable's FQN.","text":"","module":"model","href":"module/model.html#model-Graph"},{"fqn":"model::GraphNode","name":"GraphNode","kind":"data","summary":"One node in the resolved graph — a structural declaration, a `data` type, or a\ncallable. Carries its FQN, simple name, kind, enclosing-node FQN (`for` parent,\nowning node, or empty at top level), declaring module, visibility, name span,\nany trigger macros (callables only), the call signature (callables only), the\ndisclosed shape (`data` only), and lifted `///` documentation.","text":"","module":"model","href":"module/model.html#model-GraphNode"},{"fqn":"model::Inference","name":"Inference","kind":"component","summary":"Local-binding type inference, for inlay hints and hover. Assignments are\nuntyped (`x = expr`); this infers each binding's type from its right-hand side\n— a call's return type, a field access, a `from` expression, or a literal.\nBest-effort: an un-typeable expression yields nothing rather than a guess.","text":"","module":"model","href":"module/model.html#model-Inference"},{"fqn":"model::Local","name":"Local","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Local"},{"fqn":"model::Lock","name":"Lock","kind":"data","summary":"A parsed `pds.lock` (§8.4): the pinned dependency graph. A black box here — its\nshape is the lockfile's concern, not the model's.","text":"","module":"model","href":"module/model.html#model-Lock"},{"fqn":"model::Loop","name":"Loop","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Loop"},{"fqn":"model::MacroTargeting","name":"MacroTargeting","kind":"component","summary":"§2.4 / ADR-015 — macro targeting. Every built-in macro (`onevent`, `schedule`,\n`http`, `manual`) targets callables; on any structural declaration it is a\nwrong-target error, and an unrecognised macro is unknown. An `#[onevent(E)]`\nhandler MUST have exactly one parameter whose type matches the event type.","text":"","module":"model","href":"module/model.html#model-MacroTargeting"},{"fqn":"model::Member","name":"Member","kind":"data","summary":"A member reachable through `.` from a node or `data` owner: its name, kind,\ndefinition span, one-line signature detail (`run(name: string): uuid`), value\ntype (a field's type or a callable's return type), the ordered parameter\ntypes (empty for a field — their count is the call arity, ADR-022), the\n`///` summary (absent for a field — the grammar has no field doc), and\nwhether it is `public` (callables only; gates cross-module member completion,\n§8.2). Powers member completion, arity checks, and go-to-definition.","text":"","module":"model","href":"module/model.html#model-Member"},{"fqn":"model::MemberKind","name":"MemberKind","kind":"data","summary":"What kind of member is reachable through `.` from an owner: a node's callable\n(§5.1) or a `data` record field (§3.4).","text":"","module":"model","href":"module/model.html#model-MemberKind"},{"fqn":"model::Model","name":"Model","kind":"container","summary":"`crates/pseudoscript-model`. AST to one resolved graph; static checks\n(resolution, visibility, Result flow, return coverage); the §8.3 dependency\nresolver; and the LSP analysis primitives (completion, folding, semantic\ntokens, inference). WASM-safe.","text":"","module":"model","href":"module/model.html#model-Model"},{"fqn":"model::ModuleDiagnostics","name":"ModuleDiagnostics","kind":"data","summary":"One workspace module's diagnostics, attributed to its FQN. Every span lies in\nthat module's own source, so a tool with the FQN-to-file mapping publishes\neach list against the right file.","text":"","module":"model","href":"module/model.html#model-ModuleDiagnostics"},{"fqn":"model::ModuleEntry","name":"ModuleEntry","kind":"data","summary":"One module's parsed AST, resolved symbol table, and FQN, as held by a\n`Workspace`.","text":"","module":"model","href":"module/model.html#model-ModuleEntry"},{"fqn":"model::NodeDoc","name":"NodeDoc","kind":"data","summary":"A node's documentation, lifted from its `///` block (LANG.md §2.1): the\nsummary (compact-diagram text), the extended description (tooltip text), and\nits tags (`\n`, including the `#`), in source order.","text":"","module":"model","href":"module/model.html#model-NodeDoc"},{"fqn":"model::NodeKind","name":"NodeKind","kind":"data","summary":"What kind of model element a `GraphNode` is. Adds `Callable` to the structural\nkinds: structural declarations, `data` types (including hoisted union\nvariants), and callables all become nodes.","text":"","module":"model","href":"module/model.html#model-NodeKind"},{"fqn":"model::OnEvent","name":"OnEvent","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-OnEvent"},{"fqn":"model::PackageId","name":"PackageId","kind":"data","summary":"A content-addressable package identity: its source, resolved revision, and\nin-repo path. Drives the `pds_modules/` slug a git package materialises into.","text":"","module":"model","href":"module/model.html#model-PackageId"},{"fqn":"model::ParentKind","name":"ParentKind","kind":"component","summary":"§4 / ADR-010 — C4 parent-kind well-formedness. A `for` parent links a node to\nits enclosing level, and the levels MUST nest: a `container` parents a\n`system`, a `component` parents a `container`. A parent of the wrong kind is a\ndiagnostic (`container `X` parent `Y` is not a system`). A bare parent that\nnames no same-module node is unresolved (§8.1); a qualified parent this\nsingle-module model cannot see is left to cross-module resolution.","text":"","module":"model","href":"module/model.html#model-ParentKind"},{"fqn":"model::Private","name":"Private","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Private"},{"fqn":"model::Public","name":"Public","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Public"},{"fqn":"model::Record","name":"Record","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Record"},{"fqn":"model::ReservedNames","name":"ReservedNames","kind":"component","summary":"§2.3 / ADR-012 — reserved-word identifiers. A declared identifier MUST NOT be a\nreserved word: a keyword, a primitive type name, or `Result`/`Option`. Covers\nevery name a module declares — `data`, node, callable, field, parameter,\nvariant, and feature names — so `data string` or a parameter named `Result` is\na diagnostic.","text":"","module":"model","href":"module/model.html#model-ReservedNames"},{"fqn":"model::Resolution","name":"Resolution","kind":"data","summary":"The outcome of resolving a cross-module reference (LANG.md §8.2): the FQN\nnames a public symbol, a private one reached from another module, or nothing.","text":"","module":"model","href":"module/model.html#model-Resolution"},{"fqn":"model::ResolveHit","name":"ResolveHit","kind":"data","summary":"One resolved caret hit: the clicked identifier's span, the defining module\nand the definition's span there, the resolved graph FQN (a member resolves to\n`owner::member` — the key `emit`'s symbol projection uses), a Markdown title\nline, and the doc summary or signature detail shown under it.","text":"","module":"model","href":"module/model.html#model-ResolveHit"},{"fqn":"model::Resolver","name":"Resolver","kind":"component","summary":"Resolves a module's declarations into a symbol table (LANG.md §8). FQNs are\nfile-derived: a single `.pds` file is one module, named by its `//!` path.\nHolds two namespaces — type names and node names share the symbol table, while\nfeature names live in a separate namespace the `Checks` enforce — plus the\nmember, fieldless-variant, and constant-type indexes. The LSP reuses the\ntable for hover and go-to-definition.","text":"","module":"model","href":"module/model.html#model-Resolver"},{"fqn":"model::ResultFlow","name":"ResultFlow","kind":"component","summary":"§6 — flow-sensitive `Result` / `Option` typestate. Each binding carries a\nstate (`Unknown`, `Ok`/`Err` for a `Result`, `Some`/`None` for an `Option`),\nnarrowed on entering the branches of `if (r.isErr)` / `if (r.isOk)` /\n`if (o.isNone)` / `if (o.isSome)`. Reading `.value` while known-`Err` or\nknown-`None`, or `.error` while known-`Ok`, is a model error. The env is\ncloned per branch so narrowing does not leak; an `if` whose guarded arm\ndiverges narrows the fall-through to the inverse state (`Ok`<->`Err`,\n`Some`<->`None`).","text":"","module":"model","href":"module/model.html#model-ResultFlow"},{"fqn":"model::Return","name":"Return","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Return"},{"fqn":"model::ReturnCoverage","name":"ReturnCoverage","kind":"component","summary":"ADR-016 — return coverage. A disclosed non-void callable MUST return on every\npath. A `return` makes a block diverge; an `if`/`else` diverges only when both\narms do; `for`/`while` bodies and bare expressions never guarantee a return.\nAn explicit `void` and a parse-recovered missing return type (ADR-040) both\nmean void.","text":"","module":"model","href":"module/model.html#model-ReturnCoverage"},{"fqn":"model::Rev","name":"Rev","kind":"data","summary":"A resolved revision selector — at most one of these (§8.3); `Default` is the\nremote's default-branch HEAD.","text":"","module":"model","href":"module/model.html#model-Rev"},{"fqn":"model::Scenario","name":"Scenario","kind":"data","summary":"A `feature` BDD scenario attached to its target node (LANG.md §5.2, §9.3):\nits name, the canonicalised target FQN (as written if it does not resolve),\nthe declaring module's FQN, the span of the feature name (a host's\ngo-to-definition target), lifted `///` documentation, and the ordered\ngiven/when/then steps. Steps are prose, not resolved, so a scenario adds no\nedges; it renders as a card on its target node's doc page.","text":"","module":"model","href":"module/model.html#model-Scenario"},{"fqn":"model::ScenarioStep","name":"ScenarioStep","kind":"data","summary":"One step of a `Scenario` (LANG.md §5.2): a keyword (`given`/`when`/`then`/\n`and`/`but`) and its prose, with the string literal's quotes stripped.","text":"","module":"model","href":"module/model.html#model-ScenarioStep"},{"fqn":"model::SelfCall","name":"SelfCall","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-SelfCall"},{"fqn":"model::Semantic","name":"Semantic","kind":"component","summary":"AST-aware semantic colouring: each identifier classified by its declared role\n(node→namespace, `data`→class, callable→method, parameter, call segment), the\nspans `lsp_core` delta-encodes for the editor.","text":"","module":"model","href":"module/model.html#model-Semantic"},{"fqn":"model::SigParam","name":"SigParam","kind":"data","summary":"One parameter of a callable signature: its name and rendered type.","text":"","module":"model","href":"module/model.html#model-SigParam"},{"fqn":"model::Signature","name":"Signature","kind":"data","summary":"A callable node's signature (§5.1): its ordered parameters and rendered return\ntype. Carried on a callable `GraphNode` so the sequence and symbol views can\nshow the call shape without re-reading source.","text":"","module":"model","href":"module/model.html#model-Signature"},{"fqn":"model::Source","name":"Source","kind":"data","summary":"The resolved source of a dependency (ADR-026): a fetched git repository at a\nrevision and in-repo sub-path, or a local sibling workspace read from disk.","text":"","module":"model","href":"module/model.html#model-Source"},{"fqn":"model::Step","name":"Step","kind":"data","summary":"One step in a callable's ordered sequence trace (LANG.md §7, §9.2). Calls in a\nchained expression are emitted left-to-right; control flow becomes `Alt`/\n`Loop` frames whose bodies are nested step lists, in §7 evaluation order.","text":"","module":"model","href":"module/model.html#model-Step"},{"fqn":"model::Symbol","name":"Symbol","kind":"data","summary":"One declared, addressable node in a module namespace: the bare name, its FQN\n(`module::Ledger`), its declaration kind, whether it is `public` (§8.2), and\nthe span of its name.","text":"","module":"model","href":"module/model.html#model-Symbol"},{"fqn":"model::SymbolKind","name":"SymbolKind","kind":"data","summary":"The lowercase declaration keyword a `Symbol` names. Drives the parent-kind and\nmacro-target diagnostics, which embed it as prose. `Constant` is a top-level\nprimitive value (§3.6, ADR-039).","text":"","module":"model","href":"module/model.html#model-SymbolKind"},{"fqn":"model::SymbolTable","name":"SymbolTable","kind":"data","summary":"One module's resolved symbol table (LANG.md §8): its FQN plus the declared\nnodes, the members reachable through `.`, the fieldless-variant index\n(ADR-032), and the constant-type index (ADR-039). The `Resolver` produces it;\nthe `Workspace` indexes and the `Checks` consume it.","text":"","module":"model","href":"module/model.html#model-SymbolTable"},{"fqn":"model::Tag","name":"Tag","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Tag"},{"fqn":"model::Trigger","name":"Trigger","kind":"data","summary":"A trigger macro on a callable (LANG.md §2.4): the macro that makes the\ncallable a diagram entry point and synthesises an inbound initiator edge. Each\nvariant maps to a synthesised initiator node (`event:`, `scheduler`,\n`client`, `caller`).","text":"","module":"model","href":"module/model.html#model-Trigger"},{"fqn":"model::TypeRefs","name":"TypeRefs","kind":"component","summary":"§3.3 / §8.1 — type-reference resolution. Every named type in a declaration — a\nfield, parameter, or return type, and each generic argument — MUST resolve. A\nbare name that resolves to nothing is reported as unresolved; a name that\nresolves to a declared type or node MUST be written by its full FQN\n(`module::Name`), so an unqualified declared-type annotation is rejected.\nBuilt-in types (the primitives, `Result`, `Option`) stay bare.","text":"","module":"model","href":"module/model.html#model-TypeRefs"},{"fqn":"model::Union","name":"Union","kind":"data","summary":"","text":"","module":"model","href":"module/model.html#model-Union"},{"fqn":"model::VariantCollision","name":"VariantCollision","kind":"component","summary":"§3.5 / ADR-006 — union variant collision. A top-level `data Name` followed by\nan inline variant `| Name { ... }` hoisting the same name is a collision. Bare\nvariants reference existing data and never collide.","text":"","module":"model","href":"module/model.html#model-VariantCollision"},{"fqn":"model::Visibility","name":"Visibility","kind":"data","summary":"Whether a node is `public` (cross-module addressable) or module-private\n(LANG.md §8.2).","text":"","module":"model","href":"module/model.html#model-Visibility"},{"fqn":"model::Workspace","name":"Workspace","kind":"component","summary":"The resolved set of modules keyed by FQN (LANG.md §8). Built once per\nworkspace from `(fqn, parsed module)` pairs; owns the per-module models and a\nglobal FQN-to-`Symbol` index for cross-module resolution.","text":"","module":"model","href":"module/model.html#model-Workspace"},{"fqn":"model::WorkspaceModule","name":"WorkspaceModule","kind":"data","summary":"One `.pds` module of a workspace: its caller-supplied FQN (the loader derives\nit from the file path relative to `pds.toml`, LANG.md §8.1) and its source\ntext. This crate stays pure over in-memory modules; it never touches the\nfilesystem.","text":"","module":"model","href":"module/model.html#model-WorkspaceModule"},{"fqn":"project","name":"project","kind":"module","summary":"","text":"","module":"project","href":"module/project.html"},{"fqn":"project::Deps","name":"Deps","kind":"component","summary":"Resolves a workspace's declared dependencies into modules the checker binds as\nexternals (§8.3): reads `pds.lock` + `pds_modules/`, materialises each direct\ndependency's `.pds` modules under its dependency-name prefix, and resolves a\nlocal path dependency's directory.","text":"","module":"project","href":"module/project.html#project-Deps"},{"fqn":"project::IoError","name":"IoError","kind":"data","summary":"A filesystem failure surfaced to the caller: the path or manifest that could\nnot be read, with a human-readable message.","text":"","module":"project","href":"module/project.html#project-IoError"},{"fqn":"project::Loader","name":"Loader","kind":"component","summary":"Resolves and reads a workspace off disk: finds the root by walking up to the\nnearest `pds.toml`, then walks the tree for visible `.pds` files and loads each\nas a `(fqn, source)` module — mapping its path to a flat module FQN (§8.1) and\nskipping `target/` and `pds_modules/`.","text":"","module":"project","href":"module/project.html#project-Loader"},{"fqn":"project::Project","name":"Project","kind":"container","summary":"`crates/pseudoscript-project`. The disk-facing loader: resolves the workspace\nroot, reads its `.pds` modules, and materialises dependency modules from the\nvendor directory. The only place the native toolchain touches the filesystem\nfor project loading.","text":"","module":"project","href":"module/project.html#project-Project"},{"fqn":"syntax","name":"syntax","kind":"module","summary":"","text":"","module":"syntax","href":"module/syntax.html"},{"fqn":"syntax::Assign","name":"Assign","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Assign"},{"fqn":"syntax::BinOp","name":"BinOp","kind":"data","summary":"A binary operator (§7.5): arithmetic, comparison, equality, and boolean.","text":"","module":"syntax","href":"module/syntax.html#syntax-BinOp"},{"fqn":"syntax::Binary","name":"Binary","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Binary"},{"fqn":"syntax::BlankLines","name":"BlankLines","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-BlankLines"},{"fqn":"syntax::Block","name":"Block","kind":"data","summary":"A `{ ... }` statement block (§7): statements in order, plus the brace span.","text":"","module":"syntax","href":"module/syntax.html#syntax-Block"},{"fqn":"syntax::BlockComment","name":"BlockComment","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-BlockComment"},{"fqn":"syntax::BodyMember","name":"BodyMember","kind":"data","summary":"A member of a disclosed node body: a callable, or — under error recovery —\na nested structural declaration the parser flags as illegal (ADR-011).","text":"","module":"syntax","href":"module/syntax.html#syntax-BodyMember"},{"fqn":"syntax::BoolLit","name":"BoolLit","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-BoolLit"},{"fqn":"syntax::CallableDecl","name":"CallableDecl","kind":"data","summary":"A callable (implicit operation) declared inside a disclosed node (§5.1).\n`returnTy` is required (ADR-040) — a missing return type is a syntax error,\nrecovered as `void`; `body` absent means a black box (`;`).","text":"","module":"syntax","href":"module/syntax.html#syntax-CallableDecl"},{"fqn":"syntax::CallableMember","name":"CallableMember","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-CallableMember"},{"fqn":"syntax::Compose","name":"Compose","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Compose"},{"fqn":"syntax::Constant","name":"Constant","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Constant"},{"fqn":"syntax::ConstantDecl","name":"ConstantDecl","kind":"data","summary":"A `constant NAME = Literal` declaration (§3.6, ADR-039): a top-level primitive\nvalue. `public` lives on the enclosing `Declaration`, mirroring `data`.","text":"","module":"syntax","href":"module/syntax.html#syntax-ConstantDecl"},{"fqn":"syntax::Convert","name":"Convert","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Convert"},{"fqn":"syntax::Data","name":"Data","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Data"},{"fqn":"syntax::DataBody","name":"DataBody","kind":"data","summary":"The three `data` forms. A bare `| Name` variant is an enum case; a\n`| Name { .. }` variant carries an inline record (§3.5, ADR-006).","text":"","module":"syntax","href":"module/syntax.html#syntax-DataBody"},{"fqn":"syntax::DataDecl","name":"DataDecl","kind":"data","summary":"A `data` declaration: a record, a union, or a black box (§3.4, §3.5).","text":"","module":"syntax","href":"module/syntax.html#syntax-DataDecl"},{"fqn":"syntax::DeclItem","name":"DeclItem","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-DeclItem"},{"fqn":"syntax::DeclKind","name":"DeclKind","kind":"data","summary":"The structural payload of a declaration: a node (person/system/container/\ncomponent), a `data` type, or a top-level `constant` (§3.6, ADR-039).","text":"","module":"syntax","href":"module/syntax.html#syntax-DeclKind"},{"fqn":"syntax::DeclMember","name":"DeclMember","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-DeclMember"},{"fqn":"syntax::Declaration","name":"Declaration","kind":"data","summary":"A documented, annotated structural declaration (§4): the shared\n`doc → macros → public` prefix plus the structural payload.","text":"","module":"syntax","href":"module/syntax.html#syntax-Declaration"},{"fqn":"syntax::Diagnostic","name":"Diagnostic","kind":"data","summary":"One message about a span of source. The single diagnostic type every crate\nemits, so a driver collects lexer, parser, and checker output into one\nordered list. `code` is an optional stable identifier (e.g. `E0001`) for\ntooling; the human-facing text is `message`. `codeDescription` is an optional\nURL the code resolves to — an article explaining the rule — which the LSP\nsurfaces as the diagnostic's clickable link. Empty when the code carries no\narticle.","text":"","module":"syntax","href":"module/syntax.html#syntax-Diagnostic"},{"fqn":"syntax::DocBlock","name":"DocBlock","kind":"data","summary":"A `///` doc block split into summary and extended on the first blank `///`\nline (ADR-009): summary feeds compact diagrams, extended feeds tooltips,\ntags carry the `\n` markers.","text":"","module":"syntax","href":"module/syntax.html#syntax-DocBlock"},{"fqn":"syntax::Expr","name":"Expr","kind":"data","summary":"An expression (§7, §10): its form and source span.","text":"","module":"syntax","href":"module/syntax.html#syntax-Expr"},{"fqn":"syntax::ExprKind","name":"ExprKind","kind":"data","summary":"The expression forms (§10). `Marker` is a built-in generic constructor —\n`Ok`/`Err` (`Result`) or `Some`/`None` (`Option`); `From` names a target\ntype and its `source`, whose `FromSource` form is the §7.2 vs §7.4 split;\n`Postfix` is a `.name`/`.name(..)` access chain (ADR-007); `OwnCall` is a\nbare same-node call `Name(args)` (§5.1, ADR-041); `Unary`/`Binary` are the\n§7.5 operator forms. A `Marker` or `From` head is never a binary operand (§7.5).","text":"","module":"syntax","href":"module/syntax.html#syntax-ExprKind"},{"fqn":"syntax::ExprStmt","name":"ExprStmt","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-ExprStmt"},{"fqn":"syntax::FeatureDecl","name":"FeatureDecl","kind":"data","summary":"A `feature Name for Path { given* when+ then+ }` BDD scenario (§5.2). Steps\nare prose, not resolved against the model; the strict given→when→then order\nis enforced by the parser. A feature takes no macros and no `public`.","text":"","module":"syntax","href":"module/syntax.html#syntax-FeatureDecl"},{"fqn":"syntax::FeatureItem","name":"FeatureItem","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-FeatureItem"},{"fqn":"syntax::FeatureStep","name":"FeatureStep","kind":"data","summary":"One step line in a feature flow: a step keyword and its prose string (§5.2).","text":"","module":"syntax","href":"module/syntax.html#syntax-FeatureStep"},{"fqn":"syntax::Field","name":"Field","kind":"data","summary":"A record field `name: Type`.","text":"","module":"syntax","href":"module/syntax.html#syntax-Field"},{"fqn":"syntax::For","name":"For","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-For"},{"fqn":"syntax::From","name":"From","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-From"},{"fqn":"syntax::FromSource","name":"FromSource","kind":"data","summary":"The source of a `from` expression (§7.2, §7.4, ADR-035) — the parser\nbranches on the leading token, so the two forms are distinct: a `{`\nopens `Compose`, a set the target `data` record/variant is built from;\nanything else is `Convert`, carrying the target type onto one value.","text":"","module":"syntax","href":"module/syntax.html#syntax-FromSource"},{"fqn":"syntax::If","name":"If","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-If"},{"fqn":"syntax::InnerDoc","name":"InnerDoc","kind":"data","summary":"One `//!` inner-doc line documenting the module (§2.1).","text":"","module":"syntax","href":"module/syntax.html#syntax-InnerDoc"},{"fqn":"syntax::Item","name":"Item","kind":"data","summary":"A top-level item: a documented structural declaration, or a `feature` BDD\nscenario.","text":"","module":"syntax","href":"module/syntax.html#syntax-Item"},{"fqn":"syntax::Lexed","name":"Lexed","kind":"data","summary":"The full result of lexing: the conformance token stream plus interleaved\ntrivia for the formatter.","text":"","module":"syntax","href":"module/syntax.html#syntax-Lexed"},{"fqn":"syntax::Lexer","name":"Lexer","kind":"component","summary":"Hand-written lexer for LANG.md §2. One pass yields the conformance token\nstream (comments discarded, `///`→`Doc`+`Tag`, `//!`→`InnerDoc`) and\nfull-fidelity trivia for the formatter. Infallible: lexical anomalies surface\nas parser diagnostics, never a failed lex.","text":"","module":"syntax","href":"module/syntax.html#syntax-Lexer"},{"fqn":"syntax::LineComment","name":"LineComment","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-LineComment"},{"fqn":"syntax::LineIndex","name":"LineIndex","kind":"component","summary":"Precomputed newline offsets turning a byte offset into a 1-based (line, col)\npair in O(log n). Built once per source and reused for every lookup; column\ncounts bytes from the line start.","text":"","module":"syntax","href":"module/syntax.html#syntax-LineIndex"},{"fqn":"syntax::List","name":"List","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-List"},{"fqn":"syntax::Lit","name":"Lit","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Lit"},{"fqn":"syntax::Literal","name":"Literal","kind":"data","summary":"A literal value (ADR-013). `raw` carries the source text (string literals\nkeep their quotes).","text":"","module":"syntax","href":"module/syntax.html#syntax-Literal"},{"fqn":"syntax::LiteralArg","name":"LiteralArg","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-LiteralArg"},{"fqn":"syntax::Macro","name":"Macro","kind":"data","summary":"A `#[..]` macro (outer attribute) on a callable (§2.4): its name path and\nargument form.","text":"","module":"syntax","href":"module/syntax.html#syntax-Macro"},{"fqn":"syntax::MacroArg","name":"MacroArg","kind":"data","summary":"One argument inside a macro's `( .. )` list (§10 `MetaArg`).","text":"","module":"syntax","href":"module/syntax.html#syntax-MacroArg"},{"fqn":"syntax::MacroArgs","name":"MacroArgs","kind":"data","summary":"The three macro argument forms (§2.4): `#[manual]` word, `#[onevent(Path)]`/\n`#[http(\"..\")]` list, `#[schedule = \"..]` name=value.","text":"","module":"syntax","href":"module/syntax.html#syntax-MacroArgs"},{"fqn":"syntax::Marker","name":"Marker","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Marker"},{"fqn":"syntax::MarkerKind","name":"MarkerKind","kind":"data","summary":"The built-in generic constructor a `Marker` names (§6, ADR-019). The\nvariants mirror the four marker keywords (their names carry the `Kw` prefix\nbecause `Ok`/`Err`/`Some`/`None` are reserved words): `KwOk`/`KwErr` build a\n`Result`, `KwSome`/`KwNone` build an `Option`.","text":"","module":"syntax","href":"module/syntax.html#syntax-MarkerKind"},{"fqn":"syntax::Module","name":"Module","kind":"data","summary":"The typed syntax tree of one module (LANG.md §10): module-level inner docs,\nthen declarations and features in source order. Every node carries a `Span`;\ndeclarations and statements also carry leading trivia so the formatter can\nreproduce layout.","text":"","module":"syntax","href":"module/syntax.html#syntax-Module"},{"fqn":"syntax::NameValue","name":"NameValue","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-NameValue"},{"fqn":"syntax::NestedArg","name":"NestedArg","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-NestedArg"},{"fqn":"syntax::Node","name":"Node","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Node"},{"fqn":"syntax::NodeDecl","name":"NodeDecl","kind":"data","summary":"A `person` / `system` / `container` / `component` declaration. `parent` is\nthe `for ` path, present for container/component. `body` is the\ndisclosed member list, or absent for a black box (`;`).","text":"","module":"syntax","href":"module/syntax.html#syntax-NodeDecl"},{"fqn":"syntax::NodeKind","name":"NodeKind","kind":"data","summary":"The structural keyword class of a node.","text":"","module":"syntax","href":"module/syntax.html#syntax-NodeKind"},{"fqn":"syntax::NumberLit","name":"NumberLit","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-NumberLit"},{"fqn":"syntax::OwnCall","name":"OwnCall","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-OwnCall"},{"fqn":"syntax::Param","name":"Param","kind":"data","summary":"A callable parameter `name: Type`.","text":"","module":"syntax","href":"module/syntax.html#syntax-Param"},{"fqn":"syntax::Paren","name":"Paren","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Paren"},{"fqn":"syntax::Parsed","name":"Parsed","kind":"data","summary":"Parser output: the tree (always present, recovered on error) plus the\ndiagnostics collected while parsing. Parsing never fails outright.","text":"","module":"syntax","href":"module/syntax.html#syntax-Parsed"},{"fqn":"syntax::Parser","name":"Parser","kind":"component","summary":"Recursive-descent parser for the §10 grammar with error recovery: on\nunexpected input it records a `Diagnostic`, resynchronises to the next\nstatement/declaration boundary, and continues. Always yields a (possibly\npartial) `Module`; never panics.","text":"","module":"syntax","href":"module/syntax.html#syntax-Parser"},{"fqn":"syntax::Path","name":"Path","kind":"data","summary":"A `::`-separated path of identifiers (§2.2, §10 `Path`); never empty.","text":"","module":"syntax","href":"module/syntax.html#syntax-Path"},{"fqn":"syntax::PathArg","name":"PathArg","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-PathArg"},{"fqn":"syntax::PathRef","name":"PathRef","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-PathRef"},{"fqn":"syntax::Postfix","name":"Postfix","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Postfix"},{"fqn":"syntax::PostfixSeg","name":"PostfixSeg","kind":"data","summary":"One `.name` or `.name(args)` step in a postfix chain (ADR-007). `callArgs`\npresent marks a call; absent marks field access.","text":"","module":"syntax","href":"module/syntax.html#syntax-PostfixSeg"},{"fqn":"syntax::Record","name":"Record","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Record"},{"fqn":"syntax::Ref","name":"Ref","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Ref"},{"fqn":"syntax::Reference","name":"Reference","kind":"data","summary":"A reference primary (§10 `Ref`): an FQN. `self` was dropped (ADR-041); a\nsame-node call is the bare `OwnCall` primary, not a reference.","text":"","module":"syntax","href":"module/syntax.html#syntax-Reference"},{"fqn":"syntax::Return","name":"Return","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Return"},{"fqn":"syntax::Severity","name":"Severity","kind":"data","summary":"Diagnostic severity. `Error` invalidates the input; `Warning`/`Info` advise\nand never block compilation.","text":"","module":"syntax","href":"module/syntax.html#syntax-Severity"},{"fqn":"syntax::Span","name":"Span","kind":"data","summary":"A half-open byte range `[start, end)` into one module's source. Offsets are\nbytes, not characters; columns derived via `LineIndex` count bytes too,\nmatching the conformance token goldens.","text":"","module":"syntax","href":"module/syntax.html#syntax-Span"},{"fqn":"syntax::SpannedTrivia","name":"SpannedTrivia","kind":"data","summary":"A trivia element paired with the source span it occupies.","text":"","module":"syntax","href":"module/syntax.html#syntax-SpannedTrivia"},{"fqn":"syntax::StepKind","name":"StepKind","kind":"data","summary":"The step keyword class of a feature step (§5.2). `And`/`But` continue the\npreceding step's flow phase.","text":"","module":"syntax","href":"module/syntax.html#syntax-StepKind"},{"fqn":"syntax::Stmt","name":"Stmt","kind":"data","summary":"A statement valid inside a callable body (§7), with the leading trivia that\npreceded it.","text":"","module":"syntax","href":"module/syntax.html#syntax-Stmt"},{"fqn":"syntax::StmtKind","name":"StmtKind","kind":"data","summary":"The statement forms (§7).","text":"","module":"syntax","href":"module/syntax.html#syntax-StmtKind"},{"fqn":"syntax::StringLit","name":"StringLit","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-StringLit"},{"fqn":"syntax::Syntax","name":"Syntax","kind":"container","summary":"`crates/pseudoscript-syntax`. The foundation crate: source text to tokens\nand a typed AST, emitting the shared `Diagnostic`. WASM-safe and I/O-free.","text":"","module":"syntax","href":"module/syntax.html#syntax-Syntax"},{"fqn":"syntax::Tag","name":"Tag","kind":"data","summary":"A `\n` tag from a doc block, including the leading `#` (§2.4).","text":"","module":"syntax","href":"module/syntax.html#syntax-Tag"},{"fqn":"syntax::Token","name":"Token","kind":"data","summary":"A lexical token: its kind, source span, and rendered lexeme (LANG.md §2).","text":"For most tokens `text` is the raw source slice. For `Doc`/`InnerDoc` it is\nthe doc text with the marker and surrounding whitespace stripped; for `Tag`\nit includes the leading `#`.","module":"syntax","href":"module/syntax.html#syntax-Token"},{"fqn":"syntax::TokenKind","name":"TokenKind","kind":"data","summary":"Every lexical token class (LANG.md §2). The `name` rendering (e.g.\n`KW_SYSTEM`) is the conformance golden's `KIND`. Primitive type names and\n`Result` are NOT keywords — they lex as `Ident` and are classified in type\nposition by the model crate.","text":"","module":"syntax","href":"module/syntax.html#syntax-TokenKind"},{"fqn":"syntax::Trivia","name":"Trivia","kind":"data","summary":"Non-token source between tokens, preserved for the formatter. Doc comments,\ntags, macros, and modifiers are first-class tokens/AST data, not trivia —\nonly discarded comments and blank-line gaps live here.","text":"","module":"syntax","href":"module/syntax.html#syntax-Trivia"},{"fqn":"syntax::Type","name":"Type","kind":"data","summary":"A type expression: a named base with optional generic arguments and an\noptional trailing `[]` array suffix (§3.3, ADR-008 — no optionality marker).","text":"","module":"syntax","href":"module/syntax.html#syntax-Type"},{"fqn":"syntax::Unary","name":"Unary","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Unary"},{"fqn":"syntax::UnaryOp","name":"UnaryOp","kind":"data","summary":"A unary operator (§7.5): boolean `!` or numeric `-`.","text":"","module":"syntax","href":"module/syntax.html#syntax-UnaryOp"},{"fqn":"syntax::Union","name":"Union","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-Union"},{"fqn":"syntax::Variant","name":"Variant","kind":"data","summary":"One union variant: its name and an optional inline record body.","text":"","module":"syntax","href":"module/syntax.html#syntax-Variant"},{"fqn":"syntax::While","name":"While","kind":"data","summary":"","text":"","module":"syntax","href":"module/syntax.html#syntax-While"},{"fqn":"universe","name":"universe","kind":"module","summary":"","text":"","module":"universe","href":"module/universe.html"},{"fqn":"universe::Adapter","name":"Adapter","kind":"component","summary":"Adapts a resolved model into the software graph: keeps only the structural\nnodes, hooks each to its structural parent for the containment tree, and lifts\nevery call to the structural level — tallying directed traffic per\ncaller→callee pair so the edge carries direction.","text":"","module":"universe","href":"module/universe.html#universe-Adapter"},{"fqn":"universe::C4Level","name":"C4Level","kind":"data","summary":"The C4 abstraction level a node sits at. Only these four enter the graph;\ndata and callables do not — a relationship through them is lifted to the\nnearest enclosing structural node.","text":"","module":"universe","href":"module/universe.html#universe-C4Level"},{"fqn":"universe::EdgeOut","name":"EdgeOut","kind":"data","summary":"One relationship in the flat snapshot, with its traffic (call count).","text":"","module":"universe","href":"module/universe.html#universe-EdgeOut"},{"fqn":"universe::Flatten","name":"Flatten","kind":"component","summary":"Flattens the software graph into the renderer-facing snapshot: each node's id,\nlevel string, and parent id; each relationship's endpoints and traffic. The\nboundary the web IDE reads — petgraph indices never cross it.","text":"","module":"universe","href":"module/universe.html#universe-Flatten"},{"fqn":"universe::FlowDef","name":"FlowDef","kind":"data","summary":"One entry-point flow: the entry callable's FQN and simple name, the flow's\npalette colour, and its ordered legs. The renderer draws one filament chain\nper flow and streams its beads in the flow's colour.","text":"","module":"universe","href":"module/universe.html#universe-FlowDef"},{"fqn":"universe::FlowHop","name":"FlowHop","kind":"data","summary":"One call leg of a flow: the caller and callee as structural node ids (the\nsame ids the snapshot places), plus the message label shown on the leg.","text":"","module":"universe","href":"module/universe.html#universe-FlowHop"},{"fqn":"universe::FlowTracer","name":"FlowTracer","kind":"component","summary":"Traces every entry-point flow: a triggered callable (or a person-owned\naction) starts a flow; its sequence projection is walked call-by-call and\neach leg's endpoints lift to the nearest placed structural node — the same\nlift the Adapter applies to relationships, so flows and edges agree. Colours\nkey from the entry's FQN by a stable hash into a fixed palette — the same\nkeying the web IDE paints with — so a flow keeps its hue everywhere it\nappears and an unrelated model edit never recolours it.","text":"","module":"universe","href":"module/universe.html#universe-FlowTracer"},{"fqn":"universe::GraphNode","name":"GraphNode","kind":"data","summary":"A structural node in the software graph: its stable model FQN, its C4 level,\nand its place in the containment tree (the enclosing node, empty for a\ntop-level system, and the ids it contains). No position — the renderer\nplaces it.","text":"","module":"universe","href":"module/universe.html#universe-GraphNode"},{"fqn":"universe::NodeOut","name":"NodeOut","kind":"data","summary":"One node in the flat snapshot: its id, C4 level as a lowercase string, and the\nid of its containment parent (empty for a top-level system).","text":"","module":"universe","href":"module/universe.html#universe-NodeOut"},{"fqn":"universe::Relationship","name":"Relationship","kind":"data","summary":"A directed relationship between two structural nodes, weighted by `traffic` —\nthe number of underlying calls lifted onto the pair. Self-relationships are\ndropped; the count drives the renderer's flow animation.","text":"","module":"universe","href":"module/universe.html#universe-Relationship"},{"fqn":"universe::Snapshot","name":"Snapshot","kind":"data","summary":"The flat, serialisable contract the web IDE's `ForceGraph` consumes: nodes\nand directed weighted edges, with no engine internals (petgraph indices)\nleaking across the boundary. Positions are not here.","text":"","module":"universe","href":"module/universe.html#universe-Snapshot"},{"fqn":"universe::SoftwareGraph","name":"SoftwareGraph","kind":"data","summary":"The software graph: structural nodes with the containment tree, directed\nrelationship edges weighted by traffic, and the top-level systems (the\ncontainment roots, in model-declaration order). The crate's in-memory form,\nbefore flattening to a renderer-facing `Snapshot`.","text":"","module":"universe","href":"module/universe.html#universe-SoftwareGraph"},{"fqn":"universe::Universe","name":"Universe","kind":"container","summary":"`crates/pseudoscript-universe`. Maps the resolved C4 model into the software\ngraph the 3D view renders, then flattens it to a determinstic snapshot.","text":"","module":"universe","href":"module/universe.html#universe-Universe"},{"fqn":"universe.html","name":"3D Universe","kind":"page","summary":"The model in 3D — structure and flows","text":"","module":"","href":"universe.html"}]; \ No newline at end of file diff --git a/model/site/universe.html b/model/site/universe.html index 6c3b8ab9..f31657b0 100644 --- a/model/site/universe.html +++ b/model/site/universe.html @@ -11,9 +11,9 @@ -
PseudoScript
Model

Universe

The whole model as a 3D scene — drag to orbit, scroll to zoom, click a - node to open its docs.

Systems

Flows

  • dispatch 0 hops
  • run 3 hops
  • runAll 1 hop
  • add 0 hops
  • install 0 hops
  • list 0 hops
  • remove 0 hops
  • update 0 hops
  • runAll 1 hop
  • serve 2 hops
  • run 5 hops
  • run 6 hops
  • serve 0 hops
  • run 0 hops
  • run 9 hops
  • lang 0 hops
  • skill 0 hops
  • run 9 hops
  • run 3 hops
  • run 0 hops
  • editModel 9 hops
  • renderDocs 14 hops
  • installCli 1 hop
  • tryInBrowser 4 hops
Generated by pds doc.
- +
PseudoScript
Model

Universe

The whole model as a 3D scene — drag to orbit, scroll to zoom, click a + node to open its docs.

Systems

Flows

  • dispatch 0 hops
  • run 18 hops
  • runAll 1 hop
  • add 0 hops
  • install 0 hops
  • list 0 hops
  • remove 0 hops
  • update 0 hops
  • runAll 1 hop
  • serve 54 hops
  • run 10 hops
  • run 6 hops
  • serve 0 hops
  • run 0 hops
  • run 9 hops
  • lang 0 hops
  • skill 0 hops
  • run 9 hops
  • run 3 hops
  • run 0 hops
  • editModel 11 hops
  • renderDocs 55 hops
  • installCli 1 hop
  • tryInBrowser 4 hops
Generated by pds doc.
+ diff --git a/model/syntax.pds b/model/syntax.pds index 682aa753..7b587e26 100644 --- a/model/syntax.pds +++ b/model/syntax.pds @@ -179,13 +179,14 @@ public data Expr { kind: syntax::ExprKind, span: syntax::Span } /// The expression forms (§10). `Marker` is a built-in generic constructor — /// `Ok`/`Err` (`Result`) or `Some`/`None` (`Option`); `From` names a target /// type and its `source`, whose `FromSource` form is the §7.2 vs §7.4 split; -/// `Postfix` is a `.name`/`.name(..)` access chain (ADR-007); `Unary`/`Binary` -/// are the §7.5 operator forms. A `Marker` or `From` head is never a binary -/// operand (§7.5). +/// `Postfix` is a `.name`/`.name(..)` access chain (ADR-007); `OwnCall` is a +/// bare same-node call `Name(args)` (§5.1, ADR-041); `Unary`/`Binary` are the +/// §7.5 operator forms. A `Marker` or `From` head is never a binary operand (§7.5). public data ExprKind = | Marker { kind: syntax::MarkerKind, payload: syntax::Expr } | From { ty: syntax::Type, source: syntax::FromSource } | Postfix { base: syntax::Expr, segments: syntax::PostfixSeg[] } + | OwnCall { name: string, args: syntax::Expr[] } | Ref { target: syntax::Reference } | Lit { value: syntax::Literal } | Unary { op: syntax::UnaryOp, opSpan: syntax::Span, expr: syntax::Expr } @@ -235,9 +236,9 @@ public data MarkerKind = /// present marks a call; absent marks field access. public data PostfixSeg { name: string, callArgs: syntax::Expr[], span: syntax::Span } -/// A reference primary (§10 `Ref`): `self` or an FQN. +/// A reference primary (§10 `Ref`): an FQN. `self` was dropped (ADR-041); a +/// same-node call is the bare `OwnCall` primary, not a reference. public data Reference = - | SelfNode { span: syntax::Span } | PathRef { path: syntax::Path } /// A literal value (ADR-013). `raw` carries the source text (string literals @@ -310,7 +311,7 @@ public container Syntax for context::Pseudoscript { component Lexer for syntax::Syntax { /// The conformance token stream (LANG.md §2). tokenize(text: string): syntax::Token[] { - lexed = Lexed from self.lex(text) + lexed = Lexed from lex(text) return lexed.tokens } @@ -344,9 +345,9 @@ component Lexer for syntax::Syntax { /// The conformance rendering: one `KIND@line:col "lexeme"` line per token, /// line/col resolved via `LineIndex`, interior `\` and `"` escaped. public renderTokens(text: string): string { - tokens = Token[] from self.tokenize(text) + tokens = Token[] from tokenize(text) syntax::LineIndex.build(text) - return self.renderStream(tokens) + return renderStream(tokens) } /// Formats a token stream into the conformance text, one @@ -377,8 +378,8 @@ component Parser for syntax::Syntax { /// the trivia stream. public parse(text: string): syntax::Parsed { lexed = Lexed from syntax::Lexer.lex(text) - ast = Module from self.parseModule(lexed) - diagnostics = Diagnostic[] from self.diagnostics() + ast = Module from parseModule(lexed) + diagnostics = Diagnostic[] from diagnostics() return Parsed from { ast, diagnostics } } @@ -453,9 +454,10 @@ component Parser for syntax::Syntax { /// so `Result from ..` is told apart from a comparison `a < b`. atFromHead(): bool; - /// A primary: a literal, `self`, a `( group )`, or a path reference. Markers - /// and `from` heads never reach here — `parseExpr` dispatches them first — so - /// a `<` after a path here is the less-than operator (§7.5). + /// A primary: a literal, a bare same-node call `Name(args)`, a `( group )`, or + /// a path reference. Markers and `from` heads never reach here — `parseExpr` + /// dispatches them first — so a `<` after a path here is the less-than + /// operator (§7.5). parsePrimary(): syntax::Expr; /// A run of `Doc`/`Tag` tokens into a `DocBlock`, splitting summary from diff --git a/model/universe.pds b/model/universe.pds index ffcfbabd..19f2978a 100644 --- a/model/universe.pds +++ b/model/universe.pds @@ -99,9 +99,9 @@ component Adapter for universe::Universe { /// Adapt an already-resolved model: place the structural nodes, wire /// containment, then lift and tally call traffic into directed edges. public fromModel(graph: model::Graph): universe::SoftwareGraph { - nodes = GraphNode[] from self.placeStructural(graph) - rooted = SoftwareGraph from self.wireContainment(graph, nodes) - return self.tallyTraffic(graph, rooted) + nodes = GraphNode[] from placeStructural(graph) + rooted = SoftwareGraph from wireContainment(graph, nodes) + return tallyTraffic(graph, rooted) } /// Each model node whose kind is structural (system/container/component/person) @@ -137,8 +137,8 @@ component Adapter for universe::Universe { component FlowTracer for universe::Universe { /// Every flow, one per entry point in FQN order, coloured by its FQN key. public flows(graph: model::Graph): universe::FlowDef[] { - entries = model::GraphNode[] from self.entryPoints(graph) - return self.traceAll(graph, entries) + entries = model::GraphNode[] from entryPoints(graph) + return traceAll(graph, entries) } /// The flow entry points: every callable carrying a trigger macro, plus every @@ -153,12 +153,12 @@ component FlowTracer for universe::Universe { /// hops, and carry the flow's colour. A projection failure yields a flow with /// no hops rather than aborting — a partial model still animates. trace(graph: model::Graph, entry: model::GraphNode, color: string): universe::FlowDef { - scene = Result from emit::Emit.project(graph, self.sequenceView(entry)) + scene = Result from emit::Emit.project(graph, sequenceView(entry)) if (scene.isErr) { - return self.emptyFlow(entry, color) + return emptyFlow(entry, color) } - hops = universe::FlowHop[] from self.walkMessages(graph, scene.value) - return self.flowOf(entry, color, hops) + hops = universe::FlowHop[] from walkMessages(graph, scene.value) + return flowOf(entry, color, hops) } /// Assemble the flow from the entry's FQN and simple name, its colour, and @@ -191,8 +191,8 @@ component Flatten for universe::Universe { /// Flatten a graph to a `Snapshot`: map each node and relationship to its flat /// `out` form. public snapshot(graph: universe::SoftwareGraph): universe::Snapshot { - nodes = NodeOut[] from self.nodesOf(graph) - edges = EdgeOut[] from self.edgesOf(graph) + nodes = NodeOut[] from nodesOf(graph) + edges = EdgeOut[] from edgesOf(graph) return Snapshot from { nodes, edges } } diff --git a/web-ide/src/lib/bundled/LANG.md b/web-ide/src/lib/bundled/LANG.md index 10f53ac8..3450d103 100644 --- a/web-ide/src/lib/bundled/LANG.md +++ b/web-ide/src/lib/bundled/LANG.md @@ -47,8 +47,8 @@ Every cross-reference is a **fully-qualified name (FQN)**, derived from the file ``` system container component person data constant for from -public self -return Ok Err Some None +public return Ok Err +Some None if else while in true false feature given when then and but @@ -123,7 +123,7 @@ Option // optional value: Some(T) | None ### 3.3 Type expressions A named type (`BankingInfo`), generic (`Result`, `Option`), or array (`T[]`). `[]` is the only type suffix; an absent value is modeled with `Option` (§6). -Every named type — a field, parameter, or return type, and each generic argument — MUST resolve to a primitive (§3.1), `Result`/`Option` (§3.2), or a declared type or node (§3.4, §3.5, §4); an unresolved type MUST be rejected (ADR-022). A reference to a declared type or node MUST be its FQN (§8.1), including one in the same module; only primitives, `Result`/`Option`, and `self` are bare. +Every named type — a field, parameter, or return type, and each generic argument — MUST resolve to a primitive (§3.1), `Result`/`Option` (§3.2), or a declared type or node (§3.4, §3.5, §4); an unresolved type MUST be rejected (ADR-022). A reference to a declared type or node MUST be its FQN (§8.1), including one in the same module; only primitives and `Result`/`Option` are bare. ### 3.4 Data declarations A `data` type models any payload — DTOs, entities, messages alike. It MAY stay a **black box** with `;` (fields not yet disclosed). @@ -224,8 +224,8 @@ A function-shaped declaration is a callable. - All calls are request/response. A call to a resolvable callable MUST pass one argument per declared parameter, and each inferable argument MUST match its parameter's type; a wrong arity or argument type MUST be rejected (ADR-022, ADR-023). - Every callable MUST declare a return type; a callable without one MUST be rejected (ADR-040). `void` declares that no value is returned. A disclosed non-`void` callable MUST return a value on every path. - A `return` operand whose type is determinable — a literal, an `Ok`/`Err`/`Some`/`None` marker (§6), a `from` (§7.2), a typed binding, or a call to a resolvable callable — MUST match the declared return type; a mismatch MUST be rejected. A union variant satisfies its union type (§3.5). A bare reference resolving to a `data` record or a node is not a value and MUST be rejected (§7.2). -- A bare name in a body MUST resolve to a parameter, a binding, or a `for` binding; it MUST NOT resolve to a node or union variant (ADR-030) — those are referenced by FQN (§8.1). An unresolved bare name MUST be rejected (ADR-022). -- A same-node callable is invoked via `self.Name(args)` (`self` = the enclosing node); this also covers recursion. +- A bare name read as a value MUST resolve to a parameter, a binding, or a `for` binding; it MUST NOT resolve to a node or union variant (ADR-030) — those are referenced by FQN (§8.1). An unresolved bare name MUST be rejected (ADR-022). +- A same-node callable is invoked by a bare call `Name(args)` — a sibling, or the enclosing callable itself for recursion (ADR-041). A bare name in call position MUST resolve to a callable on the enclosing node; one matching no callable on the node MUST be rejected (ADR-022). - A callable's name and its parameter names MUST NOT be reserved words (§2.3) — `container`, `component`, `data`, and `for` are reserved. - A call statement MAY ignore its `Result` (the call still renders as a message). - A black-box callable shows in C4 as a capability; a call to it in a sequence diagram is a single message with no expansion. @@ -402,7 +402,7 @@ Each path segment becomes an FQN segment, which MUST be an identifier (§2.2). A A module has four distinct namespaces: **type names** (`data` declarations and hoisted record variants, §3.5), **node names** (`system`/`container`/`component`/`person`), **feature names** (§5.2), and **value names** (`constant`, §3.6). A name MUST be unique within its namespace; the four do not collide — a `data`, a `container`, a `feature`, and a `constant` MAY share a name. Callable and parameter names are scoped to their owner, not the module. -Every reference to a node, type, union variant, or constant MUST be its FQN, including a reference to one declared in the same module (ADR-030). A bare leaf name MUST NOT resolve to a node, type, variant, or constant; it resolves only to a parameter, a binding, or a `for` binding (§7). `self` and member access (§7.1) are unaffected, as are the primitives (§3.1) and `Result`/`Option` (§3.2). Within `banking/core.pds`, a sibling node is addressed `banking::core::Other`, never `Other`. An FQN names a node by its module path and name only; the system→container→component nesting (§4) is carried by `for`, not the name. A structural drill — a node addressed through its C4 ancestry, `Container::Component` or `module::System::Container::Component` — is not an FQN and MUST NOT resolve (ADR-036). +Every reference to a node, type, union variant, or constant MUST be its FQN, including a reference to one declared in the same module (ADR-030). A bare leaf name read as a value MUST NOT resolve to a node, type, variant, or constant; it resolves only to a parameter, a binding, or a `for` binding (§7). A bare name in call position resolves to a callable on the enclosing node (§5.1, ADR-041). Member access (§7.1) is unaffected, as are the primitives (§3.1) and `Result`/`Option` (§3.2). Within `banking/core.pds`, a sibling node is addressed `banking::core::Other`, never `Other`. An FQN names a node by its module path and name only; the system→container→component nesting (§4) is carried by `for`, not the name. A structural drill — a node addressed through its C4 ancestry, `Container::Component` or `module::System::Container::Component` — is not an FQN and MUST NOT resolve (ADR-036). An FQN's first segment is a **root**. The file-derived module paths above are the local roots; a `[dependencies]` entry (§8.3) adds one root per declared dependency. @@ -453,7 +453,7 @@ From disclosed callables per §7. A **triggered** callable (one bearing a trigge A call to a **disclosed** callee expands inline: the callee becomes the active lifeline, its body traces in place, and each of its `return`s is a return message to its caller's lifeline. A call to a **black-box** callable renders as a single message with no expansion (§5.1). A callee already in flight on the call path (direct or mutual recursion) MUST NOT re-expand; it renders as a single message. -In a chained expression, each call is its own message, emitted left-to-right; field accesses between calls are local. A `self.` call renders as a self-message. +In a chained expression, each call is its own message, emitted left-to-right; field accesses between calls are local. A same-node call (`Name(args)`, §5.1) renders as a self-message and expands its callee's body inline, exactly as a direct call to a disclosed callee does (ADR-041); recursion is stack-guarded as above. A method on a local value or chain intermediate renders as a leaf self-message — it names no node callable and has no body to follow. Each lifeline head card shows the participant's C4 kind and name. A `container` or `component` participant SHOULD also show its `for` ancestry (enclosing node names, outermost first) dimmed beneath the name. Every declared participant SHOULD show its `///` summary, as on a C4 card (§9.1). A synthesised initiator carries neither. @@ -572,11 +572,12 @@ AddExpr = MulExpr { ( "+" | "-" ) MulExpr } ; MulExpr = UnaryExpr { ( "*" | "/" | "%" ) UnaryExpr } ; UnaryExpr = ( "!" | "-" ) UnaryExpr | Postfix ; Postfix = Primary { "." Ident [ "(" [ Args ] ")" ] } ; // field access / call, chained -Primary = Ref | Literal | "(" Expr ")" ; +Primary = Call | Ref | Literal | "(" Expr ")" ; +Call = Ident "(" [ Args ] ")" ; // same-node callable (§5.1) Marker = ( "Ok" | "Err" | "Some" ) [ "(" Expr ")" ] | "None" ; // built-in generic constructors FromExpr = Type "from" ( "{" [ Expr { "," Expr } ] "}" | Expr ) ; // brace source set, or a single value; "[]" target composes an array Args = Expr { "," Expr } ; -Ref = "self" | Ident | Path ; // self or an FQN +Ref = Ident | Path ; // a local name or an FQN Path = Ident { "::" Ident } ; Literal = String | Number | Bool ; diff --git a/web-ide/src/lib/components/RenameDialog.test.ts b/web-ide/src/lib/components/RenameDialog.test.ts index d7b70be6..5a23838e 100644 --- a/web-ide/src/lib/components/RenameDialog.test.ts +++ b/web-ide/src/lib/components/RenameDialog.test.ts @@ -8,7 +8,7 @@ import RenameDialog from "./RenameDialog.svelte"; const occurrences = [ { fqn: "orders", line: 3, col: 10, text: "public Place(): void", match_start: 7, match_end: 12, decl: true }, - { fqn: "orders", line: 9, col: 4, text: "self.Place()", match_start: 5, match_end: 10, decl: false }, + { fqn: "orders", line: 9, col: 4, text: "Place()", match_start: 0, match_end: 5, decl: false }, ] as never; describe("RenameDialog", () => { diff --git a/web-ide/src/lib/fim-context.ts b/web-ide/src/lib/fim-context.ts index 30f79404..8d1dd9fe 100644 --- a/web-ide/src/lib/fim-context.ts +++ b/web-ide/src/lib/fim-context.ts @@ -30,7 +30,7 @@ const GRAMMAR_PRIMER = `// PseudoScript (.pds) — a C4 architecture-modeling la // statements x = Type from expr x = Type from { partA, partB } // if (expr) { … } else { … } for (x in xs) { … } while (expr) { … } // return expr Ok(x) Err(e) Some(x) None -// calls module::Node.method(args) self.method(args) — references are flat FQNs +// calls module::Node.method(args) Name(args) — references are flat FQNs; a bare call is same-node // triggers #[http("POST /path")] #[onevent(Event)] #[schedule = "cron"] #[manual] // behaviour feature Name for module::Node { given "…" when "…" then "…" and "…" but "…" } // Example: diff --git a/web-ide/src/lib/pds-ide-wasm/pseudoscript_ide_bg.wasm b/web-ide/src/lib/pds-ide-wasm/pseudoscript_ide_bg.wasm index 43f9a2e8..a12ee8bd 100644 Binary files a/web-ide/src/lib/pds-ide-wasm/pseudoscript_ide_bg.wasm and b/web-ide/src/lib/pds-ide-wasm/pseudoscript_ide_bg.wasm differ diff --git a/web-ide/src/lib/samples/acme-payments/backoffice.pds b/web-ide/src/lib/samples/acme-payments/backoffice.pds index 883950a9..a00629c6 100644 --- a/web-ide/src/lib/samples/acme-payments/backoffice.pds +++ b/web-ide/src/lib/samples/acme-payments/backoffice.pds @@ -46,14 +46,14 @@ component Console for backoffice::Backoffice { if (owned.isErr) { return Err(backoffice::ConsoleError::NotFound) } - keep = Result from self.approves(decision.verdict) + keep = Result from approves(decision.verdict) if (keep.isOk) { risk::Risk.clear(decision.intent) return Ok } refunded = Result from refunds::Refunds.create(merchant.value.id, RefundRequest from { decision.intent, owned.value.amount }) if (refunded.isErr) { - return Err(self.refundRejected(refunded.error)) + return Err(refundRejected(refunded.error)) } risk::Risk.clear(decision.intent) return Ok @@ -68,7 +68,7 @@ component Console for backoffice::Backoffice { } done = Result from refunds::Refunds.create(merchant.value.id, req) if (done.isErr) { - return Err(self.refundRejected(done.error)) + return Err(refundRejected(done.error)) } return Ok(done.value) } diff --git a/web-ide/src/lib/samples/acme-payments/charges.pds b/web-ide/src/lib/samples/acme-payments/charges.pds index ee54d1e8..c75772b5 100644 --- a/web-ide/src/lib/samples/acme-payments/charges.pds +++ b/web-ide/src/lib/samples/acme-payments/charges.pds @@ -104,60 +104,60 @@ component ChargeService for charges::Charges { id = shared::ChargeId from charges::ChargeStore.nextId() charge = Charge from { id, intent, amount, charges::ChargeStatus::Authorizing } charges::ChargeStore.save(charge) - key = IdempotencyKey from self.keyFor(id, "authorize") + key = IdempotencyKey from keyFor(id, "authorize") held = Result from charges::Gateway.authorize(amount, instrument, key) if (held.isErr) { - self.onAuthError(charge, held.error) + onAuthError(charge, held.error) return Err(held.error) } - return Ok(self.recordAuth(charge, held.value)) + return Ok(recordAuth(charge, held.value)) } /// Complete a 3-D Secure challenge on a charge awaiting action, promoting it to /// a usable hold. public completeAction(intent: shared::PaymentIntentId, outcome: context::NetworkChallengeResult): Result { - loaded = Result from self.loadPending(intent) + loaded = Result from loadPending(intent) if (loaded.isErr) { return Err(loaded.error) } - key = IdempotencyKey from self.keyFor(loaded.value.charge, "authorize") + key = IdempotencyKey from keyFor(loaded.value.charge, "authorize") cleared = Result from charges::Gateway.completeAction(loaded.value.auth, outcome, key) if (cleared.isErr) { - self.onAuthError(self.chargeOf(loaded.value), cleared.error) + onAuthError(chargeOf(loaded.value), cleared.error) return Err(cleared.error) } - return Ok(self.markHeld(loaded.value.charge, cleared.value)) + return Ok(markHeld(loaded.value.charge, cleared.value)) } /// Capture (settle) an authorised charge. Idempotent: a second capture returns /// the existing receipt without calling the network again. public capture(intent: shared::PaymentIntentId): Result { - done = Option from self.receiptFor(intent) + done = Option from receiptFor(intent) if (done.isSome) { return Ok(done.value) } - loaded = Result from self.loadAuthorized(intent) + loaded = Result from loadAuthorized(intent) if (loaded.isErr) { return Err(loaded.error) } - key = IdempotencyKey from self.keyFor(loaded.value.charge, "capture") + key = IdempotencyKey from keyFor(loaded.value.charge, "capture") taken = Result from charges::Gateway.capture(loaded.value.auth, loaded.value.amount, key) if (taken.isErr) { - self.onCaptureError(self.chargeOfAuthorized(loaded.value), taken.error) + onCaptureError(chargeOfAuthorized(loaded.value), taken.error) return Err(taken.error) } - captured = Charge from self.markCaptured(loaded.value.charge, taken.value) + captured = Charge from markCaptured(loaded.value.charge, taken.value) charges::ChargeStore.save(captured) return Ok(Receipt from { captured.id, taken.value.ref }) } /// Void an authorised-but-uncaptured hold, releasing the cardholder's funds. public release(intent: shared::PaymentIntentId): void { - loaded = Result from self.loadAuthorized(intent) + loaded = Result from loadAuthorized(intent) if (loaded.isOk) { - key = IdempotencyKey from self.keyFor(loaded.value.charge, "void") + key = IdempotencyKey from keyFor(loaded.value.charge, "void") charges::Gateway.cancelAuth(loaded.value.auth, key) - charges::ChargeStore.save(self.markVoided(loaded.value.charge)) + charges::ChargeStore.save(markVoided(loaded.value.charge)) } } @@ -165,16 +165,16 @@ component ChargeService for charges::Charges { /// caller (`refunds`) only after the network acknowledges; a failed refund is /// surfaced, never swallowed. public refund(intent: shared::PaymentIntentId, amount: shared::Money): Result { - loaded = Result from self.loadCaptured(intent) + loaded = Result from loadCaptured(intent) if (loaded.isErr) { return Err(loaded.error) } - key = IdempotencyKey from self.keyFor(loaded.value.charge, "refund") + key = IdempotencyKey from keyFor(loaded.value.charge, "refund") back = Result from charges::Gateway.refund(loaded.value.capture, amount, key) if (back.isErr) { return Err(back.error) } - charges::ChargeStore.save(self.markRefunded(loaded.value.charge)) + charges::ChargeStore.save(markRefunded(loaded.value.charge)) return Ok(back.value) } @@ -185,15 +185,15 @@ component ChargeService for charges::Charges { /// still unknown is left for a later sweep, so a captured cardholder is never /// wrongly failed. public reconcileCapture(intent: shared::PaymentIntentId): void { - done = Option from self.receiptFor(intent) + done = Option from receiptFor(intent) if (done.isNone) { truth = Option from charges::Gateway.lookupCapture(intent) if (truth.isSome) { - charges::ChargeStore.save(self.recordCapture(intent, truth.value)) + charges::ChargeStore.save(recordCapture(intent, truth.value)) } else { gone = Result from charges::Gateway.captureDefinitelyMissing(intent) if (gone.isOk) { - charges::ChargeStore.save(self.recordFailed(intent)) + charges::ChargeStore.save(recordFailed(intent)) } } } @@ -211,9 +211,9 @@ component ChargeService for charges::Charges { /// On an authorise failure: a DEFINITE decline marks the charge `Failed`; an /// INDETERMINATE outcome leaves it `Authorizing` for `reconcileCapture`. onAuthError(charge: charges::Charge, e: charges::ChargeError): void { - sure = Result from self.isDefinite(e) + sure = Result from isDefinite(e) if (sure.isOk) { - charges::ChargeStore.save(self.markFailed(charge, e)) + charges::ChargeStore.save(markFailed(charge, e)) } } @@ -221,9 +221,9 @@ component ChargeService for charges::Charges { /// an INDETERMINATE outcome leaves both intact and the charge `Authorized`, /// because the capture may actually have settled. onCaptureError(charge: charges::Charge, e: charges::ChargeError): void { - sure = Result from self.isDefinite(e) + sure = Result from isDefinite(e) if (sure.isOk) { - charges::ChargeStore.save(self.markFailed(charge, e)) + charges::ChargeStore.save(markFailed(charge, e)) } } @@ -268,7 +268,7 @@ public component Gateway for charges::Charges { public authorize(amount: shared::Money, instrument: context::NetworkInstrument, key: charges::IdempotencyKey): Result { held = Result from context::CardNetwork.authorize(amount, instrument, key) if (held.isErr) { - return Err(self.translate(held.error)) + return Err(translate(held.error)) } return Ok(held.value) } @@ -277,7 +277,7 @@ public component Gateway for charges::Charges { public completeAction(auth: context::NetworkAuthRef, outcome: context::NetworkChallengeResult, key: charges::IdempotencyKey): Result { cleared = Result from context::CardNetwork.completeAction(auth, outcome, key) if (cleared.isErr) { - return Err(self.translate(cleared.error)) + return Err(translate(cleared.error)) } return Ok(cleared.value) } @@ -286,7 +286,7 @@ public component Gateway for charges::Charges { public capture(auth: context::NetworkAuthRef, amount: shared::Money, key: charges::IdempotencyKey): Result { taken = Result from context::CardNetwork.capture(auth, amount, key) if (taken.isErr) { - return Err(self.translate(taken.error)) + return Err(translate(taken.error)) } return Ok(taken.value) } @@ -295,7 +295,7 @@ public component Gateway for charges::Charges { public refund(capture: context::NetworkCaptureRef, amount: shared::Money, key: charges::IdempotencyKey): Result { back = Result from context::CardNetwork.refund(capture, amount, key) if (back.isErr) { - return Err(self.translate(back.error)) + return Err(translate(back.error)) } return Ok(back.value) } diff --git a/web-ide/src/lib/samples/acme-payments/disputes.pds b/web-ide/src/lib/samples/acme-payments/disputes.pds index 76a1c2a8..5fdcce1f 100644 --- a/web-ide/src/lib/samples/acme-payments/disputes.pds +++ b/web-ide/src/lib/samples/acme-payments/disputes.pds @@ -72,7 +72,7 @@ component DisputeService for disputes::Disputes { /// acknowledged but holds funds only once. #[http("POST /v1/network/disputes")] public open(event: disputes::DisputeEvent): Result { - verified = Result from self.verify(event) + verified = Result from verify(event) if (verified.isErr) { return Err(verified.error) } @@ -90,11 +90,11 @@ component DisputeService for disputes::Disputes { /// The merchant contests a dispute with evidence: forward it to the network and /// move the dispute to `UnderReview`. public submitEvidence(merchant: shared::MerchantId, req: disputes::EvidenceSubmission): Result { - loaded = Result from self.getFor(merchant, req.dispute) + loaded = Result from getFor(merchant, req.dispute) if (loaded.isErr) { return Err(loaded.error) } - open = Result from self.contestable(loaded.value) + open = Result from contestable(loaded.value) if (open.isErr) { return Err(open.error) } @@ -102,7 +102,7 @@ component DisputeService for disputes::Disputes { if (sent.isErr) { return Err(disputes::DisputeError::NetworkUnreachable) } - reviewing = Dispute from self.mark(loaded.value, disputes::DisputeStatus::UnderReview) + reviewing = Dispute from mark(loaded.value, disputes::DisputeStatus::UnderReview) disputes::DisputeStore.save(reviewing) return Ok(reviewing) } @@ -112,7 +112,7 @@ component DisputeService for disputes::Disputes { /// a replayed ruling settles once. #[http("POST /v1/network/dispute-outcomes")] public resolve(event: disputes::ResolutionEvent): Result { - verified = Result from self.verifyResolution(event) + verified = Result from verifyResolution(event) if (verified.isErr) { return Err(verified.error) } @@ -120,9 +120,9 @@ component DisputeService for disputes::Disputes { if (seen.isOk) { return Ok(Acknowledged from { event.eventId }) } - loaded = Result from self.get(event.dispute) + loaded = Result from get(event.dispute) if (loaded.isOk) { - self.applyRuling(loaded.value, event.ruling) + applyRuling(loaded.value, event.ruling) } disputes::CallbackLog.record(event.eventId) return Ok(Acknowledged from { event.eventId }) @@ -137,11 +137,11 @@ component DisputeService for disputes::Disputes { /// Forfeit a dispute the merchant never answered: mark it `Lost`. The funds /// were already held at open, so no further debit is needed. public forfeit(dispute: shared::DisputeId): void { - loaded = Result from self.get(dispute) + loaded = Result from get(dispute) if (loaded.isOk) { - open = Result from self.contestable(loaded.value) + open = Result from contestable(loaded.value) if (open.isOk) { - disputes::DisputeStore.save(self.mark(loaded.value, disputes::DisputeStatus::Lost)) + disputes::DisputeStore.save(mark(loaded.value, disputes::DisputeStatus::Lost)) } } } @@ -149,23 +149,23 @@ component DisputeService for disputes::Disputes { /// Apply a ruling: a win records `Won` and raises `DisputeWon`; a loss records /// `Lost`. applyRuling(dispute: disputes::Dispute, ruling: disputes::DisputeRuling): void { - won = Result from self.isWin(ruling) + won = Result from isWin(ruling) if (won.isOk) { - disputes::DisputeStore.save(self.mark(dispute, disputes::DisputeStatus::Won)) + disputes::DisputeStore.save(mark(dispute, disputes::DisputeStatus::Won)) disputes::Events.won(DisputeWon from { dispute.id, dispute.merchant, dispute.amount }) } else { - disputes::DisputeStore.save(self.mark(dispute, disputes::DisputeStatus::Lost)) + disputes::DisputeStore.save(mark(dispute, disputes::DisputeStatus::Lost)) } } /// Read a dispute on behalf of `merchant`, refusing one that belongs to /// another merchant. public getFor(merchant: shared::MerchantId, dispute: shared::DisputeId): Result { - loaded = Result from self.get(dispute) + loaded = Result from get(dispute) if (loaded.isErr) { return Err(loaded.error) } - owns = Result from self.ownedBy(loaded.value, merchant) + owns = Result from ownedBy(loaded.value, merchant) if (owns.isErr) { return Err(owns.error) } diff --git a/web-ide/src/lib/samples/acme-payments/gateway.pds b/web-ide/src/lib/samples/acme-payments/gateway.pds index e69fa9cd..aee6706d 100644 --- a/web-ide/src/lib/samples/acme-payments/gateway.pds +++ b/web-ide/src/lib/samples/acme-payments/gateway.pds @@ -115,7 +115,7 @@ component TokenApi for gateway::Gateway { } method = Result from vault::Vault.tokenize(merchant.value.id, card) if (method.isErr) { - return Err(self.rejectTokenize(method.error)) + return Err(rejectTokenize(method.error)) } return Ok(method.value) } @@ -140,10 +140,10 @@ component IntentApi for gateway::Gateway { return Err(guard.error) } if (guard.value.isSome) { - return self.reloadIntent(guard.value.value) + return reloadIntent(guard.value.value) } created = PaymentIntent from intents::Intents.create(merchant.value.id, req) - gateway::Idempotency.record(idem, self.intentId(created)) + gateway::Idempotency.record(idem, intentId(created)) return Ok(created) } @@ -155,7 +155,7 @@ component IntentApi for gateway::Gateway { } done = Result from intents::Intents.confirm(merchant.value.id, req) if (done.isErr) { - return Err(self.rejectIntent(done.error)) + return Err(rejectIntent(done.error)) } return Ok(done.value) } @@ -165,7 +165,7 @@ component IntentApi for gateway::Gateway { public confirmAction(intent: shared::PaymentIntentId, outcome: intents::ChallengeOutcome): Result { done = Result from intents::Intents.confirmAction(intent, outcome) if (done.isErr) { - return Err(self.rejectIntent(done.error)) + return Err(rejectIntent(done.error)) } return Ok(done.value) } @@ -178,7 +178,7 @@ component IntentApi for gateway::Gateway { } done = Result from intents::Intents.capture(merchant.value.id, intent) if (done.isErr) { - return Err(self.rejectIntent(done.error)) + return Err(rejectIntent(done.error)) } return Ok(done.value) } @@ -192,7 +192,7 @@ component IntentApi for gateway::Gateway { } done = Result from intents::Intents.cancel(merchant.value.id, intent) if (done.isErr) { - return Err(self.rejectIntent(done.error)) + return Err(rejectIntent(done.error)) } return Ok(done.value) } @@ -219,13 +219,13 @@ component RefundApi for gateway::Gateway { return Err(guard.error) } if (guard.value.isSome) { - return self.reloadRefund(guard.value.value) + return reloadRefund(guard.value.value) } done = Result from refunds::Refunds.create(merchant.value.id, req) if (done.isErr) { - return Err(self.rejectRefund(done.error)) + return Err(rejectRefund(done.error)) } - gateway::Idempotency.record(idem, self.refundId(done.value)) + gateway::Idempotency.record(idem, refundId(done.value)) return Ok(done.value) } @@ -247,7 +247,7 @@ component DisputeApi for gateway::Gateway { } done = Result from disputes::Disputes.submitEvidence(merchant.value.id, req) if (done.isErr) { - return Err(self.rejectDispute(done.error)) + return Err(rejectDispute(done.error)) } return Ok(done.value) } diff --git a/web-ide/src/lib/samples/acme-payments/identity.pds b/web-ide/src/lib/samples/acme-payments/identity.pds index 022113fe..8d6b0d87 100644 --- a/web-ide/src/lib/samples/acme-payments/identity.pds +++ b/web-ide/src/lib/samples/acme-payments/identity.pds @@ -86,7 +86,7 @@ component Keys for identity::Identity { if (merchant.isNone) { return Err(identity::ApiKeyError::InvalidKey) } - granted = Result from self.permits(key, need) + granted = Result from permits(key, need) if (granted.isErr) { return Err(identity::ApiKeyError::InsufficientScope) } diff --git a/web-ide/src/lib/samples/acme-payments/intents.pds b/web-ide/src/lib/samples/acme-payments/intents.pds index cbca7c31..e0eac4be 100644 --- a/web-ide/src/lib/samples/acme-payments/intents.pds +++ b/web-ide/src/lib/samples/acme-payments/intents.pds @@ -121,11 +121,11 @@ component PaymentIntents for intents::Intents { /// Secure requirement parks the intent in `RequiresAction` for the cardholder. /// #headline public confirm(merchant: shared::MerchantId, req: intents::ConfirmIntent): Result { - intent = Result from self.getFor(merchant, req.intent) + intent = Result from getFor(merchant, req.intent) if (intent.isErr) { return Err(intent.error) } - ready = Result from self.confirmable(intent.value) + ready = Result from confirmable(intent.value) if (ready.isErr) { return Err(ready.error) } @@ -137,59 +137,59 @@ component PaymentIntents for intents::Intents { outcome = RiskOutcome from risk::Risk.screen(input) blocked = Result from risk::Risk.isBlocked(outcome) if (blocked.isOk) { - return Err(self.reject(intent.value, outcome)) + return Err(reject(intent.value, outcome)) } review = Result from risk::Risk.isReview(outcome) if (review.isOk) { risk::Risk.flag(req.intent, "elevated risk") } - return self.proceed(intent.value, instrument.value) + return proceed(intent.value, instrument.value) } /// Complete a 3-D Secure challenge on an intent awaiting action, then resume /// the authorise-and-capture flow. Called by the cardholder, not the merchant. public confirmAction(intent: shared::PaymentIntentId, outcome: intents::ChallengeOutcome): Result { - loaded = Result from self.get(intent) + loaded = Result from get(intent) if (loaded.isErr) { return Err(loaded.error) } - pending = Result from self.inAction(loaded.value) + pending = Result from inAction(loaded.value) if (pending.isErr) { return Err(pending.error) } - cleared = Result from charges::Charges.completeAction(intent, self.challengeResult(outcome)) + cleared = Result from charges::Charges.completeAction(intent, challengeResult(outcome)) if (cleared.isErr) { - return Err(self.afterChargeFailure(loaded.value, cleared.error)) + return Err(afterChargeFailure(loaded.value, cleared.error)) } - return self.afterAuthorized(loaded.value) + return afterAuthorized(loaded.value) } /// Capture a manually-captured intent the merchant has decided to settle. public capture(merchant: shared::MerchantId, intent: shared::PaymentIntentId): Result { - loaded = Result from self.getFor(merchant, intent) + loaded = Result from getFor(merchant, intent) if (loaded.isErr) { return Err(loaded.error) } - ready = Result from self.capturable(loaded.value) + ready = Result from capturable(loaded.value) if (ready.isErr) { return Err(ready.error) } - return self.finalize(loaded.value) + return finalize(loaded.value) } /// Cancel an intent before capture, voiding any authorised hold so the /// cardholder's funds are released promptly. public cancel(merchant: shared::MerchantId, intent: shared::PaymentIntentId): Result { - loaded = Result from self.getFor(merchant, intent) + loaded = Result from getFor(merchant, intent) if (loaded.isErr) { return Err(loaded.error) } - cancelable = Result from self.cancelable(loaded.value) + cancelable = Result from cancelable(loaded.value) if (cancelable.isErr) { return Err(cancelable.error) } charges::Charges.release(intent) - canceled = PaymentIntent from self.mark(loaded.value, intents::IntentStatus::Canceled) + canceled = PaymentIntent from mark(loaded.value, intents::IntentStatus::Canceled) intents::IntentStore.save(canceled) intents::Events.canceled(PaymentCanceled from { canceled }) return Ok(canceled) @@ -202,16 +202,16 @@ component PaymentIntents for intents::Intents { /// outcome still unknown is left for the next sweep — the intent is never /// abandoned while its charge might be real. Every branch is idempotent. public resume(intent: shared::PaymentIntentId): void { - loaded = Result from self.get(intent) + loaded = Result from get(intent) if (loaded.isOk) { charges::Charges.reconcileCapture(intent) receipt = Option from charges::Charges.receiptFor(intent) if (receipt.isSome) { - self.complete(loaded.value, receipt.value) + complete(loaded.value, receipt.value) } else { failed = Result from charges::Charges.definitelyFailed(intent) if (failed.isOk) { - self.abandon(loaded.value) + abandon(loaded.value) } } } @@ -225,11 +225,11 @@ component PaymentIntents for intents::Intents { /// Read an intent on behalf of `merchant`, refusing one that belongs to /// another merchant. public getFor(merchant: shared::MerchantId, intent: shared::PaymentIntentId): Result { - loaded = Result from self.get(intent) + loaded = Result from get(intent) if (loaded.isErr) { return Err(loaded.error) } - owns = Result from self.ownedBy(loaded.value, merchant) + owns = Result from ownedBy(loaded.value, merchant) if (owns.isErr) { return Err(owns.error) } @@ -250,25 +250,25 @@ component PaymentIntents for intents::Intents { proceed(intent: intents::PaymentIntent, instrument: context::NetworkInstrument): Result { auth = Result from charges::Charges.authorize(intent.id, intent.amount, instrument) if (auth.isErr) { - return Err(self.afterChargeFailure(intent, auth.error)) + return Err(afterChargeFailure(intent, auth.error)) } - action = Result from self.actionRequired(auth.value) + action = Result from actionRequired(auth.value) if (action.isOk) { - pending = PaymentIntent from self.mark(intent, intents::IntentStatus::RequiresAction) + pending = PaymentIntent from mark(intent, intents::IntentStatus::RequiresAction) intents::IntentStore.save(pending) return Ok(pending) } - return self.afterAuthorized(intent) + return afterAuthorized(intent) } /// With the hold in hand: capture now for automatic capture, or park in /// `RequiresCapture` for the merchant to capture later. afterAuthorized(intent: intents::PaymentIntent): Result { - auto = Result from self.isAutomatic(intent) + auto = Result from isAutomatic(intent) if (auto.isOk) { - return self.finalize(intent) + return finalize(intent) } - awaiting = PaymentIntent from self.mark(intent, intents::IntentStatus::RequiresCapture) + awaiting = PaymentIntent from mark(intent, intents::IntentStatus::RequiresCapture) intents::IntentStore.save(awaiting) return Ok(awaiting) } @@ -277,9 +277,9 @@ component PaymentIntents for intents::Intents { finalize(intent: intents::PaymentIntent): Result { receipt = Result from charges::Charges.capture(intent.id) if (receipt.isErr) { - return Err(self.afterChargeFailure(intent, receipt.error)) + return Err(afterChargeFailure(intent, receipt.error)) } - done = PaymentIntent from self.mark(intent, intents::IntentStatus::Succeeded) + done = PaymentIntent from mark(intent, intents::IntentStatus::Succeeded) intents::IntentStore.save(done) intents::Events.succeeded(PaymentSucceeded from { done, receipt.value.charge }) return Ok(done) @@ -290,20 +290,20 @@ component PaymentIntents for intents::Intents { /// (a timeout — the money may have moved) parks it in `Processing` for the /// sweeper to reconcile, never failing it on uncertainty. afterChargeFailure(intent: intents::PaymentIntent, e: charges::ChargeError): intents::IntentError { - sure = Result from self.definite(e) + sure = Result from definite(e) if (sure.isErr) { - intents::IntentStore.save(self.mark(intent, intents::IntentStatus::Processing)) + intents::IntentStore.save(mark(intent, intents::IntentStatus::Processing)) return intents::IntentError::Uncertain } - reset = PaymentIntent from self.mark(intent, intents::IntentStatus::RequiresPaymentMethod) + reset = PaymentIntent from mark(intent, intents::IntentStatus::RequiresPaymentMethod) intents::IntentStore.save(reset) intents::Events.failed(PaymentFailed from { reset }) - return self.declined(e) + return declined(e) } /// Drive a reconciled-as-captured intent to `Succeeded` idempotently. complete(intent: intents::PaymentIntent, receipt: charges::Receipt): void { - done = PaymentIntent from self.mark(intent, intents::IntentStatus::Succeeded) + done = PaymentIntent from mark(intent, intents::IntentStatus::Succeeded) intents::IntentStore.save(done) intents::Events.succeeded(PaymentSucceeded from { done, receipt.charge }) } @@ -311,7 +311,7 @@ component PaymentIntents for intents::Intents { /// Abandon an intent whose charge never completed: reset it for retry and /// raise `PaymentFailed`. abandon(intent: intents::PaymentIntent): void { - reset = PaymentIntent from self.mark(intent, intents::IntentStatus::RequiresPaymentMethod) + reset = PaymentIntent from mark(intent, intents::IntentStatus::RequiresPaymentMethod) intents::IntentStore.save(reset) intents::Events.failed(PaymentFailed from { reset }) } diff --git a/web-ide/src/lib/samples/acme-payments/ledger.pds b/web-ide/src/lib/samples/acme-payments/ledger.pds index 0de3ead1..be8c62f0 100644 --- a/web-ide/src/lib/samples/acme-payments/ledger.pds +++ b/web-ide/src/lib/samples/acme-payments/ledger.pds @@ -65,14 +65,14 @@ component BalanceService for ledger::Ledger { /// time alone. #[onevent(intents::PaymentSucceeded)] public onPaymentSucceeded(event: intents::PaymentSucceeded): void { - entry = BalanceTransaction from self.creditFor(event) + entry = BalanceTransaction from creditFor(event) ledger::EntryLog.appendOnce(entry) } /// A successful refund debits available, immediately. #[onevent(refunds::RefundSucceeded)] public onRefund(event: refunds::RefundSucceeded): void { - entry = BalanceTransaction from self.refundDebit(event) + entry = BalanceTransaction from refundDebit(event) ledger::EntryLog.appendOnce(entry) } @@ -80,14 +80,14 @@ component BalanceService for ledger::Ledger { /// available, held until the dispute resolves. #[onevent(disputes::DisputeOpened)] public onDisputeOpened(event: disputes::DisputeOpened): void { - entry = BalanceTransaction from self.disputeDebit(event) + entry = BalanceTransaction from disputeDebit(event) ledger::EntryLog.appendOnce(entry) } /// A won dispute reverses the hold, returning the disputed amount to available. #[onevent(disputes::DisputeWon)] public onDisputeWon(event: disputes::DisputeWon): void { - entry = BalanceTransaction from self.disputeReversal(event) + entry = BalanceTransaction from disputeReversal(event) ledger::EntryLog.appendOnce(entry) } @@ -102,14 +102,14 @@ component BalanceService for ledger::Ledger { /// so two concurrent payouts cannot both draw the same funds, and the balance /// can never go negative from a payout. public withdraw(merchant: shared::MerchantId, payout: shared::PayoutId, amount: shared::Money): Result { - debit = BalanceTransaction from self.payoutDebit(merchant, payout, amount) + debit = BalanceTransaction from payoutDebit(merchant, payout, amount) return ledger::EntryLog.appendIfAvailable(merchant, debit, amount) } /// Return drawn funds to available when a payout fails to leave the bank, by /// appending a `PayoutReversal` (idempotent on the payout). public restore(merchant: shared::MerchantId, payout: shared::PayoutId, amount: shared::Money): void { - ledger::EntryLog.appendOnce(self.payoutReversal(merchant, payout, amount)) + ledger::EntryLog.appendOnce(payoutReversal(merchant, payout, amount)) } /// Build a pending `ChargeCredit` from a payment-succeeded event: net of fee, diff --git a/web-ide/src/lib/samples/acme-payments/notifications.pds b/web-ide/src/lib/samples/acme-payments/notifications.pds index 8b51726c..9c8eec8e 100644 --- a/web-ide/src/lib/samples/acme-payments/notifications.pds +++ b/web-ide/src/lib/samples/acme-payments/notifications.pds @@ -10,19 +10,19 @@ public component Notifier for notifications::Notifications { /// Alert a merchant that a payment was disputed and needs a response. #[onevent(disputes::DisputeOpened)] public onDisputeOpened(event: disputes::DisputeOpened): void { - context::EmailProvider.email(self.recipient(event.merchant), "Action needed: a payment was disputed", "A cardholder disputed a payment. Submit evidence before the deadline.") + context::EmailProvider.email(recipient(event.merchant), "Action needed: a payment was disputed", "A cardholder disputed a payment. Submit evidence before the deadline.") } /// Alert a merchant that a payout failed and the funds were returned. #[onevent(payouts::PayoutFailed)] public onPayoutFailed(event: payouts::PayoutFailed): void { - context::EmailProvider.email(self.recipient(event.merchant), "Your payout failed", "We couldn't pay out to your bank; the funds are back in your balance.") + context::EmailProvider.email(recipient(event.merchant), "Your payout failed", "We couldn't pay out to your bank; the funds are back in your balance.") } /// Confirm to a merchant that a payout reached their bank. #[onevent(payouts::PayoutPaid)] public onPayoutPaid(event: payouts::PayoutPaid): void { - context::EmailProvider.email(self.recipient(event.merchant), "Your payout is on the way", "A payout to your bank account has been sent.") + context::EmailProvider.email(recipient(event.merchant), "Your payout is on the way", "A payout to your bank account has been sent.") } /// The email address for a merchant. diff --git a/web-ide/src/lib/samples/acme-payments/payouts.pds b/web-ide/src/lib/samples/acme-payments/payouts.pds index 33f27659..85000164 100644 --- a/web-ide/src/lib/samples/acme-payments/payouts.pds +++ b/web-ide/src/lib/samples/acme-payments/payouts.pds @@ -63,7 +63,7 @@ component PayoutService for payouts::Payouts { return Ok(existing.value) } balance = Balance from ledger::Ledger.balance(merchant) - due = Result from self.payable(balance) + due = Result from payable(balance) if (due.isErr) { return Err(due.error) } @@ -72,23 +72,23 @@ component PayoutService for payouts::Payouts { if (drawn.isErr) { return Err(payouts::PayoutError::InsufficientFunds) } - destination = context::BankAccountRef from self.destinationFor(merchant) + destination = context::BankAccountRef from destinationFor(merchant) payout = Payout from { id, merchant, due.value, destination, payouts::PayoutStatus::Pending } payouts::PayoutStore.save(payout) - return self.send(payout) + return send(payout) } /// Send a recorded payout to the banking partner. On rejection, restore the /// drawn funds to the ledger and fail the payout (surfacing `PayoutFailed`); /// on acceptance, mark it in transit for the reconciler to settle. send(payout: payouts::Payout): Result { - key = PayoutKey from self.keyFor(payout.id) + key = PayoutKey from keyFor(payout.id) transfer = Result from context::BankingPartner.initiate(payout.amount, payout.destination, key) if (transfer.isErr) { - self.refund(payout) - return Err(self.translate(transfer.error)) + refund(payout) + return Err(translate(transfer.error)) } - inTransit = Payout from self.markInTransit(payout, transfer.value) + inTransit = Payout from markInTransit(payout, transfer.value) payouts::PayoutStore.save(inTransit) return Ok(inTransit) } @@ -99,11 +99,11 @@ component PayoutService for payouts::Payouts { public reconcile(id: shared::PayoutId): void { loaded = Option from payouts::PayoutStore.fetch(id) if (loaded.isSome) { - pending = Result from self.inTransit(loaded.value) + pending = Result from inTransit(loaded.value) if (pending.isOk) { state = Option from context::BankingPartner.status(pending.value.transfer) if (state.isSome) { - self.applyState(loaded.value, state.value) + applyState(loaded.value, state.value) } } } @@ -121,22 +121,22 @@ component PayoutService for payouts::Payouts { /// Apply a partner transfer state: settle, or restore-and-fail on a return. applyState(payout: payouts::Payout, state: context::BankTransferState): void { - settled = Result from self.isSettled(state) + settled = Result from isSettled(state) if (settled.isOk) { - payouts::PayoutStore.save(self.markPaid(payout)) + payouts::PayoutStore.save(markPaid(payout)) payouts::Events.paid(PayoutPaid from { payout.id, payout.merchant, payout.amount }) } else { - self.refund(payout) + refund(payout) } } /// Restore a failed payout's funds to the ledger and record the failure, /// raising `PayoutFailed`. Idempotent: a payout already failed is left alone. refund(payout: payouts::Payout): void { - fresh = Result from self.notYetFailed(payout) + fresh = Result from notYetFailed(payout) if (fresh.isOk) { ledger::Ledger.restore(payout.merchant, payout.id, payout.amount) - payouts::PayoutStore.save(self.markFailed(payout)) + payouts::PayoutStore.save(markFailed(payout)) payouts::Events.failed(PayoutFailed from { payout.id, payout.merchant, payout.amount }) } } diff --git a/web-ide/src/lib/samples/acme-payments/refunds.pds b/web-ide/src/lib/samples/acme-payments/refunds.pds index 4a7f10c2..f7fbe949 100644 --- a/web-ide/src/lib/samples/acme-payments/refunds.pds +++ b/web-ide/src/lib/samples/acme-payments/refunds.pds @@ -51,7 +51,7 @@ component RefundService for refunds::Refunds { if (intent.isErr) { return Err(refunds::RefundError::IntentNotFound) } - ready = Result from self.refundable(intent.value) + ready = Result from refundable(intent.value) if (ready.isErr) { return Err(ready.error) } @@ -60,10 +60,10 @@ component RefundService for refunds::Refunds { refunds::RefundStore.save(pending) back = Result from charges::Charges.refund(req.intent, req.amount) if (back.isErr) { - refunds::RefundStore.save(self.markFailed(pending, back.error)) - return Err(self.declined(back.error)) + refunds::RefundStore.save(markFailed(pending, back.error)) + return Err(declined(back.error)) } - done = Refund from self.markSucceeded(pending) + done = Refund from markSucceeded(pending) refunds::RefundStore.save(done) refunds::Events.refunded(RefundSucceeded from { done.id, done.merchant, done.amount }) return Ok(done) diff --git a/web-ide/src/lib/samples/acme-payments/vault.pds b/web-ide/src/lib/samples/acme-payments/vault.pds index 6ecba669..f1276371 100644 --- a/web-ide/src/lib/samples/acme-payments/vault.pds +++ b/web-ide/src/lib/samples/acme-payments/vault.pds @@ -34,16 +34,16 @@ component Tokenizer for vault::Vault { /// Tokenise a card: validate it, exchange it with the network for an /// instrument token, store only the token plus a safe display form. public tokenize(merchant: shared::MerchantId, card: vault::CardEntry): Result { - valid = Result from self.validate(card) + valid = Result from validate(card) if (valid.isErr) { return Err(valid.error) } - instrument = Result from self.exchange(card) + instrument = Result from exchange(card) if (instrument.isErr) { return Err(instrument.error) } id = shared::PaymentMethodId from vault::TokenStore.nextId() - method = PaymentMethod from self.represent(id, card) + method = PaymentMethod from represent(id, card) vault::TokenStore.save(merchant, method, instrument.value) return Ok(method) } diff --git a/web-ide/src/lib/samples/acme-payments/webhooks.pds b/web-ide/src/lib/samples/acme-payments/webhooks.pds index a75552f0..06186fa6 100644 --- a/web-ide/src/lib/samples/acme-payments/webhooks.pds +++ b/web-ide/src/lib/samples/acme-payments/webhooks.pds @@ -37,44 +37,44 @@ component Dispatcher for webhooks::Webhooks { /// Deliver `payment_intent.succeeded`. #[onevent(intents::PaymentSucceeded)] public onPaymentSucceeded(event: intents::PaymentSucceeded): void { - self.dispatch(self.encodeSucceeded(event)) + dispatch(encodeSucceeded(event)) } /// Deliver `payment_intent.payment_failed`. #[onevent(intents::PaymentFailed)] public onPaymentFailed(event: intents::PaymentFailed): void { - self.dispatch(self.encodeFailed(event)) + dispatch(encodeFailed(event)) } /// Deliver `charge.refunded`. #[onevent(refunds::RefundSucceeded)] public onRefunded(event: refunds::RefundSucceeded): void { - self.dispatch(self.encodeRefunded(event)) + dispatch(encodeRefunded(event)) } /// Deliver `charge.dispute.created`. #[onevent(disputes::DisputeOpened)] public onDisputeOpened(event: disputes::DisputeOpened): void { - self.dispatch(self.encodeDisputeOpened(event)) + dispatch(encodeDisputeOpened(event)) } /// Deliver `payout.paid`. #[onevent(payouts::PayoutPaid)] public onPayoutPaid(event: payouts::PayoutPaid): void { - self.dispatch(self.encodePayoutPaid(event)) + dispatch(encodePayoutPaid(event)) } /// Deliver `payout.failed`. #[onevent(payouts::PayoutFailed)] public onPayoutFailed(event: payouts::PayoutFailed): void { - self.dispatch(self.encodePayoutFailed(event)) + dispatch(encodePayoutFailed(event)) } /// Retry deliveries whose backoff has elapsed (driven by the scheduled sweep). public redeliver(): void { due = Delivery[] from webhooks::DeliveryStore.due() for (delivery in due) { - self.attempt(delivery.event) + attempt(delivery.event) } } @@ -84,7 +84,7 @@ component Dispatcher for webhooks::Webhooks { seen = Result from webhooks::DeliveryStore.seen(webhook.id) if (seen.isErr) { webhooks::DeliveryStore.record(webhook) - self.attempt(webhook.id) + attempt(webhook.id) } } @@ -94,13 +94,13 @@ component Dispatcher for webhooks::Webhooks { attempt(event: string): void { deliverable = Option from webhooks::DeliveryStore.deliverable(event) if (deliverable.isSome) { - endpoint = string from self.endpointFor(deliverable.value.merchant) - signed = string from self.sign(deliverable.value) + endpoint = string from endpointFor(deliverable.value.merchant) + signed = string from sign(deliverable.value) sent = Result from context::MerchantEndpoint.deliver(endpoint, deliverable.value.payload, signed) if (sent.isErr) { - webhooks::DeliveryStore.save(self.onFailure(deliverable.value)) + webhooks::DeliveryStore.save(onFailure(deliverable.value)) } else { - webhooks::DeliveryStore.save(self.markDelivered(deliverable.value)) + webhooks::DeliveryStore.save(markDelivered(deliverable.value)) } } } diff --git a/web-ide/src/lib/samples/acme-tickets/backoffice.pds b/web-ide/src/lib/samples/acme-tickets/backoffice.pds index a4760c6e..be860831 100644 --- a/web-ide/src/lib/samples/acme-tickets/backoffice.pds +++ b/web-ide/src/lib/samples/acme-tickets/backoffice.pds @@ -66,7 +66,7 @@ component Console for backoffice::Backoffice { } live = Result from catalog::Catalog.putOnSale(event) if (live.isErr) { - return Err(self.notFound(live.error)) + return Err(notFound(live.error)) } return Ok(live.value) } @@ -79,7 +79,7 @@ component Console for backoffice::Backoffice { } dead = Result from catalog::Catalog.cancel(event) if (dead.isErr) { - return Err(self.notFound(dead.error)) + return Err(notFound(dead.error)) } return Ok(dead.value) } @@ -92,7 +92,7 @@ component Console for backoffice::Backoffice { } refunded = Result from orders::Orders.refund(order) if (refunded.isErr) { - return Err(self.refundRejected(refunded.error)) + return Err(refundRejected(refunded.error)) } return Ok(refunded.value) } diff --git a/web-ide/src/lib/samples/acme-tickets/catalog.pds b/web-ide/src/lib/samples/acme-tickets/catalog.pds index 26a1ec01..90b7476b 100644 --- a/web-ide/src/lib/samples/acme-tickets/catalog.pds +++ b/web-ide/src/lib/samples/acme-tickets/catalog.pds @@ -106,7 +106,7 @@ component Events for catalog::Catalog { if (found.isNone) { return Err(EventNotFound from { id }) } - live = Event from self.transition(found.value, catalog::EventStatus::OnSale) + live = Event from transition(found.value, catalog::EventStatus::OnSale) catalog::EventStore.save(live) return Ok(live) } @@ -118,7 +118,7 @@ component Events for catalog::Catalog { if (found.isNone) { return Err(EventNotFound from { id }) } - dead = Event from self.transition(found.value, catalog::EventStatus::Cancelled) + dead = Event from transition(found.value, catalog::EventStatus::Cancelled) catalog::EventStore.save(dead) catalog::Signals.cancelled(EventCancelled from { id }) return Ok(dead) @@ -129,7 +129,7 @@ component Events for catalog::Catalog { public settle(id: shared::EventId): void { found = Option from catalog::EventStore.fetch(id) if (found.isSome) { - next = Option from self.lifecycle(found.value) + next = Option from lifecycle(found.value) if (next.isSome) { catalog::EventStore.save(next.value) } diff --git a/web-ide/src/lib/samples/acme-tickets/identity.pds b/web-ide/src/lib/samples/acme-tickets/identity.pds index afa72219..86e7c51b 100644 --- a/web-ide/src/lib/samples/acme-tickets/identity.pds +++ b/web-ide/src/lib/samples/acme-tickets/identity.pds @@ -79,7 +79,7 @@ component Sessions for identity::Identity { if (user.isNone) { return Err(identity::AuthError::BadCredentials) } - ok = Result from self.verify(creds, user.value) + ok = Result from verify(creds, user.value) if (ok.isErr) { return Err(identity::AuthError::BadCredentials) } @@ -102,11 +102,11 @@ component Sessions for identity::Identity { /// Authenticate, then require the `Organizer` role. public requireOrganizer(token: identity::SessionToken): Result { - user = Result from self.authenticate(token) + user = Result from authenticate(token) if (user.isErr) { return Err(user.error) } - allowed = Result from self.ensureOrganizer(user.value) + allowed = Result from ensureOrganizer(user.value) if (allowed.isErr) { return Err(allowed.error) } @@ -115,11 +115,11 @@ component Sessions for identity::Identity { /// Authenticate, then require the `Support` role. public requireSupport(token: identity::SessionToken): Result { - user = Result from self.authenticate(token) + user = Result from authenticate(token) if (user.isErr) { return Err(user.error) } - allowed = Result from self.ensureSupport(user.value) + allowed = Result from ensureSupport(user.value) if (allowed.isErr) { return Err(allowed.error) } diff --git a/web-ide/src/lib/samples/acme-tickets/inventory.pds b/web-ide/src/lib/samples/acme-tickets/inventory.pds index c6a7ce25..26421dae 100644 --- a/web-ide/src/lib/samples/acme-tickets/inventory.pds +++ b/web-ide/src/lib/samples/acme-tickets/inventory.pds @@ -91,11 +91,11 @@ component Holds for inventory::Inventory { /// tier pool. Overselling is impossible: if the pool cannot satisfy the /// request the allocation fails and no hold is created. public reserve(event: shared::EventId, tier: shared::TierId, quantity: shared::Quantity, owner: shared::UserId): Result { - free = Result from self.noActiveHold(owner, event) + free = Result from noActiveHold(owner, event) if (free.isErr) { return Err(free.error) } - taken = Result from self.allocateSharded(event, tier, quantity) + taken = Result from allocateSharded(event, tier, quantity) if (taken.isErr) { return Err(inventory::InventoryError::OutOfStock) } @@ -110,7 +110,7 @@ component Holds for inventory::Inventory { /// tier is genuinely sold out — not merely when one shard ran dry — so /// sharding adds throughput without weakening the no-oversell guarantee. allocateSharded(event: shared::EventId, tier: shared::TierId, quantity: shared::Quantity): Result { - preferred = SeatShard from self.pickShard(event, tier) + preferred = SeatShard from pickShard(event, tier) first = Result from inventory::Pool.allocateFrom(preferred, quantity) if (first.isOk) { return Ok(preferred) @@ -124,7 +124,7 @@ component Holds for inventory::Inventory { if (found.isNone) { return Err(HoldNotFound from { id }) } - live = Result from self.ensureLive(found.value, owner) + live = Result from ensureLive(found.value, owner) if (live.isErr) { return Err(live.error) } @@ -139,11 +139,11 @@ component Holds for inventory::Inventory { if (found.isNone) { return Err(HoldNotFound from { id }) } - held = Result from self.ensureHeld(found.value) + held = Result from ensureHeld(found.value) if (held.isErr) { return Err(held.error) } - moved = Hold from self.transition(found.value, inventory::HoldStatus::Committing) + moved = Hold from transition(found.value, inventory::HoldStatus::Committing) inventory::HoldStore.save(moved) return Ok(moved) } @@ -157,16 +157,16 @@ component Holds for inventory::Inventory { if (found.isNone) { return Err(HoldNotFound from { id }) } - done = Option from self.existingAllocation(found.value) + done = Option from existingAllocation(found.value) if (done.isSome) { return Ok(done.value) } - ready = Result from self.ensureCommitting(found.value) + ready = Result from ensureCommitting(found.value) if (ready.isErr) { return Err(ready.error) } - allocation = Allocation from self.allocate(found.value) - inventory::HoldStore.save(self.transition(found.value, inventory::HoldStatus::Confirmed)) + allocation = Allocation from allocate(found.value) + inventory::HoldStore.save(transition(found.value, inventory::HoldStatus::Confirmed)) return Ok(allocation) } @@ -177,10 +177,10 @@ component Holds for inventory::Inventory { public release(id: shared::HoldId): void { found = Option from inventory::HoldStore.fetch(id) if (found.isSome) { - live = Result from self.ensureReleasable(found.value) + live = Result from ensureReleasable(found.value) if (live.isOk) { inventory::Pool.restoreTo(found.value.shard, found.value.quantity) - inventory::HoldStore.save(self.transition(found.value, inventory::HoldStatus::Released)) + inventory::HoldStore.save(transition(found.value, inventory::HoldStatus::Released)) } } } @@ -192,10 +192,10 @@ component Holds for inventory::Inventory { public releaseSold(id: shared::HoldId): void { found = Option from inventory::HoldStore.fetch(id) if (found.isSome) { - sold = Result from self.ensureConfirmed(found.value) + sold = Result from ensureConfirmed(found.value) if (sold.isOk) { inventory::Pool.restoreTo(found.value.shard, found.value.quantity) - inventory::HoldStore.save(self.transition(found.value, inventory::HoldStatus::Released)) + inventory::HoldStore.save(transition(found.value, inventory::HoldStatus::Released)) } } } @@ -206,10 +206,10 @@ component Holds for inventory::Inventory { public expire(id: shared::HoldId): void { found = Option from inventory::HoldStore.fetch(id) if (found.isSome) { - held = Result from self.ensureHeld(found.value) + held = Result from ensureHeld(found.value) if (held.isOk) { inventory::Pool.restoreTo(found.value.shard, found.value.quantity) - inventory::HoldStore.save(self.transition(found.value, inventory::HoldStatus::Expired)) + inventory::HoldStore.save(transition(found.value, inventory::HoldStatus::Expired)) } } } diff --git a/web-ide/src/lib/samples/acme-tickets/notifications.pds b/web-ide/src/lib/samples/acme-tickets/notifications.pds index 2e595744..523cb312 100644 --- a/web-ide/src/lib/samples/acme-tickets/notifications.pds +++ b/web-ide/src/lib/samples/acme-tickets/notifications.pds @@ -9,19 +9,19 @@ public component Notifier for notifications::Notifications { /// Alert a queued attendee that they have been admitted to buy. #[onevent(waitingroom::Admitted)] public onAdmitted(event: waitingroom::Admitted): void { - context::NotificationProvider.sms(self.contactFor(event.token), "You're in — you have a few minutes to buy.") + context::NotificationProvider.sms(contactFor(event.token), "You're in — you have a few minutes to buy.") } /// Confirm a completed purchase and its tickets. #[onevent(orders::OrderConfirmed)] public onConfirmed(event: orders::OrderConfirmed): void { - context::NotificationProvider.email(self.recipient(event.buyer), "Your tickets", "Your tickets are in your wallet.") + context::NotificationProvider.email(recipient(event.buyer), "Your tickets", "Your tickets are in your wallet.") } /// Notify the buyer that their order was refunded. #[onevent(orders::OrderRefunded)] public onRefunded(event: orders::OrderRefunded): void { - context::NotificationProvider.email(self.recipient(event.order.buyer), "Refund processed", "Your refund is on its way.") + context::NotificationProvider.email(recipient(event.order.buyer), "Refund processed", "Your refund is on its way.") } /// The contact for an admission token's attendee. diff --git a/web-ide/src/lib/samples/acme-tickets/orders.pds b/web-ide/src/lib/samples/acme-tickets/orders.pds index 35ebf844..66cc4824 100644 --- a/web-ide/src/lib/samples/acme-tickets/orders.pds +++ b/web-ide/src/lib/samples/acme-tickets/orders.pds @@ -105,13 +105,13 @@ component Reservation for orders::Orders { if (sellable.isErr) { return Err(orders::ReserveError::NotOnSale) } - within = Result from self.withinLimit(req.quantity) + within = Result from withinLimit(req.quantity) if (within.isErr) { return Err(orders::ReserveError::ExceedsLimit) } held = Result from inventory::Inventory.reserve(req.event, req.tier, req.quantity, buyer.value.id) if (held.isErr) { - return Err(self.unavailable(held.error)) + return Err(unavailable(held.error)) } quote = Result from pricing::Pricing.quote(req.event, req.tier, req.quantity) if (quote.isErr) { @@ -170,9 +170,9 @@ component OrderService for orders::Orders { orders::OrderStore.save(order) charged = Result from payments::Payments.charge(id, locked.value.total, req.method) if (charged.isErr) { - return Err(self.afterChargeFailure(order, charged.error)) + return Err(afterChargeFailure(order, charged.error)) } - paid = Order from self.mark(order, orders::OrderStatus::Charged) + paid = Order from mark(order, orders::OrderStatus::Charged) orders::OrderStore.save(paid) allocated = Result from inventory::Inventory.confirm(req.hold) if (allocated.isErr) { @@ -181,10 +181,10 @@ component OrderService for orders::Orders { return Err(RefundFailed from { id }) } inventory::Inventory.release(req.hold) - orders::OrderStore.save(self.mark(order, orders::OrderStatus::Refunded)) + orders::OrderStore.save(mark(order, orders::OrderStatus::Refunded)) return Err(orders::CheckoutError::AllocationFailed) } - confirmed = Order from self.mark(paid, orders::OrderStatus::Confirmed) + confirmed = Order from mark(paid, orders::OrderStatus::Confirmed) orders::OrderStore.save(confirmed) done = OrderConfirmed from { confirmed, buyer.value.id, allocated.value } orders::Events.confirmed(done) @@ -198,13 +198,13 @@ component OrderService for orders::Orders { /// real outcome against the provider rather than guessing. Treating a timeout as /// a decline is how a charged buyer loses their seat with no record. afterChargeFailure(order: orders::Order, e: payments::PaymentError): orders::CheckoutError { - sure = Result from self.definite(e) + sure = Result from definite(e) if (sure.isErr) { return orders::CheckoutError::PaymentUncertain } inventory::Inventory.release(order.hold) - orders::OrderStore.save(self.mark(order, orders::OrderStatus::Failed)) - return self.declined(e) + orders::OrderStore.save(mark(order, orders::OrderStatus::Failed)) + return declined(e) } /// Refund a confirmed order (support- or expiry-driven): reverse the charge, @@ -212,11 +212,11 @@ component OrderService for orders::Orders { /// order is not marked refunded and the seats are not freed until the money /// is actually returned. public refund(id: shared::OrderId): Result { - order = Result from self.get(id) + order = Result from get(id) if (order.isErr) { return Err(order.error) } - ready = Result from self.refundable(order.value) + ready = Result from refundable(order.value) if (ready.isErr) { return Err(ready.error) } @@ -225,7 +225,7 @@ component OrderService for orders::Orders { return Err(RefundFailed from { id }) } inventory::Inventory.releaseSold(order.value.hold) - refunded = Order from self.mark(order.value, orders::OrderStatus::Refunded) + refunded = Order from mark(order.value, orders::OrderStatus::Refunded) orders::OrderStore.save(refunded) event = OrderRefunded from { refunded } orders::Events.refunded(event) @@ -239,7 +239,7 @@ component OrderService for orders::Orders { public onEventCancelled(event: catalog::EventCancelled): void { affected = Order[] from orders::OrderStore.confirmedFor(event.event) for (order in affected) { - self.refund(order.id) + refund(order.id) } } @@ -252,16 +252,16 @@ component OrderService for orders::Orders { /// charge might be real, so a buyer is never failed after being charged. Every /// branch is idempotent. public resume(id: shared::OrderId): void { - order = Result from self.get(id) + order = Result from get(id) if (order.isOk) { payments::Payments.reconcileCharge(id) receipt = Option from payments::Payments.receiptFor(id) if (receipt.isSome) { - self.complete(order.value) + complete(order.value) } else { failed = Result from payments::Payments.definitelyFailed(id) if (failed.isOk) { - self.abandon(order.value) + abandon(order.value) } } } @@ -277,7 +277,7 @@ component OrderService for orders::Orders { complete(order: orders::Order): void { allocated = Result from inventory::Inventory.confirm(order.hold) if (allocated.isOk) { - confirmed = Order from self.mark(order, orders::OrderStatus::Confirmed) + confirmed = Order from mark(order, orders::OrderStatus::Confirmed) orders::OrderStore.save(confirmed) done = OrderConfirmed from { confirmed, confirmed.buyer, allocated.value } orders::Events.confirmed(done) @@ -287,7 +287,7 @@ component OrderService for orders::Orders { /// Abandon an order whose charge never completed: release the hold, fail it. abandon(order: orders::Order): void { inventory::Inventory.release(order.hold) - orders::OrderStore.save(self.mark(order, orders::OrderStatus::Failed)) + orders::OrderStore.save(mark(order, orders::OrderStatus::Failed)) } /// Read an order, or `OrderNotFound`. @@ -302,11 +302,11 @@ component OrderService for orders::Orders { /// Read an order on behalf of `owner`, refusing one that belongs to another /// user. A non-owner is told `OrderNotFound`, never that the order exists. public getFor(owner: shared::UserId, id: shared::OrderId): Result { - order = Result from self.get(id) + order = Result from get(id) if (order.isErr) { return Err(order.error) } - owns = Result from self.ownedBy(order.value, owner) + owns = Result from ownedBy(order.value, owner) if (owns.isErr) { return Err(owns.error) } diff --git a/web-ide/src/lib/samples/acme-tickets/payments.pds b/web-ide/src/lib/samples/acme-tickets/payments.pds index 5fe922af..5780b210 100644 --- a/web-ide/src/lib/samples/acme-tickets/payments.pds +++ b/web-ide/src/lib/samples/acme-tickets/payments.pds @@ -89,24 +89,24 @@ component PaymentService for payments::Payments { /// calling the provider again. /// #headline public charge(order: shared::OrderId, amount: shared::Money, method: payments::PaymentMethod): Result { - existing = Option from self.receiptFor(order) + existing = Option from receiptFor(order) if (existing.isSome) { return Ok(existing.value) } - key = IdempotencyKey from self.keyFor(order, "charge") + key = IdempotencyKey from keyFor(order, "charge") payment = Payment from { order, amount, payments::PaymentStatus::Charging } payments::PaymentStore.save(payment) held = Result from payments::Gateway.authorize(amount, method, key) if (held.isErr) { - self.onChargeError(payment, held.error) + onChargeError(payment, held.error) return Err(held.error) } taken = Result from payments::Gateway.capture(held.value.ref, amount, key) if (taken.isErr) { - self.onCaptureError(payment, held.value.ref, key, taken.error) + onCaptureError(payment, held.value.ref, key, taken.error) return Err(taken.error) } - charged = Payment from self.markCharged(payment, held.value, taken.value) + charged = Payment from markCharged(payment, held.value, taken.value) payments::PaymentStore.save(charged) return Ok(Receipt from { charged.id, taken.value.ref }) } @@ -118,16 +118,16 @@ component PaymentService for payments::Payments { /// is a deliberate scope cut, not an oversight — refunds are off the surge hot /// path and rarely contended. public refund(order: shared::OrderId): Result { - loaded = Result from self.loadCharged(order) + loaded = Result from loadCharged(order) if (loaded.isErr) { return Err(loaded.error) } - key = IdempotencyKey from self.keyFor(order, "refund") + key = IdempotencyKey from keyFor(order, "refund") back = Result from payments::Gateway.refund(loaded.value.capture, loaded.value.amount, key) if (back.isErr) { return Err(back.error) } - refunded = Payment from self.recordRefund(order) + refunded = Payment from recordRefund(order) payments::PaymentStore.save(refunded) return Ok(refunded) } @@ -139,9 +139,9 @@ component PaymentService for payments::Payments { public settle(order: shared::OrderId, ref: context::ProviderCaptureRef): void { payment = Option from payments::PaymentStore.byOrder(order) if (payment.isSome) { - awaiting = Result from self.awaitingSettlement(payment.value) + awaiting = Result from awaitingSettlement(payment.value) if (awaiting.isOk) { - payments::PaymentStore.save(self.applySettlement(payment.value, ref)) + payments::PaymentStore.save(applySettlement(payment.value, ref)) } } } @@ -153,11 +153,11 @@ component PaymentService for payments::Payments { public reconcile(order: shared::OrderId): void { payment = Option from payments::PaymentStore.byOrder(order) if (payment.isSome) { - awaiting = Result from self.awaitingSettlement(payment.value) + awaiting = Result from awaitingSettlement(payment.value) if (awaiting.isOk) { truth = Option from payments::Gateway.lookup(order) if (truth.isSome) { - payments::PaymentStore.save(self.applySettlement(payment.value, truth.value)) + payments::PaymentStore.save(applySettlement(payment.value, truth.value)) } } } @@ -170,15 +170,15 @@ component PaymentService for payments::Payments { /// outcome still unknown is left for a later sweep. This is what lets the order /// saga recover a timed-out checkout instead of guessing. public reconcileCharge(order: shared::OrderId): void { - done = Option from self.receiptFor(order) + done = Option from receiptFor(order) if (done.isNone) { truth = Option from payments::Gateway.lookupCharge(order) if (truth.isSome) { - payments::PaymentStore.save(self.recordCharge(order, truth.value)) + payments::PaymentStore.save(recordCharge(order, truth.value)) } else { gone = Result from payments::Gateway.chargeDefinitelyMissing(order) if (gone.isOk) { - payments::PaymentStore.save(self.recordChargeFailed(order)) + payments::PaymentStore.save(recordChargeFailed(order)) } } } @@ -195,9 +195,9 @@ component PaymentService for payments::Payments { /// On an authorise failure: a DEFINITE decline marks the payment `Failed`; an /// INDETERMINATE outcome leaves it `Charging` for `reconcileCharge` to resolve. onChargeError(payment: payments::Payment, e: payments::PaymentError): void { - sure = Result from self.isDefinite(e) + sure = Result from isDefinite(e) if (sure.isOk) { - payments::PaymentStore.save(self.markFailed(payment, e)) + payments::PaymentStore.save(markFailed(payment, e)) } } @@ -205,10 +205,10 @@ component PaymentService for payments::Payments { /// the payment `Failed`; an INDETERMINATE outcome leaves both intact and the /// payment `Charging`, because the capture may actually have settled. onCaptureError(payment: payments::Payment, auth: context::ProviderAuthRef, key: payments::IdempotencyKey, e: payments::PaymentError): void { - sure = Result from self.isDefinite(e) + sure = Result from isDefinite(e) if (sure.isOk) { payments::Gateway.cancelAuth(auth, key) - payments::PaymentStore.save(self.markFailed(payment, e)) + payments::PaymentStore.save(markFailed(payment, e)) } } @@ -245,7 +245,7 @@ public component Gateway for payments::Payments { public authorize(amount: shared::Money, method: payments::PaymentMethod, key: payments::IdempotencyKey): Result { held = Result from context::PaymentProvider.authorize(amount, method, key) if (held.isErr) { - return Err(self.translate(held.error)) + return Err(translate(held.error)) } return Ok(held.value) } @@ -254,7 +254,7 @@ public component Gateway for payments::Payments { public capture(auth: context::ProviderAuthRef, amount: shared::Money, key: payments::IdempotencyKey): Result { taken = Result from context::PaymentProvider.capture(auth, amount, key) if (taken.isErr) { - return Err(self.translate(taken.error)) + return Err(translate(taken.error)) } return Ok(taken.value) } @@ -263,7 +263,7 @@ public component Gateway for payments::Payments { public refund(capture: context::ProviderCaptureRef, amount: shared::Money, key: payments::IdempotencyKey): Result { back = Result from context::PaymentProvider.refund(capture, amount, key) if (back.isErr) { - return Err(self.translate(back.error)) + return Err(translate(back.error)) } return Ok(back.value) } @@ -298,7 +298,7 @@ public component WebhookHandler for payments::Payments { /// replayed webhook is acknowledged but changes nothing. #[http("POST /webhooks/payments")] public handle(event: payments::SettlementEvent): Result { - verified = Result from self.verify(event) + verified = Result from verify(event) if (verified.isErr) { return Err(verified.error) } diff --git a/web-ide/src/lib/samples/acme-tickets/pricing.pds b/web-ide/src/lib/samples/acme-tickets/pricing.pds index 5fbb59bc..429a5bbb 100644 --- a/web-ide/src/lib/samples/acme-tickets/pricing.pds +++ b/web-ide/src/lib/samples/acme-tickets/pricing.pds @@ -43,9 +43,9 @@ component Quotes for pricing::Pricing { if (base.isErr) { return Err(TierUnavailable from { tier }) } - surge = SurgeFactor from self.surgeFor(event, tier) - unit = shared::Money from self.apply(base.value, surge) - total = shared::Money from self.lineTotal(unit, quantity) + surge = SurgeFactor from surgeFor(event, tier) + unit = shared::Money from apply(base.value, surge) + total = shared::Money from lineTotal(unit, quantity) return Ok(Quote from { event, tier, unit, surge, total }) } @@ -56,9 +56,9 @@ component Quotes for pricing::Pricing { surgeFor(event: shared::EventId, tier: shared::TierId): pricing::SurgeFactor { snap = Option from pricing::Demand.snapshot(event, tier) if (snap.isNone) { - return self.neutral() + return neutral() } - return self.curve(snap.value.pressure) + return curve(snap.value.pressure) } /// The no-surge factor (multiplier 1.0) used when demand is unknown. @@ -100,7 +100,7 @@ public component Demand for pricing::Pricing { public onConfirmed(event: orders::OrderConfirmed): void { fresh = Option from pricing::DemandLog.seen(event.order.id) if (fresh.isNone) { - self.record(event.order.event, event.order.tier, event.order.quantity) + record(event.order.event, event.order.tier, event.order.quantity) pricing::DemandLog.mark(event.order.id) } } diff --git a/web-ide/src/lib/samples/acme-tickets/tickets.pds b/web-ide/src/lib/samples/acme-tickets/tickets.pds index 8021c990..98a31cbb 100644 --- a/web-ide/src/lib/samples/acme-tickets/tickets.pds +++ b/web-ide/src/lib/samples/acme-tickets/tickets.pds @@ -47,7 +47,7 @@ component Issuer for tickets::Tickets { /// loop silently. #[onevent(orders::OrderConfirmed)] public onConfirmed(event: orders::OrderConfirmed): void { - issued = Result from self.issue(event.order, event.allocation) + issued = Result from issue(event.order, event.allocation) if (issued.isErr) { tickets::Quarantine.stranded(event.order.id) } @@ -57,14 +57,14 @@ component Issuer for tickets::Tickets { /// cannot be redeemed at the gate (no paid-then-refunded double entry). #[onevent(orders::OrderRefunded)] public onRefunded(event: orders::OrderRefunded): void { - self.revoke(event.order.id) + revoke(event.order.id) } /// Mint the tickets for an order's allocation and deliver them to the buyer. /// Mints exactly the ticket ids the inventory allocation named, so credentials /// match the seats that left the pool. public issue(order: orders::Order, allocation: inventory::Allocation): Result { - minted = Ticket[] from self.mint(order, allocation) + minted = Ticket[] from mint(order, allocation) tickets::TicketStore.saveAll(minted) tickets::Delivery.deliver(order.buyer, minted) return Ok(minted) @@ -79,7 +79,7 @@ component Issuer for tickets::Tickets { revoke(order: shared::OrderId): void { held = Ticket[] from tickets::TicketStore.forOrder(order) for (ticket in held) { - tickets::TicketStore.save(self.markRevoked(ticket)) + tickets::TicketStore.save(markRevoked(ticket)) } } @@ -98,11 +98,11 @@ component Redemption for tickets::Tickets { if (found.isNone) { return Err(tickets::RedeemError::TicketNotFound) } - admit = Result from self.admissible(found.value) + admit = Result from admissible(found.value) if (admit.isErr) { return Err(admit.error) } - redeemed = Ticket from self.markRedeemed(found.value) + redeemed = Ticket from markRedeemed(found.value) tickets::TicketStore.save(redeemed) return Ok(redeemed) } diff --git a/web-ide/src/lib/samples/acme-tickets/waitingroom.pds b/web-ide/src/lib/samples/acme-tickets/waitingroom.pds index f6ba88b7..3ad6bad3 100644 --- a/web-ide/src/lib/samples/acme-tickets/waitingroom.pds +++ b/web-ide/src/lib/samples/acme-tickets/waitingroom.pds @@ -53,7 +53,7 @@ component Queue for waitingroom::WaitingRoom { /// Join the queue for an event. Refuses with `QueueClosed` when the queue is /// shedding load — the backpressure valve for a surge that outruns capacity. public join(event: shared::EventId, user: shared::UserId): Result { - open = Result from self.accepting(event) + open = Result from accepting(event) if (open.isErr) { return Err(open.error) } @@ -65,7 +65,7 @@ component Queue for waitingroom::WaitingRoom { /// position polls during a hot on-sale never hit the enqueue path. public position(ticket: waitingroom::QueueTicket): waitingroom::Position { snap = Option from waitingroom::QueueStore.snapshot(ticket.event) - return self.estimate(ticket, snap) + return estimate(ticket, snap) } /// `Err(QueueClosed)` when the queue is shedding load. @@ -84,7 +84,7 @@ component Admission for waitingroom::WaitingRoom { if (found.isNone) { return Err(waitingroom::WaitingError::AdmissionInvalid) } - return self.unexpired(found.value) + return unexpired(found.value) } /// `Err(AdmissionExpired)` if the token's window has closed. @@ -100,7 +100,7 @@ component Gatekeeper for waitingroom::WaitingRoom { /// of remaining inventory and in-flight checkouts, so admission never outruns /// what the inventory and payment paths can serve. public admitNext(event: shared::EventId): void { - room = number from self.headroom(event) + room = number from headroom(event) due = QueueTicket[] from waitingroom::QueueStore.dequeue(event, room) for (ticket in due) { token = AdmissionToken from waitingroom::AdmissionStore.grant(ticket) @@ -118,10 +118,10 @@ component Gatekeeper for waitingroom::WaitingRoom { headroom(event: shared::EventId): number { seats = Option from inventory::Inventory.snapshot(event) if (seats.isNone) { - return self.budget(0, 0) + return budget(0, 0) } inflight = number from waitingroom::AdmissionStore.outstandingAsOf(event, seats.value.version) - return self.budget(seats.value.available, inflight) + return budget(seats.value.available, inflight) } /// `max(0, seats - outstanding)` — the admission budget left. diff --git a/web-ide/static/pseudocode-skill.zip b/web-ide/static/pseudocode-skill.zip index 10e9f6ae..93fe390a 100644 Binary files a/web-ide/static/pseudocode-skill.zip and b/web-ide/static/pseudocode-skill.zip differ