@litfamily/litmuse 0.1.1 → 0.1.3

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 (177) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +23 -1
  3. package/README_ko-KR.md +23 -1
  4. package/TRADEMARK.md +12 -0
  5. package/bin/litmuse.mjs +4 -0
  6. package/lib/stage-card.mjs +249 -6
  7. package/package.json +3 -2
  8. package/plugin/.muse-plugin/plugin.json +26 -11
  9. package/plugin/commands/lit-status.md +3 -1
  10. package/plugin/commands/lit.md +1 -1
  11. package/plugin/hooks/lib/digest.mjs +1 -1
  12. package/plugin/hooks/lib/next-step.mjs +112 -0
  13. package/plugin/hooks/lib/routes.mjs +59 -0
  14. package/plugin/hooks/lib/stage.mjs +53 -6
  15. package/plugin/hooks/lib/workflow-stack.mjs +336 -0
  16. package/plugin/hooks/post-tool-use-failure.mjs +2 -0
  17. package/plugin/hooks/post-tool-use.mjs +2 -0
  18. package/plugin/hooks/session-end.mjs +2 -1
  19. package/plugin/hooks/session-start.mjs +32 -2
  20. package/plugin/hooks/stop.mjs +23 -3
  21. package/plugin/hooks/user-prompt-submit.mjs +138 -7
  22. package/plugin/lib/capped-fetch.mjs +25 -0
  23. package/plugin/lib/pinned-fetch.mjs +79 -0
  24. package/plugin/lib/plan-task-rows.mjs +32 -0
  25. package/plugin/lib/render_pages.py +29 -1
  26. package/plugin/lib/rewind/collect.mjs +808 -0
  27. package/plugin/lib/rewind/gauge.mjs +72 -0
  28. package/plugin/lib/rewind/next-step.mjs +91 -0
  29. package/plugin/lib/rewind/redact.mjs +83 -0
  30. package/plugin/lib/rewind/transcripts.mjs +229 -0
  31. package/plugin/skills/frontend-ui-ux/SKILL.md +24 -9
  32. package/plugin/skills/frontend-ui-ux/kit/components.css +414 -0
  33. package/plugin/skills/frontend-ui-ux/kit/fonts.json +22 -0
  34. package/plugin/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
  35. package/plugin/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
  36. package/plugin/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
  37. package/plugin/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
  38. package/plugin/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
  39. package/plugin/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
  40. package/plugin/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
  41. package/plugin/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
  42. package/plugin/skills/frontend-ui-ux/kit/ui.js +226 -0
  43. package/plugin/skills/frontend-ui-ux/references/complete-contract.md +2 -1
  44. package/plugin/skills/frontend-ui-ux/references/craft-floor.md +9 -4
  45. package/plugin/skills/frontend-ui-ux/references/default-editorial-pixel.json +59 -7
  46. package/plugin/skills/frontend-ui-ux/references/house-styles.md +234 -0
  47. package/plugin/skills/frontend-ui-ux/references/motion-guide.md +3 -3
  48. package/plugin/skills/frontend-ui-ux/references/slop-register.md +7 -7
  49. package/plugin/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
  50. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-lock.mjs +196 -0
  51. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-page.mjs +31 -10
  52. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-rules.mjs +19 -7
  53. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-static.mjs +4 -0
  54. package/plugin/skills/frontend-ui-ux/scripts/interface-probe.mjs +59 -7
  55. package/plugin/skills/frontend-ui-ux/scripts/pixel-art.mjs +60 -0
  56. package/plugin/skills/lit-adhd/SKILL.md +195 -0
  57. package/plugin/skills/lit-adhd/references/card-examples.md +121 -0
  58. package/plugin/skills/lit-adhd/references/status-taxonomy.md +45 -0
  59. package/plugin/skills/lit-adhd/scripts/rewind.mjs +11 -0
  60. package/plugin/skills/lit-diagram-drawer/SKILL.md +3 -3
  61. package/plugin/skills/lit-diagram-drawer/scripts/diagram-craft-rules.mjs +1 -2
  62. package/plugin/skills/lit-diagram-drawer/scripts/visual-quality.mjs +0 -1
  63. package/plugin/skills/lit-docx/SKILL.md +1 -1
  64. package/plugin/skills/lit-hijack/SKILL.md +589 -0
  65. package/plugin/skills/lit-hijack/references/capture-and-provenance.md +123 -0
  66. package/plugin/skills/lit-hijack/references/clean-room-build.md +133 -0
  67. package/plugin/skills/lit-hijack/references/distil-and-licence.md +128 -0
  68. package/plugin/skills/lit-hijack/references/gap-and-port-design.md +152 -0
  69. package/plugin/skills/lit-hijack/references/parity-and-report.md +133 -0
  70. package/plugin/skills/lit-hijack/scripts/hijack.mjs +879 -0
  71. package/plugin/skills/lit-plan/SKILL.md +12 -1
  72. package/plugin/skills/lit-plan/scripts/check-plan.mjs +71 -4
  73. package/plugin/skills/lit-pptx/SKILL.md +20 -9
  74. package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
  75. package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
  76. package/plugin/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
  77. package/plugin/skills/lit-pptx/fonts/provenance.json +32 -1
  78. package/plugin/skills/lit-pptx/references/direction-step.md +19 -2
  79. package/plugin/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +173 -0
  80. package/plugin/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +154 -0
  81. package/plugin/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +139 -0
  82. package/plugin/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +158 -0
  83. package/plugin/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +179 -0
  84. package/plugin/skills/lit-pptx/references/tonalities/bauhaus.md +112 -0
  85. package/plugin/skills/lit-pptx/references/tonalities/computational-brutalism.md +118 -0
  86. package/plugin/skills/lit-pptx/references/tonalities/neo-brutalism.md +125 -0
  87. package/plugin/skills/lit-pptx/references/tonalities/newsroom.md +115 -0
  88. package/plugin/skills/lit-pptx/references/tonalities/riso.md +109 -0
  89. package/plugin/skills/lit-pptx/scripts/compile-deck.js +1 -1
  90. package/plugin/skills/lit-pptx/scripts/craft_extras.py +136 -92
  91. package/plugin/skills/lit-pptx/scripts/embed_fonts.py +13 -1
  92. package/plugin/skills/lit-pptx/scripts/lib/grid-resolver.js +105 -116
  93. package/plugin/skills/lit-pptx/scripts/lib/layout-resolver.js +9 -15
  94. package/plugin/skills/lit-pptx/scripts/lib/render-pack.js +368 -561
  95. package/plugin/skills/lit-pptx/scripts/lib/slide-grid.js +104 -0
  96. package/plugin/skills/lit-pptx/scripts/lib/template-registry.js +57 -6
  97. package/plugin/skills/lit-pptx/scripts/qa_deck.py +4 -4
  98. package/plugin/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +1 -1
  99. package/plugin/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +98 -0
  100. package/plugin/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +1 -1
  101. package/plugin/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
  102. package/plugin/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +1 -1
  103. package/plugin/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +1 -1
  104. package/plugin/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
  105. package/plugin/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
  106. package/plugin/skills/lit-pptx/templates/tonalities/night/pack.yaml +1 -1
  107. package/plugin/skills/lit-pptx/templates/tonalities/paper/pack.yaml +1 -1
  108. package/plugin/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
  109. package/plugin/skills/lit-pptx/templates/tonalities/signal/pack.yaml +2 -2
  110. package/plugin/skills/lit-pptx/templates/tonalities/studio/pack.yaml +1 -1
  111. package/plugin/skills/lit-recap/SKILL.md +3 -0
  112. package/plugin/skills/lit-schematic/SKILL.md +241 -0
  113. package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +14 -0
  114. package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +8 -0
  115. package/plugin/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +22 -0
  116. package/plugin/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +73 -0
  117. package/plugin/skills/lit-schematic/references/cutaway-outputs.md +25 -0
  118. package/plugin/skills/lit-schematic/references/isometric-craft.md +24 -0
  119. package/plugin/skills/lit-schematic/references/journal-figures.md +19 -0
  120. package/plugin/skills/lit-schematic/references/kit-api.md +27 -0
  121. package/plugin/skills/lit-schematic/references/pfd-and-layout.md +20 -0
  122. package/plugin/skills/lit-schematic/references/pid-drafting.md +26 -0
  123. package/plugin/skills/lit-schematic/references/pid-symbols.md +27 -0
  124. package/plugin/skills/lit-schematic/references/verification.md +24 -0
  125. package/plugin/skills/lit-schematic/scripts/doctor.mjs +13 -0
  126. package/plugin/skills/lit-schematic/scripts/lib/figure.mjs +49 -0
  127. package/plugin/skills/lit-schematic/scripts/lib/isa.mjs +18 -0
  128. package/plugin/skills/lit-schematic/scripts/lib/iso.mjs +58 -0
  129. package/plugin/skills/lit-schematic/scripts/lib/legend-sheet.mjs +10 -0
  130. package/plugin/skills/lit-schematic/scripts/lib/qa-core.mjs +241 -0
  131. package/plugin/skills/lit-schematic/scripts/lib/sheet.mjs +446 -0
  132. package/plugin/skills/lit-schematic/scripts/lib/svg.mjs +39 -0
  133. package/plugin/skills/lit-schematic/scripts/lib/theme.mjs +25 -0
  134. package/plugin/skills/lit-schematic/scripts/motion.mjs +8 -0
  135. package/plugin/skills/lit-schematic/scripts/qa.mjs +21 -0
  136. package/plugin/skills/lit-schematic/scripts/render.mjs +38 -0
  137. package/plugin/skills/lit-schematic/scripts/scaffold.mjs +73 -0
  138. package/plugin/skills/lit-scientific-visualization/SKILL.md +2 -0
  139. package/plugin/skills/lit-team/scripts/state-root.mjs +5 -18
  140. package/plugin/skills/lit-typographic-motion/SKILL.md +12 -8
  141. package/plugin/skills/lit-typographic-motion/engine/THIRD_PARTY_NOTICES +11 -0
  142. package/plugin/skills/lit-typographic-motion/engine/blender/runner.py +218 -0
  143. package/plugin/skills/lit-typographic-motion/engine/core/presets.mjs +1 -1
  144. package/plugin/skills/lit-typographic-motion/engine/node/assets.mjs +218 -0
  145. package/plugin/skills/lit-typographic-motion/engine/node/blender.mjs +144 -0
  146. package/plugin/skills/lit-typographic-motion/engine/node/board.mjs +136 -0
  147. package/plugin/skills/lit-typographic-motion/engine/node/done.mjs +42 -3
  148. package/plugin/skills/lit-typographic-motion/engine/node/gallery.mjs +120 -0
  149. package/plugin/skills/lit-typographic-motion/engine/node/house.mjs +44 -0
  150. package/plugin/skills/lit-typographic-motion/engine/node/pipeline.mjs +18 -7
  151. package/plugin/skills/lit-typographic-motion/engine/node/runtime.mjs +6 -3
  152. package/plugin/skills/lit-typographic-motion/engine/node/scaffold.mjs +210 -0
  153. package/plugin/skills/lit-typographic-motion/engine/node/score.mjs +639 -0
  154. package/plugin/skills/lit-typographic-motion/engine/node/sound-cli.mjs +21 -4
  155. package/plugin/skills/lit-typographic-motion/engine/node/sound.mjs +126 -27
  156. package/plugin/skills/lit-typographic-motion/engine/node/stage-serve.mjs +29 -5
  157. package/plugin/skills/lit-typographic-motion/engine/node/stage.mjs +82 -14
  158. package/plugin/skills/lit-typographic-motion/engine/node/styles.mjs +214 -0
  159. package/plugin/skills/lit-typographic-motion/engine/node/treatment.mjs +311 -10
  160. package/plugin/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
  161. package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
  162. package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
  163. package/plugin/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
  164. package/plugin/skills/lit-typographic-motion/recipes/hero-3d/expected.json +69 -0
  165. package/plugin/skills/lit-typographic-motion/recipes/hero-3d/index.html +139 -0
  166. package/plugin/skills/lit-typographic-motion/references/depth.md +122 -0
  167. package/plugin/skills/lit-typographic-motion/references/sound.md +74 -16
  168. package/plugin/skills/lit-typographic-motion/references/stage.md +29 -5
  169. package/plugin/skills/lit-typographic-motion/references/studio.md +245 -0
  170. package/plugin/skills/lit-typographic-motion/references/treatment.md +31 -8
  171. package/plugin/skills/lit-typographic-motion/scripts/motion.mjs +48 -7
  172. package/plugin/skills/lit-typographic-motion/styles/paper.json +41 -0
  173. package/plugin/skills/lit-typographic-motion/styles/swiss-signal.json +41 -0
  174. package/plugin/skills/lit-typographic-motion/styles/terminalcore.json +42 -0
  175. package/plugin/skills/lit-typographic-motion/styles/tidal.json +41 -0
  176. package/plugin/skills/readme-studio/SKILL.md +1 -1
  177. package/plugin/skills/start-work/SKILL.md +10 -1
