c4studio 0.2.0__tar.gz → 0.4.0__tar.gz

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.
Files changed (64) hide show
  1. c4studio-0.4.0/PKG-INFO +587 -0
  2. c4studio-0.4.0/README.md +555 -0
  3. c4studio-0.4.0/pyproject.toml +186 -0
  4. c4studio-0.4.0/src/c4studio/cli/main.py +890 -0
  5. c4studio-0.4.0/src/c4studio/diff.py +417 -0
  6. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/generators/flowchart.py +0 -1
  7. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/generators/json_export.py +20 -3
  8. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/generators/mermaid.py +1 -3
  9. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/generators/mermaid_common.py +0 -1
  10. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/graph/view_graph.py +567 -25
  11. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/icons.py +36 -1
  12. c4studio-0.4.0/src/c4studio/impact.py +288 -0
  13. c4studio-0.4.0/src/c4studio/layout_sidecar.py +342 -0
  14. c4studio-0.4.0/src/c4studio/lint.py +437 -0
  15. c4studio-0.4.0/src/c4studio/markdown.py +259 -0
  16. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/elements.py +0 -1
  17. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/enums.py +0 -1
  18. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/views.py +47 -41
  19. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/workspace.py +9 -6
  20. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/docs.py +0 -1
  21. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/dsl.py +960 -114
  22. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/expressions.py +52 -12
  23. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/json_parser.py +23 -18
  24. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/locations.py +1 -2
  25. c4studio-0.4.0/src/c4studio/publish.py +479 -0
  26. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/render.py +87 -1
  27. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/renderer/diagram-render.mjs +1201 -760
  28. c4studio-0.4.0/src/c4studio/templates/deployment.dsl +58 -0
  29. c4studio-0.4.0/src/c4studio/templates/full-c4.dsl +61 -0
  30. c4studio-0.4.0/src/c4studio/templates/minimal.dsl +18 -0
  31. c4studio-0.4.0/src/c4studio/templates/system-context.dsl +39 -0
  32. c4studio-0.4.0/src/c4studio/templates.py +111 -0
  33. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/themes.py +9 -3
  34. c4studio-0.4.0/src/c4studio/webapp/assistant.py +211 -0
  35. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/webapp/graph.py +90 -4
  36. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/webapp/model_graph.py +16 -12
  37. c4studio-0.4.0/src/c4studio/webapp/perspectives.py +164 -0
  38. c4studio-0.4.0/src/c4studio/webapp/server.py +2001 -0
  39. c4studio-0.4.0/src/c4studio/webapp/static/assets/index-BWKhqzG-.js +95 -0
  40. c4studio-0.4.0/src/c4studio/webapp/static/assets/index-CJj5SYQu.css +1 -0
  41. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/webapp/static/index.html +2 -2
  42. c4studio-0.2.0/PKG-INFO +0 -283
  43. c4studio-0.2.0/README.md +0 -258
  44. c4studio-0.2.0/pyproject.toml +0 -73
  45. c4studio-0.2.0/src/c4studio/cli/main.py +0 -329
  46. c4studio-0.2.0/src/c4studio/webapp/server.py +0 -713
  47. c4studio-0.2.0/src/c4studio/webapp/static/assets/index-CibNm4nc.js +0 -68
  48. c4studio-0.2.0/src/c4studio/webapp/static/assets/index-Px2v-U7I.css +0 -1
  49. {c4studio-0.2.0 → c4studio-0.4.0}/LICENSE +0 -0
  50. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/__init__.py +7 -7
  51. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/__main__.py +0 -0
  52. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/cli/__init__.py +0 -0
  53. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/diagnostics.py +0 -0
  54. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/generators/__init__.py +0 -0
  55. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/graph/__init__.py +0 -0
  56. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/__init__.py +45 -45
  57. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/deployment.py +0 -0
  58. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/documentation.py +0 -0
  59. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/__init__.py +0 -0
  60. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/implied.py +0 -0
  61. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/sourcemap.py +0 -0
  62. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/py.typed +0 -0
  63. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/webapp/__init__.py +0 -0
  64. {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/webapp/loader.py +0 -0
@@ -0,0 +1,587 @@
1
+ Metadata-Version: 2.4
2
+ Name: c4studio
3
+ Version: 0.4.0
4
+ Summary: Parse Structurizr DSL/JSON and generate C4 diagrams in Mermaid, with a CLI and a React web app
5
+ Keywords: structurizr,c4,architecture,diagrams,dsl
6
+ Author: Geoffrey Koh
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Topic :: Documentation
14
+ Classifier: Topic :: Software Development :: Documentation
15
+ Requires-Dist: click>=8.1
16
+ Requires-Dist: pydantic>=2.0
17
+ Requires-Dist: fastapi>=0.115
18
+ Requires-Dist: uvicorn[standard]>=0.30
19
+ Requires-Dist: anthropic>=1.0 ; extra == 'assistant'
20
+ Requires-Dist: pytest>=8.0 ; extra == 'dev'
21
+ Requires-Dist: pytest-cov>=5.0 ; extra == 'dev'
22
+ Requires-Dist: anthropic>=1.0 ; extra == 'dev'
23
+ Requires-Python: >=3.13
24
+ Project-URL: Homepage, https://github.com/geoffkoh/c4studio
25
+ Project-URL: Repository, https://github.com/geoffkoh/c4studio
26
+ Project-URL: Documentation, https://github.com/geoffkoh/c4studio/blob/main/docs/dsl-reference.md
27
+ Project-URL: Issues, https://github.com/geoffkoh/c4studio/issues
28
+ Project-URL: Migration guide, https://github.com/geoffkoh/c4studio/blob/main/docs/MIGRATION.md
29
+ Provides-Extra: assistant
30
+ Provides-Extra: dev
31
+ Description-Content-Type: text/markdown
32
+
33
+ # c4studio
34
+
35
+ Python implementation of [Structurizr](https://structurizr.com/) for architecture
36
+ modeling and C4 diagram generation — with a local-first **Studio** for writing
37
+ the DSL and watching the diagram follow.
38
+
39
+ ## Install
40
+ Published on PyPI as **`c4studio`**; the import package is `c4studio`
41
+ and the command is `c4`:
42
+
43
+ ```bash
44
+ pipx install c4studio # or: pip install c4studio
45
+ c4 new my-architecture.dsl # start from a template
46
+ c4 webapp my-architecture.dsl # edit it, and see it
47
+
48
+ # or run without installing:
49
+ uvx --from c4studio c4 webapp my-architecture.dsl
50
+ ```
51
+
52
+ Requires Python 3.13+ (uv/uvx can provision it automatically).
53
+
54
+ ## Features
55
+ - ✅ Full Structurizr metamodel support (C4 architecture model)
56
+ - ✅ DSL and JSON parsing
57
+ - ✅ **In-browser DSL editor** — syntax highlighting, autocomplete, live
58
+ diagnostics, save straight to disk, diagram beside the text
59
+ - ✅ **Searchable file tree** with create, rename and delete
60
+ - ✅ **Starter templates** and `c4 new`
61
+ - ✅ Mermaid diagram generation — C4 syntax or `flowchart`/`subgraph`
62
+ - ✅ Headless SVG rendering for CI and docs-as-code
63
+ - ✅ Comprehensive type hints
64
+ - ✅ **Perspectives** — annotate elements and relationships with security,
65
+ ownership or anything else, then show one over the diagram
66
+ - ✅ **`c4 lint`** — model standards in CI: orphans, missing descriptions,
67
+ duplicate relationships, styles that match nothing
68
+ - ✅ Custom properties on all elements
69
+ - ✅ Deployment infrastructure modeling
70
+ - ✅ Style and configuration management
71
+ - ⚙️ Optional AI assistant — off by default, and the only feature that
72
+ uses the network
73
+
74
+ ## Studio — the web app
75
+ A React (Vite + TypeScript) single-page app, served by a FastAPI backend and
76
+ launched from the CLI. It loads DSL/JSON from disk, renders each view as an
77
+ interactive [React Flow](https://reactflow.dev/) graph, and — since 0.3.0 —
78
+ lets you write the DSL that produces them.
79
+
80
+ ```bash
81
+ uv run c4 webapp samples/ # browse a directory
82
+ uv run c4 webapp file.dsl # preload a single file
83
+ uv run c4 webapp samples/ --viewer # read-only, for a kiosk or an embed
84
+ # → opens http://127.0.0.1:8090 (use --no-browser to skip, --port to change)
85
+ ```
86
+
87
+ ### Studio and Viewer
88
+
89
+ | | Command | Behaviour |
90
+ | --- | --- | --- |
91
+ | **Studio** | `c4 webapp <path>` | Browse, edit, save, create. **The default.** |
92
+ | **Viewer** | `c4 webapp <path> --viewer` | No DSL writes — the routes answer 403. Layout dragging and group collapse still persist. |
93
+
94
+ > **Upgrading from 0.2.0?** `c4 webapp` used to be read-only and is now
95
+ > writable. If you point it at anything public-facing, add `--viewer`. The
96
+ > guarantee lives on the server, so it holds against a crafted request and
97
+ > not merely a hidden button.
98
+
99
+ ### Editing
100
+
101
+ The source editor is a [CodeMirror 6](https://codemirror.net/) panel that
102
+ opens beside the diagram — from the **Source** button at the right of the
103
+ top bar, the `e` key, the rail on the diagram's right edge, or *Open in
104
+ Source* on any element. The diagram stays put while it opens, and a ● on
105
+ the button marks unsaved work behind a closed editor.
106
+
107
+ - **Syntax highlighting and autocomplete** for DSL keywords, your own element
108
+ identifiers, and view keys — drawn from the loaded workspace, so it knows
109
+ what you have actually defined.
110
+ - **Diagnostics as you type**, from the same parser the CLI uses. Invalid DSL
111
+ still saves: an editor that refuses to save mid-thought is unusable.
112
+ - **⌘S / Ctrl+S saves**, and the diagram re-renders from the text. Saving is
113
+ the render trigger — the same live-reload loop an external editor drives.
114
+ - **Conflict detection.** If a file changed on disk since you opened it, the
115
+ save stops and offers you both sides rather than overwriting.
116
+ - **A searchable file tree** covering every source under the root, including
117
+ `!include` fragments, with right-click to create, rename or delete.
118
+ - **New workspaces from templates** — `minimal`, `system-context`, `full-c4`
119
+ or `deployment`.
120
+
121
+ The diagram is a preview of the text, never the other way round: c4studio
122
+ never generates DSL from the model, so your comments, formatting and
123
+ `!include` structure are yours.
124
+
125
+ Select several nodes (Shift+drag, or ⌘/Ctrl+click) to align or distribute
126
+ them from the toolbar that appears, and nudge a selection with the arrow
127
+ keys — 10px with Shift. Everything persists to the layout sidecar.
128
+
129
+ Pass a directory to browse and load any `.dsl`/`.json` file from the
130
+ in-app file picker, or a single file to preload it. The element tree and
131
+ per-view graph come from the parser and `graph/view_graph`;
132
+ `systemLandscape`, `systemContext`, `container`, `component`, `dynamic`
133
+ and `deployment` views all render as interactive graphs (other view
134
+ types are flagged "not renderable yet").
135
+
136
+ The **Explorer** tab renders the entire static model as one graph —
137
+ independent of any curated view — at a selectable abstraction level
138
+ (systems / containers / components), with search across every element
139
+ (press `/`), and a details panel showing an element's metadata,
140
+ relationships, the views it appears in (click to jump) and a
141
+ show-definition link that opens the source editor.
142
+
143
+ The built SPA ships inside the package (`c4studio/webapp/static/`),
144
+ so end users need no Node toolchain. To rebuild the frontend after
145
+ changes (requires Node 20.19+ or 22.12+, what Vite asks for — distinct
146
+ from the Node 18+ that `c4 render` needs at runtime, below):
147
+
148
+ The frontend is an npm workspace: `packages/diagram-core` holds the
149
+ diagram layer (layout, node/edge components, image export) and
150
+ `frontend/` is the SPA that consumes it, so build from the repo root.
151
+
152
+ ```bash
153
+ npm install
154
+ npm run build # diagram-core, then the SPA
155
+ # outputs to src/c4studio/webapp/static/
156
+ # dev loop: `npm run dev --workspace c4studio-frontend`
157
+ # (Vite :5173, proxies /api → :8090) alongside
158
+ # `uv run c4 webapp samples/ --no-browser`
159
+ ```
160
+
161
+ > **Security**: the web app has no authentication and is intended for local
162
+ > use on `127.0.0.1`. It is local-first by design — single user, no server,
163
+ > no accounts — and sharing happens through git and generated artifacts.
164
+ > The one exception is the assistant, which is off unless you pass
165
+ > `--assistant`.
166
+
167
+ ### Tests
168
+
169
+ ```bash
170
+ uv run pytest # full suite
171
+ uv run pytest tests/test_webapp # web app tests only
172
+ ```
173
+
174
+ ## Starter templates
175
+ ```bash
176
+ uv run c4 new # workspace.dsl, minimal
177
+ uv run c4 new arch.dsl --template full-c4 --name "Acme Platform"
178
+ uv run c4 new --list # what is available
179
+ ```
180
+
181
+ | Template | What it shows |
182
+ | --- | --- |
183
+ | `minimal` | One person, one system, one view — the smallest thing that renders |
184
+ | `system-context` | A system in its context: who uses it, what it depends on |
185
+ | `full-c4` | Context, containers and components, drillable in the viewer |
186
+ | `deployment` | Containers mapped onto the infrastructure that runs them |
187
+
188
+ Existing files are never overwritten without `--force`.
189
+
190
+ ## Assistant (optional, off by default)
191
+ c4studio is local-first: nothing it does reaches the network, with one
192
+ exception you have to switch on deliberately.
193
+
194
+ ```bash
195
+ pip install 'c4studio[assistant]'
196
+ export ANTHROPIC_API_KEY=...
197
+ uv run c4 webapp samples/ --assistant
198
+ ```
199
+
200
+ Ask for a change in words; the reply comes back as a **diff against your
201
+ buffer**, which you apply or reject. Applying only changes the editor —
202
+ nothing is written until you save.
203
+
204
+ **What leaves your machine:** the workspace source, including unsaved
205
+ changes to the file you are editing. The panel lists exactly which files
206
+ were sent. Without `--assistant` the endpoint answers 403; the API key is
207
+ read from the environment at the moment of use and is never stored, logged
208
+ or sent back. A default install pulls in no extra dependency and behaves
209
+ exactly as it did before.
210
+
211
+ ## Python API
212
+
213
+ c4studio is a library as well as an app: the parser, the model and the
214
+ generators are importable, and the web app is one consumer of them.
215
+
216
+ ```python
217
+ from c4studio.models import Workspace, Person, SoftwareSystem, Container, Relationship, View, ViewType
218
+
219
+ # Create workspace
220
+ ws = Workspace(
221
+ name="My Architecture",
222
+ description="System architecture model"
223
+ )
224
+
225
+ # Define people and systems
226
+ user = Person(id="user", name="User")
227
+ system = SoftwareSystem(id="sys", name="System")
228
+ ws.people.append(user)
229
+ ws.software_systems.append(system)
230
+
231
+ # Add relationship
232
+ rel = Relationship(
233
+ source_id="user",
234
+ destination_id="sys",
235
+ description="Uses"
236
+ )
237
+ ws.relationships.append(rel)
238
+
239
+ # Create view
240
+ view = View(type=ViewType.SYSTEM_CONTEXT, key="context")
241
+ ws.views.append(view)
242
+ ```
243
+
244
+ ## Parsing
245
+ Parse Structurizr DSL or JSON files:
246
+
247
+ ```python
248
+ from c4studio.parser.dsl import parse_dsl_file
249
+ from c4studio.parser.json_parser import parse_json_file
250
+
251
+ # Parse DSL
252
+ ws = parse_dsl_file("architecture.dsl")
253
+
254
+ # Parse JSON
255
+ ws = parse_json_file("workspace.json")
256
+ ```
257
+
258
+ ## Diagram Generation
259
+ Two Mermaid targets, both rendering from the same view graph — so what they
260
+ show matches the web app's view semantics exactly:
261
+
262
+ | Target | Syntax | Covers |
263
+ | --- | --- | --- |
264
+ | `MermaidGenerator` | `C4Context` / `C4Container` / `C4Component` | system landscape, system context, container, component views |
265
+ | `FlowchartGenerator` | `flowchart` + `subgraph` | all of the above **plus** dynamic, deployment and filtered views |
266
+
267
+ Mermaid's C4 diagram types are experimental upstream and lay out poorly on
268
+ dense models, and GitHub pins its own Mermaid version — so prefer the
269
+ flowchart target for anything rendered on GitHub or in a wiki.
270
+
271
+ ```python
272
+ from c4studio.generators import FlowchartGenerator, MermaidGenerator
273
+
274
+ for view_name, mermaid_code in MermaidGenerator(ws).generate_all().items():
275
+ print(f"{view_name}:\n{mermaid_code}\n")
276
+
277
+ # Or the flowchart target, which renders every view type:
278
+ diagrams = FlowchartGenerator(ws).generate_all()
279
+ ```
280
+
281
+ From the CLI:
282
+
283
+ ```bash
284
+ uv run c4 generate architecture.dsl # C4 (default)
285
+ uv run c4 generate architecture.dsl -f flowchart # flowchart
286
+ uv run c4 generate architecture.dsl -f flowchart -o out # one .mmd per view
287
+ ```
288
+
289
+ ## Perspectives
290
+ A perspective is a named annotation you hang on elements and
291
+ relationships — security, ownership, cost, anything — and then **show over
292
+ the diagram**. What carries it keeps its place and gets a badge with its
293
+ value; everything else fades. The legend follows, listing the values
294
+ rather than the element styles the diagram is no longer painted with.
295
+
296
+ ```
297
+ api = container "Payments API" "Authorises and captures." "Python, FastAPI" {
298
+ perspectives {
299
+ "security" "mTLS to the ledger, PCI DSS scope." "High"
300
+ }
301
+ }
302
+ ```
303
+
304
+ ![The security perspective shown over a container diagram: two containers
305
+ badged "High", a person badged "Medium", the rest faded, and a legend of
306
+ the values](https://raw.githubusercontent.com/geoffkoh/c4studio/main/docs/images/perspective-overlay.png)
307
+
308
+ Colour comes from ordinary styles, matched on the perspective and
309
+ optionally on its value:
310
+
311
+ ```
312
+ element "Perspective:security[value==High]" {
313
+ background #2e7d32
314
+ color #ffffff
315
+ }
316
+ ```
317
+
318
+ The same overlay is available everywhere the diagram is:
319
+
320
+ ```bash
321
+ uv run c4 render architecture.dsl --perspective security -o diagrams/
322
+ ```
323
+
324
+ and in the VS Code preview through **C4Studio: Show Perspective…**. A
325
+ perspective carrying a `url` instead of a value has that value **read
326
+ live** — off by default, because the addresses come from the workspace
327
+ file; `c4 webapp --dynamic-perspectives` turns it on.
328
+
329
+ ## Publish a site
330
+ Turn the model into something people read without installing anything:
331
+
332
+ ```bash
333
+ uv run c4 publish architecture.dsl -o site/
334
+ ```
335
+
336
+ Every view becomes its own page — not one page with a switcher, so a link
337
+ to a diagram survives being pasted into a ticket — with the `!docs` prose
338
+ and the ADRs beside them and navigation between. The result is
339
+ **self-contained**: diagrams are inlined SVG and theme icons are embedded
340
+ as `data:` URIs, so it works from a `file://` path, on an air-gapped
341
+ machine, or on any static host.
342
+
343
+ Published diagrams follow the layout you arranged in the Studio
344
+ (`--no-layout` for a fresh one), and `--perspective security` draws the
345
+ whole site through one perspective.
346
+
347
+ To publish on every merge, add the site to a Pages workflow:
348
+
349
+ ```yaml
350
+ - uses: actions/checkout@v7
351
+ - uses: actions/setup-node@v6 # c4 render needs Node
352
+ with:
353
+ node-version: "20"
354
+ - run: pipx install c4studio
355
+ - run: c4 publish architecture.dsl -o site/
356
+ - uses: actions/upload-pages-artifact@v4
357
+ with:
358
+ path: site/
359
+ - uses: actions/deploy-pages@v4
360
+ ```
361
+
362
+ ## What changed (`c4 diff`)
363
+ A pull request that changes the architecture should say what it changed:
364
+
365
+ ```bash
366
+ uv run c4 diff architecture.dsl # working tree vs HEAD
367
+ uv run c4 diff architecture.dsl --from main --to HEAD --json
368
+ ```
369
+
370
+ Elements and relationships added, removed and changed — and **the views a
371
+ reviewer has to look at again**. A comment-only edit produces nothing,
372
+ which is the signal worth having.
373
+
374
+ Renaming an element that has an alias is reported as a change. Renaming
375
+ one *without* an alias reads as a removal and an addition, because its id
376
+ comes from its name and nothing ties the two together; when a removal and
377
+ an addition share a name, the pair is flagged as a **possible** rename
378
+ rather than assumed. A diff that invents a rename hides a deletion.
379
+
380
+ ### On every pull request
381
+
382
+ `c4 diff` needs no Node, so a short workflow can post the answer on each
383
+ pull request that touches the model, updating its own comment on every
384
+ push rather than adding one per push:
385
+
386
+ ```yaml
387
+ name: Architecture diff
388
+
389
+ on:
390
+ pull_request:
391
+ paths:
392
+ - "architecture/**"
393
+
394
+ permissions:
395
+ contents: read
396
+ pull-requests: write # to comment
397
+
398
+ jobs:
399
+ diff:
400
+ runs-on: ubuntu-latest
401
+ steps:
402
+ - uses: actions/checkout@v7
403
+ with:
404
+ fetch-depth: 0 # c4 diff reads the base revision from git history
405
+
406
+ - run: pipx install c4studio # no Node needed: diff compares models
407
+
408
+ - name: Diff the model against the target branch
409
+ env:
410
+ BASE: origin/${{ github.base_ref }}
411
+ run: |
412
+ {
413
+ echo "### Architecture changes"
414
+ echo
415
+ echo '```text'
416
+ # A failure is shown rather than failing the job: a workspace
417
+ # the base branch does not have yet cannot be compared.
418
+ c4 diff architecture/workspace.dsl --from "$BASE" --to HEAD 2>&1 \
419
+ || echo "c4 diff could not compare these revisions (exit $?)"
420
+ echo '```'
421
+ } > architecture-diff.md
422
+ # The job summary works where a comment cannot: a pull request
423
+ # from a fork gets a read-only token.
424
+ cat architecture-diff.md >> "$GITHUB_STEP_SUMMARY"
425
+
426
+ - name: Comment on the pull request
427
+ env:
428
+ GH_TOKEN: ${{ github.token }}
429
+ PR: ${{ github.event.pull_request.number }}
430
+ run: gh pr comment "$PR" --body-file architecture-diff.md --edit-last --create-if-none
431
+ ```
432
+
433
+ It reads the base branch from git history, hence `fetch-depth: 0`. A
434
+ workspace the base branch does not have yet cannot be compared, and the
435
+ comment says so instead of failing the job. `--edit-last` edits the
436
+ latest comment made with the workflow's token, so keep this the only
437
+ step that comments with it — or give it a token of its own. Diagrams are
438
+ the action's job (`mode: comment`); this posts the model change, which is
439
+ what a reviewer needs before looking at any of them.
440
+
441
+ ## Impact analysis
442
+ What else is involved when one element changes — asked of the model
443
+ rather than of whoever remembers:
444
+
445
+ ```bash
446
+ uv run c4 impact architecture.dsl paymentsApi
447
+ uv run c4 impact architecture.dsl paymentsApi --json --depth 2
448
+ ```
449
+
450
+ Dependents (what reaches it, and so may break when it changes),
451
+ dependencies (what it reaches, and so may break it), each with how many
452
+ relationships away it is, plus **the views that draw it** — so a change
453
+ advisory board knows which diagrams to open.
454
+
455
+ Containment counts in both directions: a relationship declared against a
456
+ container is one its software system takes part in, which is the same
457
+ rule the views apply when they lift an edge to the nearest visible
458
+ ancestor. An answer that ignored the hierarchy would disagree with the
459
+ diagrams it is about.
460
+
461
+ The same answer is on the Diagrams page: select an element, open its
462
+ details (`i`), and choose **Show impact**. The view repaints — the element
463
+ and whatever contains it marked *Changed*, what depends on it and what it
464
+ depends on in their own colours, the rest faded — and the card gives the
465
+ model-wide counts and how many views draw it. It stays on as you move
466
+ between views; the chip in the toolbar clears it, and choosing a
467
+ perspective replaces it.
468
+
469
+ ## Model standards (`c4 lint`)
470
+ Where `c4 check` asks whether a file parses, `c4 lint` asks whether it is
471
+ a good model — the review comments someone would otherwise have to make
472
+ every time:
473
+
474
+ ```bash
475
+ uv run c4 lint architecture.dsl
476
+ ```
477
+
478
+ Orphaned elements, missing descriptions and technologies, relationships
479
+ declared twice, and styles whose tag nothing carries (invisible
480
+ otherwise, because the legend only lists styles in use). Every finding is
481
+ a warning — a model that breaks a house style still parses and renders —
482
+ so the **exit code** is what gates CI: 1 on any finding, `--exit-zero` to
483
+ report without failing on a model nobody has linted before.
484
+
485
+ Configure it in `c4studio.lint.json` beside the workspace:
486
+
487
+ ```json
488
+ {"ignore": ["missing-technology"], "naming": {"container": "^[A-Z]"}}
489
+ ```
490
+
491
+ `--json` emits the same shape editors already read, so findings land in
492
+ the Studio's problems list and the VS Code Problems panel.
493
+
494
+ ## Headless SVG Rendering
495
+ `c4 render` draws diagrams as standalone SVG with no browser
496
+ and no server — for CI, docs-as-code pipelines and static sites. Layout is
497
+ the same code the web app runs, so positions match what you see in the
498
+ viewer, including any layout you have saved for a view.
499
+
500
+ ```bash
501
+ uv run c4 render architecture.dsl -o diagrams/ # one .svg per view
502
+ uv run c4 render architecture.dsl -v Containers # one view to stdout
503
+ ```
504
+
505
+ Each diagram carries its own title and a legend of the styles it actually
506
+ uses, so an exported file explains itself to someone who did not build it.
507
+ Pass `--no-title` or `--no-legend` to leave either out.
508
+
509
+ The output is self-contained: no external fonts or stylesheets, and theme
510
+ icons (the AWS/Azure/GCP service logos) are fetched once and embedded as
511
+ `data:` URIs, so a file renders identically wherever it is opened — even
512
+ offline. An icon that cannot be fetched is simply left out.
513
+
514
+ > **This is the only command that needs [Node.js](https://nodejs.org) 18+**
515
+ > (the renderer is bundled into the wheel, so there is no `npm install`).
516
+ > Set `C4STUDIO_NODE` if `node` is not on your `PATH`. Parsing,
517
+ > Mermaid generation, JSON export and the web app all work without it.
518
+
519
+ ## Workspace JSON Export
520
+ Export any workspace (DSL or JSON) back to Structurizr workspace JSON,
521
+ round-tripping with structurizr.com, Structurizr Lite, and this package's
522
+ own parser:
523
+
524
+ ```bash
525
+ uv run c4 export workspace.dsl -o workspace.json
526
+ ```
527
+
528
+ Or programmatically via `c4studio.generators.json_export.export_json`.
529
+
530
+ ## GitHub Action
531
+ Render the diagrams on every push and pull request, so reviewers see the
532
+ architecture change alongside the code change:
533
+
534
+ ```yaml
535
+ name: Diagrams
536
+ on: [push, pull_request]
537
+
538
+ jobs:
539
+ render:
540
+ runs-on: ubuntu-latest
541
+ steps:
542
+ - uses: actions/checkout@v4
543
+ - uses: geoffkoh/c4studio@v0.3.0
544
+ id: diagrams
545
+ with:
546
+ workspace: docs/architecture.dsl
547
+ - uses: actions/upload-artifact@v4
548
+ with:
549
+ name: diagrams
550
+ path: ${{ steps.diagrams.outputs.diagrams-path }}
551
+ ```
552
+
553
+ | Input | Default | What it does |
554
+ | --- | --- | --- |
555
+ | `workspace` | *required* | The `.dsl` or `.json` file to render |
556
+ | `output` | `diagrams` | Directory for the SVGs, one per view |
557
+ | `view` | *(all)* | Render a single view key |
558
+ | `mode` | `render` | `render`, `commit` (push the files back when they change) or `comment` (comment on the PR) |
559
+ | `version` | `c4studio` | pip requirement to install; `local` uses the checkout |
560
+ | `python-version` | `3.13` | Python to set up first — runners still default to an older one than this package needs. Empty string skips the step |
561
+
562
+ The action checks the workspace before rendering, so a parse error fails
563
+ the job with diagnostics rather than a traceback, and sets a `changed`
564
+ output. Because rendering is deterministic, `mode: commit` produces no
565
+ diff when the model has not changed.
566
+
567
+ ## VS Code Extension
568
+ [`editors/vscode/`](./editors/vscode/) ships a VS Code extension with
569
+ Structurizr DSL syntax highlighting and an in-editor C4 diagram preview
570
+ (the full web app in a side panel, live-reloading as you save). The preview
571
+ runs in **Viewer** mode — VS Code already has the file open in its own
572
+ editor, and a second editor writing to it behind VS Code's back is the one
573
+ thing the preview must not do. Build and install it locally:
574
+
575
+ ```bash
576
+ cd editors/vscode && npm install && npm run package
577
+ code --install-extension c4studio-vscode-*.vsix
578
+ ```
579
+
580
+ ## Documentation
581
+ - **[DSL Reference](./docs/dsl-reference.md)** - **How to write DSL for c4studio**: every construct, the `c4studio.*` extensions, and what is silently ignored. Examples are parsed by the test suite
582
+ - **[DSL Language Support](./docs/dsl-support.md)** - Every keyword in the Structurizr DSL and whether c4studio supports it
583
+ - **[Getting Started](./docs/README.md)** - Workflow and common patterns
584
+ - **[Studio Editor Plan](./docs/studio-editor-plan.md)** - How the editor was designed and built, and the decisions behind it
585
+ - **[Migration Guide](./docs/MIGRATION.md)** - Breaking changes, including the 0.3.0 Studio-by-default flip
586
+ - **[Enterprise Roadmap](./docs/roadmap.md)** - The staged forward plan
587
+