@jabworks/condux 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jabworks/condux",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "OpenCode plugin for the condux workflow toolkit — bundles the condux skills and injects the specialist agents (coder, explorer, planner, researcher) plus an opt-in plan-review listener.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -347,8 +347,10 @@ const parsePathSegments = (d, warnCurve) => {
347
347
  // ---------------------------------------------------------------------------
348
348
  // Per-<svg>-block walk. A single left-to-right tag scan tracks a stack of
349
349
  // <g> offsets (and font-size inheritance), skips <defs>/<marker> subtrees
350
- // entirely, and special-cases <text>…</text> to pull out its raw content
351
- // (and any positioned <tspan> children) without a general text-node model.
350
+ // entirely — the skip survives any container nested inside them (<g>,
351
+ // <symbol>, …), since only the defs/marker frame itself may end it — and
352
+ // special-cases <text>…</text> to pull out its raw content (and any
353
+ // positioned <tspan> children) without a general text-node model.
352
354
  // ---------------------------------------------------------------------------
353
355
 
354
356
  const walkBlock = (source, blockOffset, blockSource, findLine, notify, styles) => {
@@ -379,9 +381,11 @@ const walkBlock = (source, blockOffset, blockSource, findLine, notify, styles) =
379
381
  let curFontSize = parseFloat(getAttr(svgOpenTag, 'font-size') ?? '') || null;
380
382
  let skipDepth = 0;
381
383
  const stack = [];
382
- // The frame a closing tag restores: offsets, inherited font size, and
383
- // whether the subtree was inside <defs>/<marker>.
384
- const snapshot = () => ({ prevX: curOffsetX, prevY: curOffsetY, prevFontSize: curFontSize, skip: skipDepth > 0 });
384
+ // The frame a closing tag restores: offsets and inherited font size.
385
+ // `skip` means "this frame incremented skipDepth", which only the
386
+ // defs/marker branch below ever does — a container opened inside a
387
+ // skipped subtree restores offsets on close, nothing more.
388
+ const snapshot = () => ({ prevX: curOffsetX, prevY: curOffsetY, prevFontSize: curFontSize, skip: false });
385
389
 
386
390
  const tagRe = /<\/?[a-zA-Z][^>]*>/g;
387
391
  let m;
@@ -14,9 +14,15 @@ of the `<style>` block, verbatim.
14
14
  Sizing below.
15
15
  - Palette is token roles, referenced with `var(--…)` (CSS variables work in
16
16
  inline SVG attributes): fills `var(--card)` / `var(--muted)`, strokes
17
- `var(--border)`, edges `var(--subtle)`, text `var(--foreground)`. The one
18
- accent is the `--primary` family — for the one thing the diagram is about
19
- (the new entity, the changed edge) — never more than one accent use.
17
+ `var(--border)`, edges `var(--subtle)`, text `var(--foreground)`. Node fills
18
+ and strokes come from the categorical set instead — see Visual Language.
19
+ - Accent: categorical colour is identity, never emphasis. `--primary` stays
20
+ the one accent, for the one thing the diagram is about (the new entity, the
21
+ changed edge): a 2.5px `var(--primary)` stroke and a `var(--primary-text)`
22
+ title over the node's role tint — never more than one accent use. Semantic
23
+ tokens (`--success`, `--warning`, `--destructive`, `--info`) are state only,
24
+ never role — a persistence-green node and a success-green node in one
25
+ diagram read as the same claim.
20
26
  - Text: `--sans` for titles, `--mono` for everything that is data — field
21
27
  rows, cardinalities, edge labels (D2). 13–14px labels, 11px annotations
22
28
  in `var(--muted-foreground)`. If a label doesn't fit its box, the box
@@ -49,6 +55,197 @@ of the `<style>` block, verbatim.
49
55
  - Every relationship line gets a label. An unlabeled arrow is a guess the
50
56
  reader has to make.
51
57
 
58
+ ## Visual Language
59
+
60
+ Three channels, one meaning each: **colour is role**, **mark is kind**, **dash
61
+ is protocol**. Two nodes in the same colour make a claim — that they play the
62
+ same part in the system — so colour is never picked for looks.
63
+
64
+ ### Roles
65
+
66
+ Fixed slots. A role always gets the same categorical token, in every diagram of
67
+ every design, because a per-diagram choice is decoration and a fixed table is a
68
+ language.
69
+
70
+ | Role | Slot | Applies to |
71
+ |---|---|---|
72
+ | frontend runtime | `--cat-4` | UI services, widget hosts, client-side stores |
73
+ | API / service | `--cat-2` | backend services, gateways, resolvers |
74
+ | persistence | `--cat-3` | databases, caches, object stores |
75
+ | external system | `--cat-7` | third parties, upstream sources, providers |
76
+ | messaging / queue / bus | `--cat-8` | brokers, streams, topics |
77
+ | actor / user | `--cat-6` | people and devices in flow diagrams |
78
+ | batch / scheduler | `--cat-5` | jobs, cron, pipelines |
79
+ | unassigned | `--cat-other` | anything the design has not named yet |
80
+
81
+ - Tint recipe, verbatim, as attributes on the tag:
82
+ `fill="var(--cat-N)" fill-opacity="0.10" stroke="var(--cat-N)"`.
83
+ Never move the stroke into a `.class` rule — the checker reads `stroke` off
84
+ the tag, and a class-styled node reads as a halo and vanishes from the check.
85
+ - At most five roles per diagram. A sixth means the diagram is answering two
86
+ questions (same rule as "Choosing the Shape") — split it, or fold the tail
87
+ roles into `--cat-other`.
88
+ - No new tokens. `--cat-1` … `--cat-8` and `--cat-other` are the whole set;
89
+ there is no muted variant, and inventing one crosses into the token core
90
+ every surface inherits.
91
+ - Opaque role fills are banned: body text loses contrast, worst on the dark
92
+ ground. `fill-opacity="0.10"` is the recipe, not a starting point.
93
+
94
+ ### Kinds
95
+
96
+ | Kind | Body | Mark | Border |
97
+ |---|---|---|---|
98
+ | service | rounded `<rect>` (`rx="6"`) | none | solid |
99
+ | store | rounded `<rect>` | `mark-store` | solid |
100
+ | external system | rounded `<rect>` | `mark-external` | dashed `6 4` |
101
+ | UI surface | rounded `<rect>` | `mark-ui` | solid |
102
+ | actor / user | pill `<rect>` (`rx` = height / 2) | `mark-actor` | solid |
103
+ | queue / bus | rounded `<rect>` | `mark-queue` | solid |
104
+ | batch / scheduler | rounded `<rect>` | `mark-batch` | solid |
105
+ | boundary | dashed `<rect>` behind its members | title strip | dashed `6 4` |
106
+
107
+ - **Invariant: a node's body is exactly one stroked `<rect>`.** Everything else
108
+ on it — halo, title strip, glyph — is a stroke-less `<rect>` or a `<use>`.
109
+ The checker reads `stroke` from the tag and treats a stroke-less rect as
110
+ decoration, so the one stroked rect *is* the node's footprint: a second
111
+ stroked rect inside it reclassifies the node as a boundary, and a body drawn
112
+ as a cylinder, hexagon or ellipse is invisible to the check — a false
113
+ `clean`, not a clever shape.
114
+ - No footprint-changing geometry. Kind is carried by the mark and the border,
115
+ never by a new body shape.
116
+
117
+ ### Marks
118
+
119
+ Paste this into the `<defs>` beside the arrow marker. `<defs>` content is never
120
+ walked by the checker, so a mark path is a glyph; the same path drawn in the
121
+ body of the svg is read as an unlabeled edge.
122
+
123
+ ```html
124
+ <g id="mark-store"><path d="M1,4 a8,3 0 1,0 16,0 a8,3 0 1,0 -16,0 M1,4 V13 a8,3 0 0,0 16,0 V4" fill="none" stroke="currentColor" stroke-width="1.3"/></g>
125
+ <g id="mark-external"><path d="M3,12 L15,3 M7,3 h8 v8" fill="none" stroke="currentColor" stroke-width="1.3"/></g>
126
+ <g id="mark-ui"><path d="M1,1 h16 v13 h-16 z M1,5 h16" fill="none" stroke="currentColor" stroke-width="1.3"/></g>
127
+ <g id="mark-actor"><path d="M9,2 a3,3 0 1,0 0.01,0 M2,15 a7,5 0 0,1 14,0" fill="none" stroke="currentColor" stroke-width="1.3"/></g>
128
+ <g id="mark-queue"><path d="M1,3 h12 M3,8 h12 M5,13 h12" fill="none" stroke="currentColor" stroke-width="1.3"/></g>
129
+ <g id="mark-batch"><path d="M2,8 a7,7 0 1,0 14,0 a7,7 0 1,0 -14,0 M9,4 V8 h4" fill="none" stroke="currentColor" stroke-width="1.3"/></g>
130
+ ```
131
+
132
+ - Every glyph is drawn on an 18×16 grid. Six marks for eight kinds: a service
133
+ has no mark (it is the unmarked default) and a boundary uses its title strip.
134
+ - Placement: `<use href="#mark-store" x="…" y="…" width="18" height="16"
135
+ style="color:var(--cat-N)"/>`, 14px in from the node's top-left — `x` =
136
+ node `x` + 14, `y` = node `y` + 12, the two extra pixels of headroom keeping
137
+ the glyph clear of the `rx="6"` corner. `currentColor` picks up the
138
+ `style="color:…"`, so a mark is always its node's role colour.
139
+ - A `<use>` has no footprint the checker can see, so a mark never collides
140
+ with anything. It can still collide visually: keep the node's title clear of
141
+ the glyph's 18px column.
142
+
143
+ ### Edges
144
+
145
+ | Edge means | Stroke width | Dash |
146
+ |---|---|---|
147
+ | synchronous call (HTTP, gRPC, GraphQL) | 1.5 | solid |
148
+ | in-process hand-off within a boundary | 1 | solid |
149
+ | asynchronous message or event (queue, stream, topic) | 1.5 | `6 4` |
150
+ | file, batch, or adapter protocol transfer | 1.5 | `2 3` |
151
+
152
+ - Stroke is always `var(--subtle)`. Colour never goes on an edge: colour is
153
+ role identity, and two channels on one line reads as noise.
154
+ - Everything from Layout and Routing still holds — the shared arrowhead, one
155
+ haloed `--mono` 11px label per segment, orthogonal paths, distinct ports. A
156
+ two-way edge adds `marker-start`; flow diagrams keep their hop numbers.
157
+ - Halo fill is `var(--card)`, everywhere (Layout and Routing rule 5).
158
+ Boundaries in this language are unfilled — only the title strip is tinted —
159
+ so the ground under every label is the frame's card surface.
160
+
161
+ ### Boundaries
162
+
163
+ - Body: a dashed `<rect>` in the boundary's role colour,
164
+ `fill="none" stroke="var(--cat-N)" stroke-dasharray="6 4" rx="4"`, drawn
165
+ behind its members.
166
+ - Title strip: a stroke-less tinted `<rect>` at the top of the boundary, full
167
+ width, **48 tall**, `fill="var(--cat-N)" fill-opacity="0.10" rx="4"`. It has
168
+ no stroke, so the checker reads it as decoration, not a nested node.
169
+ - Title inside the strip: `font-size="14" font-weight="600"
170
+ letter-spacing="0.04em"` in `var(--cat-N)`, 24px in from the boundary's left
171
+ edge, baseline at strip top + 30. A title strip makes a region scannable
172
+ zoomed out; a bare corner label reads at close range only.
173
+ - Members start **≥ 45px below the strip's bottom edge** — boundary top + 93.
174
+ Labels and their halos need only clear the band: halo top ≥ strip bottom.
175
+ A halo in the strip punches a `var(--card)` hole through the tint, so a
176
+ corridor that would put one there moves down, never the strip.
177
+
178
+ ### Legend
179
+
180
+ An HTML block under the `<svg>`, still inside `.frame` — never inside the svg,
181
+ where the checker scans it and stroked swatches become nodes. At most four
182
+ rows (role, kind, edge, accent), and only entries the diagram actually
183
+ contains: a legend row you cannot point at in the drawing is the five-role cap
184
+ failing out loud. The block below is the legend for the worked fragment at the
185
+ end of this section — three roles, three kinds, two edges, the accent.
186
+
187
+ ```html
188
+ <div class="legend">
189
+ <div><b>role</b><span><span class="sw" style="background:var(--cat-4);opacity:.35;border:1px solid var(--cat-4)"></span> frontend runtime</span><span><span class="sw" style="background:var(--cat-2);opacity:.35;border:1px solid var(--cat-2)"></span> API / service</span><span><span class="sw" style="background:var(--cat-8);opacity:.35;border:1px solid var(--cat-8)"></span> messaging</span></div>
190
+ <div><b>kind</b><span><span class="sw" style="border:1px solid var(--border);border-radius:4px"></span> service</span><span><svg width="18" height="16"><use href="#mark-store" width="18" height="16"/></svg> store</span><span><svg width="18" height="16"><use href="#mark-queue" width="18" height="16"/></svg> queue / bus</span></div>
191
+ <div><b>edge</b><span><i class="ln" style="border-top:1.5px solid var(--subtle)"></i> sync call</span><span><i class="ln" style="border-top:1.5px dashed var(--subtle)"></i> async message</span></div>
192
+ <div><b>accent</b><span><span class="sw" style="border:2.5px solid var(--primary);border-radius:4px"></span> the one thing this diagram is about</span></div>
193
+ </div>
194
+ <style>
195
+ .legend { display: grid; gap: var(--space-2); font-family: var(--mono); font-size: var(--text-xs); color: var(--muted-foreground); margin-top: var(--space-4); }
196
+ .legend b { color: var(--foreground); margin-right: var(--space-3); display: inline-block; width: 6ch; }
197
+ .legend span { margin-right: var(--space-4); display: inline-flex; align-items: center; gap: 6px; }
198
+ .legend .sw { display: inline-block; width: 14px; height: 14px; }
199
+ .legend .ln { display: inline-block; width: 28px; height: 0; }
200
+ </style>
201
+ ```
202
+
203
+ - The kind swatches `<use>` the same mark ids: one inline `<svg>` per row
204
+ entry, resolving against the `<defs>` in the diagram above it (same
205
+ document, so the fragment reference works).
206
+ - A CSS border cannot carry an SVG dash array, so an edge swatch approximates:
207
+ `6 4` is `dashed`, `2 3` is `dotted`. At 28px they read the same. Add the
208
+ `2 3` row only when the diagram has a file / batch / adapter edge in it.
209
+
210
+ ### A worked fragment
211
+
212
+ Two boundaries, four nodes, two protocols. Gutters are 72+, every edge is
213
+ orthogonal and leaves a 50% port, every label is haloed and clear of both strip
214
+ bands, and every node body is one stroked rect.
215
+
216
+ ```html
217
+ <!-- needs the arrow marker and the mark <defs> above. viewBox 0 0 692 352 -->
218
+ <rect x="20" y="20" width="280" height="312" rx="4" fill="none" stroke="var(--cat-4)" stroke-dasharray="6 4"/>
219
+ <rect x="20" y="20" width="280" height="48" rx="4" fill="var(--cat-4)" fill-opacity="0.10"/>
220
+ <text x="44" y="50" font-family="var(--sans)" font-size="14" font-weight="600" letter-spacing="0.04em" fill="var(--cat-4)">FRONTEND RUNTIME</text>
221
+ <rect x="392" y="20" width="280" height="312" rx="4" fill="none" stroke="var(--cat-2)" stroke-dasharray="6 4"/>
222
+ <rect x="392" y="20" width="280" height="48" rx="4" fill="var(--cat-2)" fill-opacity="0.10"/>
223
+ <text x="416" y="50" font-family="var(--sans)" font-size="14" font-weight="600" letter-spacing="0.04em" fill="var(--cat-2)">BACKEND REPORTING API</text>
224
+ <!-- members start at y=116, 48 below the strip that ends at y=68 (45 is the floor) -->
225
+ <rect x="44" y="116" width="232" height="56" rx="6" fill="var(--cat-4)" fill-opacity="0.10" stroke="var(--cat-4)"/>
226
+ <text x="160" y="150" text-anchor="middle" font-family="var(--sans)" font-size="14" font-weight="600" fill="var(--foreground)">Filter + query planner</text>
227
+ <rect x="44" y="252" width="232" height="56" rx="6" fill="var(--cat-4)" fill-opacity="0.10" stroke="var(--cat-4)"/>
228
+ <use href="#mark-store" x="58" y="264" width="18" height="16" style="color:var(--cat-4)"/>
229
+ <text x="160" y="286" text-anchor="middle" font-family="var(--sans)" font-size="14" font-weight="600" fill="var(--foreground)">Frontend catalogs</text>
230
+ <!-- the accent: role tint kept, 2.5px primary stroke, primary-text title -->
231
+ <rect x="416" y="116" width="232" height="56" rx="6" fill="var(--cat-2)" fill-opacity="0.10" stroke="var(--primary)" stroke-width="2.5"/>
232
+ <text x="532" y="150" text-anchor="middle" font-family="var(--sans)" font-size="14" font-weight="600" fill="var(--primary-text)">Reporting query API</text>
233
+ <rect x="416" y="252" width="232" height="56" rx="6" fill="var(--cat-8)" fill-opacity="0.10" stroke="var(--cat-8)"/>
234
+ <use href="#mark-queue" x="430" y="264" width="18" height="16" style="color:var(--cat-8)"/>
235
+ <text x="532" y="286" text-anchor="middle" font-family="var(--sans)" font-size="14" font-weight="600" fill="var(--foreground)">Export queue</text>
236
+ <!-- sync call: 1.5 solid, right 50% port to left 50% port, label 4px above -->
237
+ <path d="M 276 144 H 416" fill="none" stroke="var(--subtle)" stroke-width="1.5" marker-end="url(#arrow)"/>
238
+ <rect x="306" y="129" width="81" height="15" fill="var(--card)"/>
239
+ <text x="346" y="140" text-anchor="middle" font-family="var(--mono)" font-size="11" fill="var(--muted-foreground)">POST /query</text>
240
+ <!-- async message: 1.5 dashed 6 4, bottom 50% port to top 50% port -->
241
+ <path d="M 532 172 V 252" fill="none" stroke="var(--subtle)" stroke-width="1.5" stroke-dasharray="6 4" marker-end="url(#arrow)"/>
242
+ <rect x="465" y="205" width="134" height="15" fill="var(--card)"/>
243
+ <text x="532" y="216" text-anchor="middle" font-family="var(--mono)" font-size="11" fill="var(--muted-foreground)">queue: report.ready</text>
244
+ ```
245
+
246
+ Three roles, two kinds marked, one accent — the `### Legend` block above is
247
+ this fragment's legend, verbatim.
248
+
52
249
  ## Layout and Routing
53
250
 
54
251
  1. *Grid first.* Assign every node a column and a row before drawing anything.
@@ -72,8 +269,8 @@ of the `<style>` block, verbatim.
72
269
  `--mono` 11px: on a horizontal segment it sits 4px above the line, on a
73
270
  vertical segment it is centred on the line over its halo.
74
271
  Every label gets a halo so a crossing edge stays legible: a stroke-less
75
- `<rect>` in the fill the label sits on (`var(--background)` on open
76
- canvas, `var(--muted)` inside a filled boundary) drawn behind the text —
272
+ `<rect>` in `var(--card)` — the frame ground every diagram sits on — drawn
273
+ behind the text —
77
274
  the checker ignores stroke-less rects, so the halo is never mistaken for a
78
275
  node. Size the halo from the same budget as the label — characters × 11 ×
79
276
  0.6, plus 4px each side — a halo narrower than its label lets the crossing
@@ -152,9 +349,15 @@ labels carry cardinality (`1..*`, `0..1`).
152
349
  <text x="290" y="72" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">1..*</text>
153
350
  ```
154
351
 
155
- An accented entity (the diagram's one accent) swaps its title-bar fill to
156
- `var(--primary-muted)`, its strokes to `var(--primary)`, and its title text to
157
- `var(--primary-text)`.
352
+ Visual Language on this family: the title bar carries the tint of the entity's
353
+ *owning* role (`fill="var(--cat-N)" fill-opacity="0.10"`, and `stroke="var(--cat-N)"`
354
+ on both rects), and the kind mark sits 14px in from the title bar's top-left —
355
+ so a reader sees which service owns which table before reading a field name.
356
+ The bar and the body are siblings, never nested: a bar drawn *inside* the body
357
+ rect makes the checker read the whole entity as a boundary.
358
+
359
+ An accented entity (the diagram's one accent) keeps its role tint and swaps its
360
+ strokes to 2.5px `var(--primary)` and its title text to `var(--primary-text)`.
158
361
 
159
362
  ## 2. Request / Data Flow
160
363
 
@@ -162,12 +365,18 @@ Actors and stores as boxes on one horizontal band; numbered edges show
162
365
  sequence. Number every hop (`1. POST /orders`, `2. validate`, `3. INSERT`) —
163
366
  the numbers are what make it a flow rather than a topology.
164
367
 
368
+ Visual Language on this family: actors on the band are pill rects
369
+ (`rx` = height / 2) in `--cat-6` with `mark-actor`, stores are rounded rects
370
+ with `mark-store` in their owning role's slot, and the hop numbers stay — kind
371
+ and colour say *what* each box is, the numbers still say *when*.
372
+
165
373
  ## 3. Architecture (boxes-and-arrows)
166
374
 
167
- Services as rounded rects (`rx="6"`), external systems as rects with a dashed
168
- stroke (`stroke-dasharray="4 3"`), boundaries (network, process, trust) as a
169
- large dashed `<rect>` behind its members with the boundary name in the top-left
170
- corner. Edges labeled with protocol or payload (`HTTP`, `queue: order.created`).
375
+ The family the Visual Language section is written for — it uses every channel:
376
+ services, stores, externals, UI surfaces, queues and batch jobs from the kinds
377
+ table; boundaries (network, process, trust) as large dashed rects with a title
378
+ strip; edges dashed by protocol and labeled with the protocol or payload
379
+ (`HTTP`, `queue: order.created`). Take every rule and both fences from there.
171
380
 
172
381
  ## 4. State Machine
173
382
 
@@ -176,6 +385,12 @@ with a filled dot and an arrow, terminal states with a double border (nested
176
385
  `<rect>` inset by 3px). Every transition label is the *event* that causes it,
177
386
  not a description of the target state.
178
387
 
388
+ Visual Language on this family: no role colour and no marks — a state is not a
389
+ component, so a categorical fill would claim a kinship that does not exist.
390
+ Neutral bodies (`var(--card)` / `var(--border)`), `--primary` on the one state
391
+ the diagram is about, and semantic tokens where a state *is* a state
392
+ (`--success` on settled, `--destructive` on failed).
393
+
179
394
  ## Choosing the Shape
180
395
 
181
396
  | The question being asked | Shape |