@@ -0,0 +1,19 @@
1
+ # Journal figures
2
+
3
+ Column widths: 85 mm single, 120 mm intermediate, 178 mm double. Panels read (a), (b) from top
4
+ left. Type at 7 pt with a 6 pt floor. Latin text uses Arial embedded as text in the SVG and
5
+ PDF. Hangul labels use the Pretendard copy shipped with `lit-diagram-drawer` assets.
6
+
7
+ Stream colours come from the colour-vision-safe set with a key on the figure. Colour supports
8
+ the labels; labels carry the meaning in text. Keep the title out of the art; the caption owns
9
+ it.
10
+
11
+ Panels: 2D canvas panels for schematics and flows, 3D iso panels for rigs. Each panel earns
12
+ its place: one message per panel, shared scale noted where panels compare.
13
+
14
+ Export: SVG and PDF stay vector. PNG renders at 600 dpi for review. Chrome embeds Hangul as
15
+ vector-safe glyphs; say so in the reply when a venue checker flags font embedding, and ship
16
+ the PDF alongside the SVG.
17
+
18
+ Checklist before delivery: column width exact, type sizes at floor or above, key present,
19
+ caption parts named in the art, invented values labelled in the reply.
@@ -0,0 +1,27 @@
1
+ # Kit API
2
+
3
+ All helpers live under `scripts/lib/` and load by relative import from the build script.
4
+ Import the kit root and construct sheets, figures, and views from it.
5
+
6
+ `createSheet(opts)` returns a sheet with A-series size, zone frame, and title block. Options:
7
+ `size` (A3 default), `plant`, `unit`, `sheetNo`, `rev`. Methods: `add(symbol)`,
8
+ `pipe(from, to, opts)`, `signal(from, to, kind)`, `inline(line, item)`, `equipmentInfo(tag,
9
+ info)`.
10
+
11
+ `createFigure(opts)` returns a journal figure with column width (`85`, `120`, `178`).
12
+ `panel.canvas()` opens a 2D panel; `panel.iso(view)` opens a 3D panel with the isometric
13
+ camera. `fig.key(roles, at)` draws the stream key. `stream({ role })` colours a stream from
14
+ the safe set.
15
+
16
+ `createIsoView(opts)` returns an isometric scene. `lathe(profile, opts)` turns a profile;
17
+ `box(w, h, d)` and `prism(points)` place flat parts; `pipe(points, r)` routes piping;
18
+ `cut(window, { into })` opens a window revealing named internals; `callout({ part, text })`
19
+ places a callout; `group(name, parts)` names an assembly for pages and films.
20
+
21
+ `createLegendSheet(symbols)` builds the legend sheet from the drawn set. `parseLineNumber(s)`
22
+ parses a line number through the grammar. `isaTag(letters)` validates tag letter order.
23
+ `material(name)` resolves a MATERIAL tone to fill and stroke.
24
+
25
+ Router: orthogonal runs with waypoint hints (`via: [[x, y]]`). Unroutable lines throw with the
26
+ reason and the blocked span. Exports: the build script writes `drawing.svg`; the render script
27
+ derives PNG and PDF.
@@ -0,0 +1,20 @@
1
+ # PFD, BFD, and equipment layout
2
+
3
+ BFD: blocks for units with named streams between them, one-line stream labels, major
4
+ utilities in. Use it for scoping and early reviews.
5
+
6
+ PFD: major equipment with stream numbers on every line and a stream table (number, from, to,
7
+ fluid, phase, key conditions). Instruments appear where a control loop defines the process;
8
+ full instrumentation waits for the P&ID.
9
+
10
+ Stream tables sit below the drawing or on a facing sheet, numbered to match the art. Every
11
+ number on the drawing comes from the process truth; example builds label invented values in
12
+ the reply.
13
+
14
+ Equipment layout: plan view on a scaled grid with equipment footprints, maintenance clearances,
15
+ pipe-rack lanes, access aisles, and north arrow. Gravity notes (pumps below suction, knockout
16
+ drums ahead of compressors) carry over from the P&ID rules.
17
+
18
+ Layout checks: footprints clear of each other, rack lanes continuous, relief headers rising
19
+ toward the flare or scrubber, drains falling toward collection. Open plot questions (a rack
20
+ lane awaiting a destination, a clearance tweak) go in the reply beside the files.
@@ -0,0 +1,26 @@
1
+ # P&ID drafting
2
+
3
+ Sheet: ISO A-series with a zone frame, a title block (plant, unit, sheet number, revision,
4
+ drawn/checked dates), an equipment header per tagged vessel, and a legend built from the drawn
5
+ set. The legend covers every symbol the sheet uses; the gate checks coverage.
6
+
7
+ Routing: orthogonal runs with the kit router. Feed enters left, products leave right. Mains run
8
+ horizontally with vertical drops to equipment. Signal lines run in reserved lanes clear of
9
+ process piping. A line that cannot route throws with the reason; move a symbol or add
10
+ waypoints, and route around symbols.
11
+
12
+ Inline items (valves, filters, flow elements, reducers) sit on the line through `sheet.inline`.
13
+ Branches leave through `line.tap` at straight runs. Asked-for flow arrows slide along open
14
+ pipe to clear spans; a span shorter than 30 mm takes a warn flag.
15
+
16
+ Line hops: arcs of radius 2 mm or less mark crossings; larger arcs read as bends and fail the
17
+ bend check. Leaders keep 1.2 mm at print size between leaders and between leaders and dots,
18
+ ordered by violation count then total length, opening a blank row up or down where needed.
19
+
20
+ Control loops: each loop shows its measurement tap, transmitter, controller function, and final
21
+ element. Cascade loops draw set-point signals between controllers. Relief devices show set
22
+ pressure and a discharge destination; a path awaiting a destination appears in the reply as an
23
+ open engineering question.
24
+
25
+ Review order: lines through symbols, crossings and hops, labels on lines, leader dots on the
26
+ wrong face, painter order, nozzles on removed shell, empty regions calling for parts.
@@ -0,0 +1,27 @@
1
+ # P&ID symbols
2
+
3
+ ISA-5.1 letters name every instrument: first letter the measured variable (F flow, L level,
4
+ P pressure, T temperature), succeeding letters the function (I indicate, C control, T transmit,
5
+ V valve). Tag order follows the letter table; the QA gate flags letter-order breaks.
6
+
7
+ Bubbles carry function plus location: a plain circle for field instruments, a horizontal bar
8
+ for panel-board mounting, a dashed outline for computer-resident functions. Every bubble shows
9
+ its loop tag beside it.
10
+
11
+ Line types: process lines heavy (0.7), utility lines medium (0.5), instrument connections and
12
+ signal lines light (0.35), with leader dots at 0.18. Process, utility, and signal lines stay
13
+ visually distinct at print size.
14
+
15
+ Valves use `valve.<body>.<actuator>` kinds: gate, globe, ball, butterfly, diaphragm, angle
16
+ bodies; hand, solenoid, pneumatic, motor actuators. Each actuated valve exposes an actuator
17
+ port and a fail letter (FO, FC, FL, FI) validated by the gate.
18
+
19
+ Specialties in the library: DCS and PLC bubbles with location lines, computer hexagon,
20
+ tray-column and packed-column forms, U-tube and fixed-sheet exchanger forms, plate exchanger
21
+ form, eccentric reducer, flange pair, venturi and magnetic/Coriolis/vortex flow elements,
22
+ figure-8 spectacle blind, line-size breaks, strainers, traps, rupture disks, PSVs with
23
+ discharge destinations.
24
+
25
+ Line numbers parse through a small grammar (size, fluid code, class, sequence) with a dedicated
26
+ QA check. Signal marks follow ISA-5.1 Table 5.3.2 for pneumatic, electric, capillary, and
27
+ software links.
@@ -0,0 +1,24 @@
1
+ # Verification
2
+
3
+ QA codes (`qa.mjs --codes` lists all): TITLEBLOCK.FIELDS, LEGEND.COVERAGE, TAG.ORDER,
4
+ LINE.NUMBER, LINE.WEIGHT, LINE.ARROW_IN_SYMBOL, LINE.BEND_ON_LINE, LEADER.SPACING,
5
+ LABEL.CLEARANCE, EXPORT.DPI, EXPORT.PDF_SIZE.
6
+
7
+ Fixes: TITLEBLOCK gains the missing fields in the build. LEGEND rebuilds from the drawn set
8
+ through `createLegendSheet`. TAG reorders letters per `references/pid-symbols.md`.
9
+ LINE.NUMBER parses through the line grammar. LINE.WEIGHT remaps roles to 0.7/0.5/0.35/0.18.
10
+ Arrows slide to open pipe. Hop arcs shrink to 2 mm or less. Leaders respread per
11
+ `references/pid-drafting.md`. Labels move clear of lines and art.
12
+
13
+ Render loop: build, QA, render PNG, read the PNG, sweep dense sheets in a 3x2 tile, fix in
14
+ the build, rebuild. Rebuilds stay byte-identical for unchanged inputs.
15
+
16
+ Close-up sweep order: lines through symbols, crossings and hops, labels on lines, leader dots
17
+ on the wrong face, painter order, nozzles on removed shell, empty regions calling for parts.
18
+
19
+ Raster guard: the renderer caps output at 80 Mpx, renders a small reference of the same page,
20
+ compares ink across 36 regions, steps the dpi down until each inked region paints, prints the
21
+ dpi used, and halts with `RENDER_INCOMPLETE` when regions stay blank.
22
+
23
+ Review checklist: open engineering questions listed, invented values labelled, exports at size,
24
+ page console clean, motion seam matching, PDF vector.
@@ -0,0 +1,13 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { findOnPath } from '../../../lib/host-process.mjs';
5
+
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ const report = {
8
+ node: process.version,
9
+ chrome: Boolean(findOnPath('google-chrome') || findOnPath('chromium') || findOnPath('chromium-browser')),
10
+ fonts: existsSync(join(here, '..', '..', 'lit-diagram-drawer', 'assets', 'fonts', 'PretendardVariable.woff2')),
11
+ encoders: { ffmpeg: Boolean(findOnPath('ffmpeg')), img2webp: Boolean(findOnPath('img2webp')) },
12
+ };
13
+ console.log(JSON.stringify(report, null, 2));
@@ -0,0 +1,49 @@
1
+ import { el, text, doc, comment } from './svg.mjs';
2
+ import { STREAMS } from './theme.mjs';
3
+
4
+ export function createFigure(opts = {}) {
5
+ const width = opts.width ?? 85;
6
+ const marks = [comment(`titleblock plant=FIGURE unit=JOURNAL sheetNo=${width}MM rev=0`)];
7
+ const shapes = [];
8
+ const kinds = new Set(['panel', 'stream', 'key']);
9
+ const api = {
10
+ canvas(label = 'a') {
11
+ kinds.add('panel');
12
+ marks.push(comment('symbol kind=panel id=' + label));
13
+ const g = [];
14
+ return {
15
+ stream(role, x1, y1, x2, y2) {
16
+ const color = STREAMS[role.length % STREAMS.length];
17
+ marks.push(comment(`line class=signal w=0.35 kind=stream role=${role}`));
18
+ kinds.add('stream');
19
+ g.push(el('line', { x1, y1, x2, y2, stroke: color, 'stroke-width': 0.35 }));
20
+ g.push(text((x1 + x2) / 2, (y1 + y2) / 2 - 2, role, { size: 6, fill: color }));
21
+ },
22
+ box(x, y, w, h, labelText) {
23
+ kinds.add('cell');
24
+ marks.push(comment(`symbol kind=cell id=${labelText}`));
25
+ g.push(el('rect', { x, y, width: w, height: h, fill: '#fff', stroke: '#111', 'stroke-width': 0.5 }));
26
+ g.push(text(x + 2, y + 8, labelText, { size: 6 }));
27
+ },
28
+ label(s, x, y) {
29
+ g.push(text(x, y, s, { size: 7 }));
30
+ },
31
+ commit() {
32
+ shapes.push(el('g', { id: `panel-${label}` }, `\n${g.join('\n')}\n`));
33
+ },
34
+ };
35
+ },
36
+ key(roles, x, y) {
37
+ kinds.add('key');
38
+ roles.forEach((r, i) => {
39
+ shapes.push(el('line', { x1: x, y1: y + i * 8, x2: x + 10, y2: y + i * 8, stroke: STREAMS[i % STREAMS.length], 'stroke-width': 0.35 }));
40
+ shapes.push(text(x + 13, y + i * 8 + 2, r, { size: 6 }));
41
+ });
42
+ },
43
+ toSVG() {
44
+ for (const kind of [...kinds].sort()) marks.push(comment(`legend kind=${kind}`));
45
+ return doc({ width, height: Math.round(width * 0.7), title: 'journal figure' }, `${marks.join('\n')}\n${shapes.join('\n')}`);
46
+ },
47
+ };
48
+ return api;
49
+ }
@@ -0,0 +1,18 @@
1
+ const VARIABLE_LETTERS = new Set('FLPTEIAS'.split(''));
2
+ const TAG_RE = /^([A-Z]{1,4})-(\d{3,4})([A-Z]?)$/;
3
+ const LINE_RE = /^(\d+)-([A-Z0-9]+)-([A-Z0-9]+)$/;
4
+
5
+ export function validateTag(tag) {
6
+ const m = TAG_RE.exec(String(tag));
7
+ if (!m) return { ok: false, reason: `tag ${tag} breaks the LETTERS-NNNN shape` };
8
+ if (!VARIABLE_LETTERS.has(m[1][0])) {
9
+ return { ok: false, reason: `tag ${tag} opens with ${m[1][0]}, outside the variable table` };
10
+ }
11
+ return { ok: true };
12
+ }
13
+
14
+ export function parseLineNumber(s) {
15
+ const m = LINE_RE.exec(String(s).trim());
16
+ if (!m) return { ok: false, reason: `line number ${s} breaks the SIZE-FLUID-SEQ grammar` };
17
+ return { ok: true, size: m[1], fluid: m[2], seq: m[3] };
18
+ }
@@ -0,0 +1,58 @@
1
+ import { el, text, doc, comment } from './svg.mjs';
2
+ import { material } from './theme.mjs';
3
+
4
+ const C = Math.cos(Math.PI / 6);
5
+ const S = Math.sin(Math.PI / 6);
6
+
7
+ export function iso(x, y, z) {
8
+ return [(x - y) * C, (x + y) * S - z];
9
+ }
10
+
11
+ export function createIsoView(opts = {}) {
12
+ const marks = [comment(`titleblock plant=${opts.plant ?? 'SHOP'} unit=ISO sheetNo=${opts.sheetNo ?? 'ISO-001'} rev=0`)];
13
+ const shapes = [];
14
+ const kinds = new Set();
15
+ const api = {
16
+ box(w, h, d, at, tone = 'painted-steel') {
17
+ const [x, y, z] = at;
18
+ const m = material(tone);
19
+ kinds.add('iso-box');
20
+ marks.push(comment(`symbol kind=iso-box id=box-${shapes.length}`));
21
+ const p = [iso(x, y, z), iso(x + w, y, z), iso(x + w, y + d, z), iso(x, y + d, z)];
22
+ const top = p.map(([a, b]) => `${(a + 210).toFixed(1)},${(b + 150 - h).toFixed(1)}`).join(' ');
23
+ shapes.push(el('polygon', { points: top, fill: m.fill, stroke: m.stroke, 'stroke-width': 0.5 }));
24
+ return api;
25
+ },
26
+ vessel(tag, x, y, r, h, tone = 'stainless') {
27
+ const m = material(tone);
28
+ kinds.add('iso-vessel');
29
+ marks.push(comment(`symbol kind=iso-vessel id=${tag}`));
30
+ const [cx, cy] = iso(x, y, 0);
31
+ shapes.push(el('ellipse', { cx: (cx + 210).toFixed(1), cy: (cy + 150).toFixed(1), rx: (r * C).toFixed(1), ry: (r * S).toFixed(1), fill: m.fill, stroke: m.stroke, 'stroke-width': 0.5 }));
32
+ shapes.push(el('ellipse', { cx: (cx + 210).toFixed(1), cy: (cy + 150 - h).toFixed(1), rx: (r * C).toFixed(1), ry: (r * S).toFixed(1), fill: m.fill, stroke: m.stroke, 'stroke-width': 0.5 }));
33
+ shapes.push(text(cx + 210 + r * C + 4, cy + 150 - h / 2, tag, { size: 6 }));
34
+ return api;
35
+ },
36
+ pipe(points, r = 1, tone = 'painted-steel') {
37
+ const m = material(tone);
38
+ kinds.add('iso-pipe');
39
+ marks.push(comment('symbol kind=iso-pipe id=pipe'));
40
+ marks.push(comment('line class=process w=0.7'));
41
+ const flat = points.map(([x, y, z]) => { const [a, b] = iso(x, y, z); return `${(a + 210).toFixed(1)},${(b + 150).toFixed(1)}`; }).join(' ');
42
+ shapes.push(el('polyline', { points: flat, fill: 'none', stroke: m.stroke, 'stroke-width': 0.7 }));
43
+ return api;
44
+ },
45
+ callout(partText, x, y, spacing = 1.4) {
46
+ marks.push(comment(`leader spacing=${spacing} part=${partText}`));
47
+ const [a, b] = iso(x, y, 0);
48
+ shapes.push(el('circle', { cx: (a + 210).toFixed(1), cy: (b + 150).toFixed(1), r: 0.9, fill: '#111' }));
49
+ shapes.push(text(a + 210 + 4, b + 150 + 2, partText, { size: 6 }));
50
+ return api;
51
+ },
52
+ toSVG() {
53
+ for (const kind of [...kinds].sort()) marks.push(comment(`legend kind=${kind}`));
54
+ return doc({ width: 420, height: 297, title: 'isometric cutaway' }, `${marks.join('\n')}\n${shapes.join('\n')}`);
55
+ },
56
+ };
57
+ return api;
58
+ }
@@ -0,0 +1,10 @@
1
+ import { el, text, doc, comment } from './svg.mjs';
2
+
3
+ export function createLegendSheet(symbols) {
4
+ const rows = [...new Set(symbols)].sort();
5
+ const marks = [comment('titleblock plant=LEGEND unit=SYMBOLS sheetNo=LG-001 rev=0')];
6
+ for (const kind of rows) marks.push(comment(`legend kind=${kind}`));
7
+ for (const kind of rows) marks.push(comment(`symbol kind=${kind} id=legend-${kind}`));
8
+ const body = rows.map((k, i) => text(20, 30 + i * 9, k, { size: 7 })).join('\n');
9
+ return doc({ title: 'legend sheet' }, `${marks.join('\n')}\n${body}`);
10
+ }
@@ -0,0 +1,241 @@
1
+ import { ALLOWED_WIDTHS } from './theme.mjs';
2
+ import { validateTag, parseLineNumber } from './isa.mjs';
3
+
4
+ function comments(svg, prefix) {
5
+ const out = [];
6
+ const re = new RegExp(`<!--\\s*${prefix}[:\\s]([^>]*)-->`, 'g');
7
+ let m;
8
+ while ((m = re.exec(svg)) !== null) out.push(m[1].trim());
9
+ return out;
10
+ }
11
+
12
+ function kv(s) {
13
+ const o = {};
14
+ for (const part of s.split(/\s+/)) {
15
+ const i = part.indexOf('=');
16
+ if (i > 0) o[part.slice(0, i)] = part.slice(i + 1);
17
+ }
18
+ return o;
19
+ }
20
+
21
+ export const CODES = [
22
+ 'TITLEBLOCK.FIELDS', 'LEGEND.COVERAGE', 'TAG.ORDER', 'LINE.NUMBER',
23
+ 'LINE.WEIGHT', 'LINE.ARROW_IN_SYMBOL', 'LINE.BEND_ON_LINE', 'LEADER.SPACING',
24
+ 'LABEL.CLEARANCE',
25
+ ];
26
+
27
+ // ---- LABEL.CLEARANCE geometry: no label sits on another label or a line ----
28
+ // Per-glyph advances as fractions of an em (Arial-like): caps and digits run
29
+ // wide, lowercase narrow. A flat average would acquit real collisions and
30
+ // convict tight-but-clean rows like the title against its divider.
31
+ const GLYPH_W = {
32
+ A: 0.72, B: 0.72, C: 0.72, D: 0.72, E: 0.67, F: 0.61, G: 0.78, H: 0.72, I: 0.28,
33
+ J: 0.5, K: 0.72, L: 0.61, M: 0.83, N: 0.72, O: 0.78, P: 0.67, Q: 0.78, R: 0.72,
34
+ S: 0.67, T: 0.61, U: 0.72, V: 0.67, W: 0.94, X: 0.67, Y: 0.67, Z: 0.61,
35
+ a: 0.55, b: 0.55, c: 0.5, d: 0.55, e: 0.55, f: 0.28, g: 0.55, h: 0.55, i: 0.22,
36
+ j: 0.28, k: 0.5, l: 0.22, m: 0.83, n: 0.55, o: 0.55, p: 0.55, q: 0.55, r: 0.33,
37
+ s: 0.5, t: 0.28, u: 0.55, v: 0.5, w: 0.72, x: 0.5, y: 0.5, z: 0.5,
38
+ ' ': 0.28, '-': 0.33, '.': 0.28, '/': 0.28,
39
+ };
40
+ const CAP_H = 0.72; // cap height above the baseline, in em
41
+ const DESC_H = 0.21; // descender depth below the baseline, in em
42
+ const INSET = 0.5; // shrink each label box this far: grazes pass, strikes fail
43
+
44
+ function decodeEntities(s) {
45
+ return String(s).replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&amp;/g, '&');
46
+ }
47
+
48
+ function textWidth(content, size) {
49
+ let em = 0;
50
+ for (const ch of content) em += GLYPH_W[ch] ?? 0.6;
51
+ return em * size;
52
+ }
53
+
54
+ // Every <text> as an axis-aligned box. Rotated (-90) line numbers read
55
+ // bottom-to-top, so their advance runs up the page and their cap height left.
56
+ function textBoxes(svg) {
57
+ const boxes = [];
58
+ for (const m of svg.matchAll(/<text\b([^>]*)>([^<]*)<\/text>/g)) {
59
+ const attrs = m[1];
60
+ const content = decodeEntities(m[2]);
61
+ if (!content) continue;
62
+ const num = (name, fallback) => {
63
+ const hit = attrs.match(new RegExp(`${name}="([\\d.]+)"`));
64
+ return hit ? Number(hit[1]) : fallback;
65
+ };
66
+ const x = num('x', 0);
67
+ const y = num('y', 0);
68
+ const size = num('font-size', 7);
69
+ const anchor = (attrs.match(/text-anchor="(\w+)"/) ?? [])[1] ?? 'start';
70
+ const w = textWidth(content, size);
71
+ const desc = /[gjpqyQJ]/.test(content) ? DESC_H * size : 0;
72
+ const rot = attrs.match(/rotate\(-90\s+([\d.]+)\s+([\d.]+)\)/);
73
+ if (rot) {
74
+ const xx = Number(rot[1]);
75
+ boxes.push({ x0: xx - CAP_H * size, x1: xx + desc, y0: y - w, y1: y, label: content });
76
+ } else {
77
+ const top = y - CAP_H * size;
78
+ const bottom = y + desc;
79
+ if (anchor === 'middle') boxes.push({ x0: x - w / 2, x1: x + w / 2, y0: top, y1: bottom, label: content });
80
+ else if (anchor === 'end') boxes.push({ x0: x - w, x1: x, y0: top, y1: bottom, label: content });
81
+ else boxes.push({ x0: x, x1: x + w, y0: top, y1: bottom, label: content });
82
+ }
83
+ }
84
+ return boxes;
85
+ }
86
+
87
+ function shrink(box) {
88
+ return { x0: box.x0 + INSET, x1: box.x1 - INSET, y0: box.y0 + INSET, y1: box.y1 - INSET, label: box.label };
89
+ }
90
+
91
+ function overlaps(a, b) {
92
+ return a.x0 < b.x1 && b.x0 < a.x1 && a.y0 < b.y1 && b.y0 < a.y1;
93
+ }
94
+
95
+ // Line, polyline and path segments as centerlines. Curves contribute only
96
+ // their endpoints (rects and circles are checked by their edges, not needed
97
+ // here): clearance is about labels struck by straight runs.
98
+ function lineSegments(svg) {
99
+ const segs = [];
100
+ const push = (x1, y1, x2, y2) => {
101
+ if (Number.isFinite(x1 + y1 + x2 + y2) && (x1 !== x2 || y1 !== y2)) segs.push([x1, y1, x2, y2]);
102
+ };
103
+ for (const m of svg.matchAll(/<line\b[^>]*x1="([\d.]+)"[^>]*y1="([\d.]+)"[^>]*x2="([\d.]+)"[^>]*y2="([\d.]+)"[^>]*\/>/g)) {
104
+ push(Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4]));
105
+ }
106
+ for (const m of svg.matchAll(/<polyline\b[^>]*points="([^"]+)"[^>]*\/>/g)) {
107
+ const pts = m[1].trim().split(/\s+/).map((p) => p.split(',').map(Number));
108
+ for (let i = 1; i < pts.length; i++) push(pts[i - 1][0], pts[i - 1][1], pts[i][0], pts[i][1]);
109
+ }
110
+ for (const m of svg.matchAll(/<path\b[^>]*d="([^"]+)"[^>]*\/>/g)) {
111
+ const toks = m[1].match(/[MLHVZACSTQmlhvzcqsta]|[-+]?\d*\.?\d+(?:e[-+]?\d+)?/g) ?? [];
112
+ let cx = 0;
113
+ let cy = 0;
114
+ let sx = 0;
115
+ let sy = 0;
116
+ let i = 0;
117
+ const take = () => [Number(toks[i++]), Number(toks[i++])];
118
+ // The engine emits absolute commands only; anything else is skipped.
119
+ while (i < toks.length) {
120
+ const cmd = toks[i++];
121
+ if (!/[MLHVZACSTQ]/.test(cmd)) continue;
122
+ if (cmd === 'M') {
123
+ [cx, cy] = take();
124
+ [sx, sy] = [cx, cy];
125
+ } else if (cmd === 'L') {
126
+ const [x, y] = take();
127
+ push(cx, cy, x, y);
128
+ [cx, cy] = [x, y];
129
+ } else if (cmd === 'H') {
130
+ const x = Number(toks[i++]);
131
+ push(cx, cy, x, cy);
132
+ cx = x;
133
+ } else if (cmd === 'V') {
134
+ const y = Number(toks[i++]);
135
+ push(cx, cy, cx, y);
136
+ cy = y;
137
+ } else if (cmd === 'Z') {
138
+ push(cx, cy, sx, sy);
139
+ [cx, cy] = [sx, sy];
140
+ } else if (/[ACSTQ]/i.test(cmd)) {
141
+ // Curves and arcs: only the endpoint matters for clearance.
142
+ const count = /A/i.test(cmd) ? 7 : /C/i.test(cmd) ? 6 : 4;
143
+ const nums = [];
144
+ for (let k = 0; k < count && i < toks.length; k++) nums.push(Number(toks[i++]));
145
+ if (nums.length >= 2) [cx, cy] = nums.slice(-2);
146
+ }
147
+ }
148
+ }
149
+ for (const m of svg.matchAll(/<rect\b[^>]*x="([\d.]+)"[^>]*y="([\d.]+)"[^>]*width="([\d.]+)"[^>]*height="([\d.]+)"[^>]*\/>/g)) {
150
+ const [x, y, w, h] = [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
151
+ push(x, y, x + w, y);
152
+ push(x + w, y, x + w, y + h);
153
+ push(x + w, y + h, x, y + h);
154
+ push(x, y + h, x, y);
155
+ }
156
+ return segs;
157
+ }
158
+
159
+ function segHitsBox(x1, y1, x2, y2, box) {
160
+ if (x1 >= box.x0 && x1 <= box.x1 && y1 >= box.y0 && y1 <= box.y1) return true;
161
+ if (x2 >= box.x0 && x2 <= box.x1 && y2 >= box.y0 && y2 <= box.y1) return true;
162
+ const edges = [
163
+ [box.x0, box.y0, box.x1, box.y0],
164
+ [box.x1, box.y0, box.x1, box.y1],
165
+ [box.x1, box.y1, box.x0, box.y1],
166
+ [box.x0, box.y1, box.x0, box.y0],
167
+ ];
168
+ for (const [x3, y3, x4, y4] of edges) {
169
+ const d = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
170
+ if (!d) continue;
171
+ const t = ((x3 - x1) * (y4 - y3) - (y3 - y1) * (x4 - x3)) / d;
172
+ const u = ((x3 - x1) * (y2 - y1) - (y3 - y1) * (x2 - x1)) / d;
173
+ if (t > 0 && t < 1 && u > 0 && u < 1) return true;
174
+ }
175
+ return false;
176
+ }
177
+
178
+ export function runQA(svg) {
179
+ const fails = [];
180
+ const tb = comments(svg, 'titleblock').map(kv)[0] ?? {};
181
+ for (const f of ['plant', 'unit', 'sheetNo', 'rev']) {
182
+ if (!tb[f]) fails.push({ code: 'TITLEBLOCK.FIELDS', msg: `title block lacks ${f}` });
183
+ }
184
+ const symbols = new Set(comments(svg, 'symbol').map((c) => kv(c).kind).filter(Boolean));
185
+ const legend = new Set(comments(svg, 'legend').map((c) => kv(c).kind).filter(Boolean));
186
+ for (const kind of symbols) {
187
+ if (!legend.has(kind)) fails.push({ code: 'LEGEND.COVERAGE', msg: `drawn symbol ${kind} lacks a legend row` });
188
+ }
189
+ for (const c of comments(svg, 'tag')) {
190
+ const r = validateTag(kv(c).tag ?? c);
191
+ if (!r.ok) fails.push({ code: 'TAG.ORDER', msg: r.reason });
192
+ }
193
+ for (const c of comments(svg, 'linenumber')) {
194
+ const r = parseLineNumber(kv(c).line ?? c);
195
+ if (!r.ok) fails.push({ code: 'LINE.NUMBER', msg: r.reason });
196
+ }
197
+ for (const m of svg.matchAll(/stroke-width="([\d.]+)"/g)) {
198
+ if (!ALLOWED_WIDTHS.has(Number(m[1]))) {
199
+ fails.push({ code: 'LINE.WEIGHT', msg: `stroke width ${m[1]} sits outside 0.7/0.5/0.35/0.18` });
200
+ break;
201
+ }
202
+ }
203
+ for (const m of svg.matchAll(/font-size="([\d.]+)"/g)) {
204
+ if (Number(m[1]) < 6) {
205
+ fails.push({ code: 'LABEL.CLEARANCE', msg: `type size ${m[1]} drops below the 6 pt floor` });
206
+ break;
207
+ }
208
+ }
209
+ for (const c of comments(svg, 'arrow')) {
210
+ if (kv(c).clear !== 'true') fails.push({ code: 'LINE.ARROW_IN_SYMBOL', msg: `arrow at ${c} sits inside a symbol span` });
211
+ }
212
+ for (const c of comments(svg, 'hop')) {
213
+ if (Number(kv(c).r ?? 99) > 2) fails.push({ code: 'LINE.BEND_ON_LINE', msg: `hop radius ${kv(c).r} reads as a bend` });
214
+ }
215
+ for (const c of comments(svg, 'leader')) {
216
+ if (Number(kv(c).spacing ?? 0) < 1.2) {
217
+ fails.push({ code: 'LEADER.SPACING', msg: `leader spacing ${kv(c).spacing} falls below 1.2 mm` });
218
+ }
219
+ }
220
+ // No label sits on another label (legend rows, side tags) or on a line
221
+ // (a tag struck by its own signal, text overflowing its off-page box).
222
+ // P&ID sheets only: journal figures compose overlaid state panels by design,
223
+ // so their shared-region labels are out of this rule's reach.
224
+ const tbPlant = (comments(svg, 'titleblock').map(kv)[0] ?? {}).plant;
225
+ const labels = tbPlant === 'FIGURE' ? [] : textBoxes(svg).map(shrink);
226
+ for (let i = 0; i < labels.length; i++) {
227
+ for (let j = i + 1; j < labels.length; j++) {
228
+ if (overlaps(labels[i], labels[j])) {
229
+ fails.push({ code: 'LABEL.CLEARANCE', msg: `labels collide: “${labels[i].label}” on “${labels[j].label}”` });
230
+ }
231
+ }
232
+ }
233
+ if (!fails.some((f) => f.code === 'LABEL.CLEARANCE')) {
234
+ const runs = lineSegments(svg);
235
+ for (const box of labels) {
236
+ const hit = runs.find(([x1, y1, x2, y2]) => segHitsBox(x1, y1, x2, y2, box));
237
+ if (hit) fails.push({ code: 'LABEL.CLEARANCE', msg: `“${box.label}” sits on a line` });
238
+ }
239
+ }
240
+ return { pass: fails.length === 0, fails };
241
+ }