mulmoterminal 8.1.0 → 8.3.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/README.md +5 -0
- package/blueprints/adopt/templates/chaff.yml +1 -1
- package/blueprints/docs/checks/chaff.sh +1 -1
- package/blueprints/docs/skills/workspace/SKILL.md +1 -1
- package/blueprints/from-collection/checks/actions.sh +10 -1
- package/blueprints/from-collection/checks/design.mjs +71 -0
- package/blueprints/from-collection/checks/design.sh +8 -0
- package/blueprints/from-collection/checks/designRules.d.mts +21 -0
- package/blueprints/from-collection/checks/designRules.mjs +146 -0
- package/blueprints/from-collection/design/mulmoterminal.md +429 -0
- package/blueprints/from-collection/design/themes/calm.css +45 -0
- package/blueprints/from-collection/design/themes/crisp.css +45 -0
- package/blueprints/from-collection/design/themes/soft.css +45 -0
- package/blueprints/from-collection/hearing.json +8 -0
- package/blueprints/from-collection/skills/actions/SKILL.md +3 -1
- package/blueprints/from-collection/skills/actions-cloudflare/SKILL.md +3 -1
- package/blueprints/from-collection/skills/actions-firebase/SKILL.md +3 -1
- package/blueprints/from-collection/skills/actions-supabase/SKILL.md +3 -1
- package/blueprints/from-collection/skills/design/SKILL.md +48 -0
- package/blueprints/from-collection/spec/conversion.md +1 -0
- package/blueprints/from-collection/spec/requirements.md +6 -0
- package/blueprints/from-collection/steps.json +18 -0
- package/blueprints/style/skills/report/SKILL.md +1 -1
- package/common/appActions.ts +1 -0
- package/common/blueprint/executorPolicy.ts +19 -3
- package/common/blueprint/run.ts +2 -0
- package/common/blueprint/stepPrompt.ts +68 -4
- package/common/keymap.ts +1 -0
- package/common/skillCatalog.ts +86 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-ClxVnRku.js → abnfDiagram-O67JEVCF-CKlUuyns.js} +1 -1
- package/dist/assets/architecture-7GRP2DOG-CQzNA5AV.js +1 -0
- package/dist/assets/{architectureDiagram-NJMV4G6O-DPbRnCKN.js → architectureDiagram-NJMV4G6O-Cr60a0xK.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-422fI4GC.js → blockDiagram-BEXU5L5S-B46LCZEF.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-htmTlUTG.js → c4Diagram-YGBWAQC7-T1j5qLhM.js} +1 -1
- package/dist/assets/channel-C8wFkwL-.js +1 -0
- package/dist/assets/{chunk-3FUC2YCW-BbDEpgy0.js → chunk-3FUC2YCW-BFPbRdvj.js} +1 -1
- package/dist/assets/{chunk-5DYCD2WN-D97hkTCQ.js → chunk-5DYCD2WN-DTwKhTLB.js} +1 -1
- package/dist/assets/{chunk-742MDFTN-B3Mnh8e7.js → chunk-742MDFTN-BlcDxB5j.js} +1 -1
- package/dist/assets/{chunk-7INBJB4K-B5lqXrqb.js → chunk-7INBJB4K-xunRHb1j.js} +1 -1
- package/dist/assets/{chunk-7M6MHVWA-DghPQFUI.js → chunk-7M6MHVWA-DX65iUsh.js} +1 -1
- package/dist/assets/{chunk-7PRAP22T-BdAaGYkh.js → chunk-7PRAP22T-0bKDqaeU.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-DKvFKim8.js → chunk-GTNCS2PH-BN14lNpR.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-RTEdPAf3.js → chunk-GWA4HPMP-BAet7AvE.js} +1 -1
- package/dist/assets/{chunk-MBY4JIJT-x_wqlXNe.js → chunk-MBY4JIJT-DByeaoOT.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-8Cs1NIno.js → chunk-NETBCI7D-DS63BA-9.js} +1 -1
- package/dist/assets/{chunk-O7XYJQB3-Dz4vrOLX.js → chunk-O7XYJQB3-RWvzInqy.js} +1 -1
- package/dist/assets/{chunk-UA2S7LBM-Dwd6YCa2.js → chunk-UA2S7LBM-D5BRkqDP.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-rY_Nsr-S.js → chunk-WEXAMYUT-CWS1aTuv.js} +1 -1
- package/dist/assets/{chunk-Z7XXMR3K-pt-6PBwB.js → chunk-Z7XXMR3K-BmgvUbyV.js} +1 -1
- package/dist/assets/{chunk-ZIGJFQKS-DzYvLxms.js → chunk-ZIGJFQKS-B7uoh93M.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-DAAHQYjw.js → classDiagram-v2-NBCMYWYE-_Zn3IQPd.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-DTPNG3DO.js → cynefin-OW5HDTMX-BhwDPtZE.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-BiNAmBAf.js → cynefinDiagram-VND7K2PF-DQRFRLyY.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-C3aACS4M.js → dagre-6A5THRUB-CAyH0RVf.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-1moWpahw.js → diagram-22UHCM2B-CDVq4xZt.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CgsKz1ze.js → diagram-3UASUU5V-71cR4x2a.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-CtIrEsFG.js → diagram-ATOU4E4O-B77AU3Mm.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-BntC2VUx.js → diagram-CDSNMT55-BorMYeZ1.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Cdh5tBhw.js → diagram-MLGK6HIB-DkE6ngy6.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-CQz7IrSH.js → diagram-MPIPVDR6-CgGs4ob9.js} +1 -1
- package/dist/assets/{dist--0LHNaYl.js → dist-BQUQODA8.js} +1 -1
- package/dist/assets/{dist-PySjWJXd.js → dist-CzIpHy0d.js} +1 -1
- package/dist/assets/{dist-CkDbiiOe.js → dist-DYLBD_lC.js} +1 -1
- package/dist/assets/{dist-DqudlEKF.js → dist-KoB2Fn7k.js} +1 -1
- package/dist/assets/{dist-DANd2aqA.js → dist-wRL2l7Jx.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-CZaPrV3v.js → ebnfDiagram-ZINNZB2B-DLAg5XxB.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-5V_Op3XR.js → elk-276RUBZZ-D9nvt0XC.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-flX7Jn68.js → erDiagram-OPXOYQCR-D3WDLlbz.js} +1 -1
- package/dist/assets/eventmodeling-NTZA5JFV-CU2CI2_j.js +1 -0
- package/dist/assets/flowDiagram-KWPJA3E3-DYQcChuP.js +1 -0
- package/dist/assets/{ganttDiagram-FUAMR5RP-2KqutibO.js → ganttDiagram-FUAMR5RP-BGVSRLOl.js} +1 -1
- package/dist/assets/{gitGraph-4MIJSDKK-DVTDAI2J.js → gitGraph-4MIJSDKK-CVyREC8W.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-Btc5-wfa.js → gitGraphDiagram-X574FWY7-BSmV1hek.js} +1 -1
- package/dist/assets/index-C9dQ4nPc.css +1 -0
- package/dist/assets/{index-8Aw0tzeQ.js → index-eTV11Zyw.js} +316 -316
- package/dist/assets/{info-A6RAGUB7-BX4xPahk.js → info-A6RAGUB7-Ch23BJyi.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-ng2eNzlm.js → infoDiagram-VRGFBTTK-Cq8RzFTi.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-CnNc1uHK.js → ishikawaDiagram-OU5B5YK6-B3Bx_dM4.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-BHr492DG.js → journeyDiagram-ZHPQQLJL-3C2yy2Ai.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-CD4FdLFL.js → kanban-definition-PNTS6WVX-X09o9QMI.js} +1 -1
- package/dist/assets/{line-DwxnhfoI.js → line-DRK0p1A0.js} +1 -1
- package/dist/assets/{mermaid-parser.core-DEnlY2ci.js → mermaid-parser.core-jR7u2UoU.js} +3 -3
- package/dist/assets/{mermaid.core-B7u-t6P9.js → mermaid.core-BRUnk_mY.js} +4 -4
- package/dist/assets/{mindmap-definition-NLK3R4M7-BdIcteCE.js → mindmap-definition-NLK3R4M7-BLFj8zux.js} +1 -1
- package/dist/assets/{packet-AYTQ26CC-DHisXteY.js → packet-AYTQ26CC-DJdWa_92.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-DDOrA3Ua.js → pegDiagram-GJSIUBJH-DJhNtjB1.js} +1 -1
- package/dist/assets/{pie-WAS4IAKB-Ba76QqEP.js → pie-WAS4IAKB-CZdX7gwc.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-CNlravvW.js → pieDiagram-5QR66LMP-6SpfMbRT.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-B0D-BZQ2.js → quadrantDiagram-O4NWA36T-1j2j6cCS.js} +1 -1
- package/dist/assets/{radar-RG4KPBEZ-BCNuh25J.js → radar-RG4KPBEZ-ByiCNLS6.js} +1 -1
- package/dist/assets/{railroad-74A4TZTK-CMJcxNfL.js → railroad-74A4TZTK-C-JsNW52.js} +1 -1
- package/dist/assets/railroad-abnf-HS5TGJTU-Bk0OY70O.js +1 -0
- package/dist/assets/railroad-ebnf-LZEXJU2U-BNzqEfH4.js +1 -0
- package/dist/assets/railroad-peg-WCYAUIDC-DiFnHh30.js +1 -0
- package/dist/assets/{railroadDiagram-XR7U4H2S-BpB8nxWt.js → railroadDiagram-XR7U4H2S-7dFhcasc.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CsB3aqu0.js → requirementDiagram-PLB6GJNP-BAqomR3o.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-BKGvK8Jv.js → sankeyDiagram-IPEJSGJF-DhkXOhoR.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-Cr9hkQye.js → sequenceDiagram-PO4LG4MO-lDBjo1e1.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-C7gWKveV.js → stateDiagram-v2-GCMORJYK-CRo5UhQk.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-iRnomZ6W.js → swimlanes-2SLR337P-7TmzpJwY.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-S0DcrBn_.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CvM8Zt_H.js → timeline-definition-EJHVYXUP-5aLv5D93.js} +1 -1
- package/dist/assets/{treeView-Q6P3EWNA-B-3825bR.js → treeView-Q6P3EWNA-D5CE4D1g.js} +1 -1
- package/dist/assets/{treemap-WGGIJYW6-wYDmmCoC.js → treemap-WGGIJYW6-BMUEZMzm.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-COfhzrb4.js → usecaseDiagram-POWQR4AR-DpHwlMrg.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-DQz5w2Ad.js → vennDiagram-UO4OBE2U-5VroYfor.js} +1 -1
- package/dist/assets/{wardley-WFR3VGLG-D0qPa5yM.js → wardley-WFR3VGLG-jMOUNxbQ.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-B0Ffm6Eo.js → wardleyDiagram-VNRHLVJA-DZq6IR2g.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-mcCWc9NA.js → xychartDiagram-PMCCYNJV-xtu9TBWy.js} +1 -1
- package/dist/index.html +2 -2
- package/docs/guide/en/v8.2.0.md +85 -0
- package/docs/guide/en/v8.3.0.md +70 -0
- package/docs/guide/ja/v8.2.0.md +74 -0
- package/docs/guide/ja/v8.3.0.md +66 -0
- package/package.json +8 -8
- package/plugins/plugins.json +1 -1
- package/server/backends/openPath.ts +1 -1
- package/server/backends/presentPathRoot.ts +1 -1
- package/server/backends/remoteHost/pluginSkills.ts +13 -3
- package/server/backends/remoteHost/skills.ts +1 -1
- package/server/backends/shapescript.ts +2 -2
- package/server/backends/skillCatalog.ts +80 -0
- package/server/blueprint/executor.ts +68 -12
- package/server/infra/host-tools.ts +2 -2
- package/server/infra/shapescript-manage-tool.ts +3 -3
- package/server/infra/shapescript-render-tool.ts +3 -3
- package/server/infra/shapescript-usdz-tool.ts +2 -2
- package/server/routes/app-routes.ts +2 -0
- package/server/routes/skill-catalog-routes.ts +30 -0
- package/server/session/registry.ts +6 -0
- package/server/skills/mulmoterminal-header/SKILL.md +1 -1
- package/server/skills/mulmoterminal-keys/SKILL.md +3 -1
- package/server/skills/mulmoterminal-shared-app/SKILL.md +13 -4
- package/server/skills/mulmoterminal-shared-app/templates/class-seats.md +413 -0
- package/server/skills/mulmoterminal-shared-app/templates/meeting-room.md +3 -2
- package/server/skills/mulmoterminal-shared-app/templates/schedule-poll.md +424 -0
- package/dist/assets/architecture-7GRP2DOG-DbBbO1l3.js +0 -1
- package/dist/assets/channel-BAfBdDqJ.js +0 -1
- package/dist/assets/eventmodeling-NTZA5JFV-B5Bz1qEf.js +0 -1
- package/dist/assets/flowDiagram-KWPJA3E3-CChoJefx.js +0 -1
- package/dist/assets/index-BcxXAO6O.css +0 -1
- package/dist/assets/railroad-abnf-HS5TGJTU-B2S-Bbzp.js +0 -1
- package/dist/assets/railroad-ebnf-LZEXJU2U-DlnwWMr4.js +0 -1
- package/dist/assets/railroad-peg-WCYAUIDC-C3FYE35J.js +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-DliugZOm.js +0 -8
package/README.md
CHANGED
|
@@ -1753,6 +1753,11 @@ narrow and tall for one record you are discussing — and each position keeps it
|
|
|
1753
1753
|
|
|
1754
1754
|
## More features
|
|
1755
1755
|
|
|
1756
|
+
- **Skills viewer** — **More features** → **Skills** lists every Claude skill on disk in three
|
|
1757
|
+
columns — where it lives, its skills, its `SKILL.md`. The places are your `~/.claude/skills`,
|
|
1758
|
+
each enabled plugin (skills shown as `plugin:skill`), and the `.claude/skills` of each folder a
|
|
1759
|
+
terminal has run in (folders with none are left out). A project skill with the same name as one
|
|
1760
|
+
of yours is marked as overriding it. The search (name, description or folder) narrows both lists.
|
|
1756
1761
|
- **Grid of parallel sessions** — the + Terminal / grid view runs many sessions at once,
|
|
1757
1762
|
auto-sizing by count across pages. Cell borders signal state at a glance — **working**
|
|
1758
1763
|
(pulsing blue), **blocked** (amber — needs a permission / answer), **done** (blue —
|
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
- uses: actions/setup-node@v7
|
|
22
22
|
with:
|
|
23
23
|
node-version: 24
|
|
24
|
-
- run: npx -y chaffjs@0.
|
|
24
|
+
- run: npx -y chaffjs@0.18 {{PATHS}} --sarif chaff.sarif
|
|
25
25
|
continue-on-error: true
|
|
26
26
|
- uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
|
27
27
|
with:
|
|
@@ -12,7 +12,7 @@ repository, `.git/info/exclude`) until the person has approved the models.
|
|
|
12
12
|
1. `.blueprint/` already exists (the build created it). Everything this build records goes there.
|
|
13
13
|
2. Run chaff once through the base pack's wrapper, so its first download happens now and not in the
|
|
14
14
|
middle of a later step: `sh <base pack>/checks/chaff.sh rules --json > /dev/null`. The wrapper runs the
|
|
15
|
-
pinned chaff (`npx -y chaffjs@0.
|
|
15
|
+
pinned chaff (`npx -y chaffjs@0.18`) unless `CHAFF_BIN` names another. If it fails, show the person the
|
|
16
16
|
error and stop — do not work around it.
|
|
17
17
|
3. If the folder is a git repository, add `.blueprint/` to `.git/info/exclude`. That keeps the build's
|
|
18
18
|
working files out of commits without touching `.gitignore`, which belongs to the person.
|
|
@@ -44,7 +44,16 @@ const problems = entries.flatMap(({ name, decision }) => {
|
|
|
44
44
|
if (decision === "manual" && !inHeading(name)) return [`${name} is left to a person, and README.md has no heading naming it`];
|
|
45
45
|
return [];
|
|
46
46
|
});
|
|
47
|
-
|
|
47
|
+
// Said with the titles it read and the rule it reads them by, so the one fixing it sees what is missing rather than guessing.
|
|
48
|
+
const untested = entries.some(({ name, decision }) => decision === "feature" && !inTestTitle(name));
|
|
49
|
+
const hint = untested
|
|
50
|
+
? [
|
|
51
|
+
"",
|
|
52
|
+
`The test titles ${tests} declares, as this check reads them: ${titles.length === 0 ? "(none)" : titles.map((title) => JSON.stringify(title)).join(", ")}.`,
|
|
53
|
+
"Only the title of a test counts: the first argument, written as a plain string, of it(...) or test(...) imported from \"vitest\" in that file. A describe(...) name does not count, so put the whole name in each test title.",
|
|
54
|
+
]
|
|
55
|
+
: [];
|
|
56
|
+
if (problems.length > 0) { console.error([...problems, ...hint].join("\n")); process.exit(1); }
|
|
48
57
|
process.stdout.write(String(entries.filter((entry) => entry.decision === "feature").length));
|
|
49
58
|
' "$file" "$tests" "$titles")
|
|
50
59
|
[ "$features" -gt 0 ] || exit 0
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// The design step's check, run after `yarn build`: reads the built stylesheets and the app's files and holds them to
|
|
2
|
+
// designRules.mjs. Prints each problem and exits 1 when there is any. The pack's templates are read from beside this
|
|
3
|
+
// file.
|
|
4
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { designOf, designProblems } from "./designRules.mjs";
|
|
7
|
+
|
|
8
|
+
const SKIPPED = new Set(["node_modules", "dist", ".blueprint", ".git", "test", "functions", "coverage", ".wrangler", "supabase"]);
|
|
9
|
+
// Where Vite writes the build in every base (dist/, dist/client/, client/dist/).
|
|
10
|
+
const BUILD_DIR = "dist";
|
|
11
|
+
// design.sh notes when it started the build; a stylesheet older than that is left over from another build.
|
|
12
|
+
const builtSince = Number(process.env.DESIGN_BUILD_STARTED_MS ?? 0);
|
|
13
|
+
const THEMES = path.join(import.meta.dirname, "..", "design", "themes");
|
|
14
|
+
|
|
15
|
+
const readJson = (file) => {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const readText = (file) => (existsSync(file) ? readFileSync(file, "utf8") : null);
|
|
23
|
+
|
|
24
|
+
// Every file under the app whose name ends in one of `extensions`, skipping what the build makes or does not ship.
|
|
25
|
+
const filesUnder = (dir, extensions) =>
|
|
26
|
+
readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
27
|
+
const file = path.join(dir, entry.name);
|
|
28
|
+
if (entry.isDirectory()) return SKIPPED.has(entry.name) ? [] : filesUnder(file, extensions);
|
|
29
|
+
return extensions.some((extension) => entry.name.endsWith(extension)) ? [{ path: file, text: readFileSync(file, "utf8") }] : [];
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Every stylesheet this build wrote, joined; null when it wrote none.
|
|
33
|
+
function builtCss(dir = ".") {
|
|
34
|
+
const found = readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
35
|
+
const file = path.join(dir, entry.name);
|
|
36
|
+
if (!entry.isDirectory()) return [];
|
|
37
|
+
if (entry.name === BUILD_DIR) return filesUnderAll(file, ".css");
|
|
38
|
+
return SKIPPED.has(entry.name) || entry.name.startsWith(".") ? [] : [builtCss(file)].filter((css) => css !== null);
|
|
39
|
+
});
|
|
40
|
+
return found.length > 0 ? found.join("\n") : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const filesUnderAll = (dir, extension) =>
|
|
44
|
+
readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
45
|
+
const file = path.join(dir, entry.name);
|
|
46
|
+
if (entry.isDirectory()) return filesUnderAll(file, extension);
|
|
47
|
+
return entry.name.endsWith(extension) && statSync(file).mtimeMs >= builtSince ? [readFileSync(file, "utf8")] : [];
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Every copied collection's schema (source.json lists them by slug, whether the source was a collection or a shared
|
|
51
|
+
// app): each one's icon is shown in its header, and its fields decide whether there is a kanban and a calendar.
|
|
52
|
+
const schemas = (readJson(".blueprint/source/source.json")?.collections ?? [])
|
|
53
|
+
.map((slug) => readJson(`.blueprint/source/collections/${slug}/schema.json`))
|
|
54
|
+
.filter((schema) => schema !== null);
|
|
55
|
+
|
|
56
|
+
const answer = readJson(".blueprint/answers.json")?.design;
|
|
57
|
+
const design = designOf(answer);
|
|
58
|
+
const problems = designProblems({
|
|
59
|
+
answer,
|
|
60
|
+
packageJson: readJson("package.json"),
|
|
61
|
+
builtCss: builtCss(),
|
|
62
|
+
tailwindTheme: readText(path.join("node_modules", "tailwindcss", "theme.css")),
|
|
63
|
+
sources: filesUnder(".", [".vue", ".ts", ".tsx"]),
|
|
64
|
+
designMd: readText("DESIGN.md"),
|
|
65
|
+
icons: schemas.map((schema) => schema.icon).filter((icon) => typeof icon === "string" && icon !== ""),
|
|
66
|
+
views: { kanban: schemas.some((schema) => schema.kanbanField), calendar: schemas.some((schema) => schema.calendarField) },
|
|
67
|
+
template: design?.theme ? readText(path.join(THEMES, `${design.theme}.css`)) : null,
|
|
68
|
+
});
|
|
69
|
+
problems.forEach((problem) => console.error(problem));
|
|
70
|
+
if (problems.length > 0) process.exit(1);
|
|
71
|
+
console.log(`the screens follow the design "${design.option}"`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# The app builds, and what the build produced follows the design the build chose (design.mjs reads the built CSS).
|
|
3
|
+
set -eu
|
|
4
|
+
# Whole seconds, rounded down, so a stylesheet written in the starting second still counts as this build's.
|
|
5
|
+
DESIGN_BUILD_STARTED_MS=$(($(date +%s) * 1000))
|
|
6
|
+
export DESIGN_BUILD_STARTED_MS
|
|
7
|
+
yarn build
|
|
8
|
+
node --no-warnings "$(dirname "$0")/design.mjs"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const SAME_AS_MULMOTERMINAL: string;
|
|
2
|
+
export declare const DESIGNS: readonly { option: string; theme: string | null }[];
|
|
3
|
+
export declare const SCREEN_ANCHORS: readonly { screen: string; className: string; when?: string }[];
|
|
4
|
+
export declare function designOf(answer: string | undefined): { option: string; theme: string | null } | null;
|
|
5
|
+
export declare function canonicalValue(value: string): string;
|
|
6
|
+
export declare function declarations(css: string): { name: string; value: string }[];
|
|
7
|
+
export interface FileText {
|
|
8
|
+
path: string;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function designProblems(input: {
|
|
12
|
+
answer: string | undefined;
|
|
13
|
+
packageJson: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> } | null;
|
|
14
|
+
builtCss: string | null;
|
|
15
|
+
tailwindTheme: string | null;
|
|
16
|
+
template: string | null;
|
|
17
|
+
sources: readonly FileText[];
|
|
18
|
+
designMd: string | null;
|
|
19
|
+
icons: readonly string[];
|
|
20
|
+
views?: { kanban?: boolean; calendar?: boolean };
|
|
21
|
+
}): string[];
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// What the design step must leave in an app made from a collection. The answer picks MulmoTerminal's own look or a
|
|
2
|
+
// template; a template is MulmoTerminal's classes with their colours and corners redefined, so every design is written
|
|
3
|
+
// with the same classes and only the template's @theme differs. Whether Tailwind runs and which colours won is read
|
|
4
|
+
// from the BUILT CSS, not from the sources: a build is what the person gets, and no comment or stray import can fake it.
|
|
5
|
+
|
|
6
|
+
export const SAME_AS_MULMOTERMINAL = "MulmoTerminal と同じ";
|
|
7
|
+
|
|
8
|
+
/** The design answers, each with the template under design/themes/ it takes (none for MulmoTerminal's own). */
|
|
9
|
+
export const DESIGNS = [
|
|
10
|
+
{ option: SAME_AS_MULMOTERMINAL, theme: null },
|
|
11
|
+
{ option: "やわらかい(暖かい紙の色・丸い角)", theme: "soft" },
|
|
12
|
+
{ option: "くっきり(白と黒・小さな角)", theme: "crisp" },
|
|
13
|
+
{ option: "落ち着いた(深い緑・やさしい灰色)", theme: "calm" },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/** The design an answer names; an unanswered question takes MulmoTerminal's, as the question's default does. */
|
|
17
|
+
export const designOf = (answer) => DESIGNS.find((design) => design.option === (answer ?? SAME_AS_MULMOTERMINAL)) ?? null;
|
|
18
|
+
|
|
19
|
+
const PACKAGES = ["tailwindcss", "@tailwindcss/vite", "material-symbols"];
|
|
20
|
+
// The main button of MulmoTerminal's look, as Tailwind writes it: present only when Tailwind ran over screens using it.
|
|
21
|
+
const MAIN_BUTTON_RULE = /\.bg-indigo-600\s*\{\s*background-color:\s*var\(--color-indigo-600\)/u;
|
|
22
|
+
// One class per screen of MulmoTerminal's look that appears in no other section of design/mulmoterminal.md. Tailwind
|
|
23
|
+
// writes a rule only for a class some screen uses, so a screen left in its own look shows up as its class missing.
|
|
24
|
+
// The screens every collection has, plus the kanban and the calendar when its schema asks for them; the toolbar is
|
|
25
|
+
// left out because a collection without search or views may have none.
|
|
26
|
+
export const SCREEN_ANCHORS = [
|
|
27
|
+
{ screen: "header (the icon box)", className: "w-9" },
|
|
28
|
+
{ screen: "list (the table's cells)", className: "px-5" },
|
|
29
|
+
{ screen: "record panel (its field grid)", className: "gap-x-6" },
|
|
30
|
+
{ screen: "kanban (its columns)", className: "w-72", when: "kanban" },
|
|
31
|
+
{ screen: "calendar (its day cells)", className: "min-h-[5.5rem]", when: "calendar" },
|
|
32
|
+
];
|
|
33
|
+
// A class name as a CSS selector writes it: `bg-slate-900/40` is `.bg-slate-900\/40`.
|
|
34
|
+
const selectorOf = (className) => "." + className.replaceAll(/[^\w-]/gu, (character) => "\\" + character);
|
|
35
|
+
// The selector followed by something that ends a class name, so `.w-72` is not found in `.w-720` nor
|
|
36
|
+
// `.border-indigo-100` in `.border-indigo-100\/50` (an escape continues the name).
|
|
37
|
+
const hasRule = (css, className) =>
|
|
38
|
+
css
|
|
39
|
+
.split(selectorOf(className))
|
|
40
|
+
.slice(1)
|
|
41
|
+
.some((rest) => !/^[\w\\-]/u.test(rest));
|
|
42
|
+
const ICON_FONT = /font-family:\s*["']?Material Symbols Outlined/u;
|
|
43
|
+
// The tokens a template redefines; the ones the build kept must hold the chosen design's values.
|
|
44
|
+
const DESIGN_TOKEN = /^--(?:color-(?:indigo|slate|gray)-\d+|color-white|radius-[\w-]+)$/u;
|
|
45
|
+
|
|
46
|
+
// A class or an icon name left in a comment shows nothing. A `//` counts as a comment only at a line's start or after
|
|
47
|
+
// a space, so the `//` of a URL in a string is kept.
|
|
48
|
+
const withoutComments = (text) =>
|
|
49
|
+
String(text)
|
|
50
|
+
.replaceAll("\r\n", "\n")
|
|
51
|
+
.replaceAll(/\/\*[\s\S]*?\*\//gu, "")
|
|
52
|
+
.replaceAll(/<!--[\s\S]*?-->/gu, "")
|
|
53
|
+
.replaceAll(/(^|\s)\/\/.*$/gmu, "$1");
|
|
54
|
+
|
|
55
|
+
/** A CSS value as minification leaves it comparable: `oklch(0.52 0.13 35)` and `oklch(52% .13 35)` are one value. */
|
|
56
|
+
export const canonicalValue = (value) => {
|
|
57
|
+
const text = String(value).trim().toLowerCase();
|
|
58
|
+
const oklch = /^oklch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)\s*\)$/u.exec(text);
|
|
59
|
+
if (oklch) return `oklch(${Number(oklch[1]) / (oklch[2] ? 100 : 1)} ${Number(oklch[3])} ${Number(oklch[4])})`;
|
|
60
|
+
const length = /^([\d.]+)([a-z%]*)$/u.exec(text);
|
|
61
|
+
return length ? `${Number(length[1])}${length[2]}` : text.replaceAll(/\s+/gu, "");
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Every `--name: value` a stylesheet declares, comments aside, in order (a name may repeat). */
|
|
65
|
+
export const declarations = (css) =>
|
|
66
|
+
withoutComments(css)
|
|
67
|
+
.split(/[;{}]/u)
|
|
68
|
+
.map((part) => part.trim())
|
|
69
|
+
.filter((part) => part.startsWith("--") && part.includes(":"))
|
|
70
|
+
.map((part) => ({ name: part.slice(0, part.indexOf(":")).trim(), value: part.slice(part.indexOf(":") + 1).trim() }));
|
|
71
|
+
|
|
72
|
+
const dependencyProblems = (packageJson) => {
|
|
73
|
+
const declared = { ...packageJson?.dependencies, ...packageJson?.devDependencies };
|
|
74
|
+
const missing = PACKAGES.filter((name) => !Object.hasOwn(declared, name));
|
|
75
|
+
return missing.length > 0 ? [`package.json does not depend on ${missing.join(", ")}`] : [];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// The values each design token must have in the build: the template's, or Tailwind's own theme for MulmoTerminal's.
|
|
79
|
+
const expectedTokens = (design, template, tailwindTheme) =>
|
|
80
|
+
new Map(
|
|
81
|
+
declarations(design.theme === null ? tailwindTheme : template)
|
|
82
|
+
.filter((token) => DESIGN_TOKEN.test(token.name))
|
|
83
|
+
.map((token) => [token.name, canonicalValue(token.value)]),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
function tokenProblems(design, builtCss, expected) {
|
|
87
|
+
const built = declarations(builtCss);
|
|
88
|
+
if (!built.some((token) => token.name === "--color-indigo-600"))
|
|
89
|
+
return ["the built CSS does not define --color-indigo-600: Tailwind's theme is not in the build"];
|
|
90
|
+
const wrong = built.filter((token) => expected.has(token.name) && canonicalValue(token.value) !== expected.get(token.name));
|
|
91
|
+
const names = [...new Set(wrong.map((token) => token.name))];
|
|
92
|
+
if (names.length === 0) return [];
|
|
93
|
+
const given = `the built CSS gives ${names.join(", ")} other values than`;
|
|
94
|
+
return design.theme === null
|
|
95
|
+
? [`${given} Tailwind's own: MulmoTerminal's look redefines no colour or corner, so remove the @theme that sets them`]
|
|
96
|
+
: [`${given} design/themes/${design.theme}.css: import it right after "tailwindcss", unchanged, and redefine nothing else`];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function builtProblems(design, builtCss, template, tailwindTheme, views) {
|
|
100
|
+
if (builtCss === null) return ["no built CSS under dist/: yarn build did not produce the app's stylesheet"];
|
|
101
|
+
const css = withoutComments(builtCss);
|
|
102
|
+
if (!MAIN_BUTTON_RULE.test(css)) {
|
|
103
|
+
return [
|
|
104
|
+
"the built CSS has no .bg-indigo-600 rule as Tailwind writes it: Tailwind is not running in the build (tailwindcss() in vite.config's plugins, @import \"tailwindcss\" in the stylesheet), or no screen uses MulmoTerminal's main button",
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
const expected = expectedTokens(design, template, tailwindTheme);
|
|
108
|
+
const source = design.theme === null ? "node_modules/tailwindcss/theme.css" : `design/themes/${design.theme}.css`;
|
|
109
|
+
if (!expected.has("--color-indigo-600")) return [`cannot read the design's colours (${source})`];
|
|
110
|
+
const missing = SCREEN_ANCHORS.filter((anchor) => (!anchor.when || views[anchor.when]) && !hasRule(css, anchor.className));
|
|
111
|
+
return [
|
|
112
|
+
...missing.map((anchor) => `the ${anchor.screen} is not in MulmoTerminal's look: no screen uses "${anchor.className}" (design/mulmoterminal.md)`),
|
|
113
|
+
...(ICON_FONT.test(css) ? [] : ["the built CSS has no Material Symbols Outlined font: import material-symbols/outlined.css where the app starts"]),
|
|
114
|
+
...tokenProblems(design, builtCss, expected),
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const usageProblems = (sources, icons) => {
|
|
119
|
+
const text = sources.map((source) => withoutComments(source.text)).join("\n");
|
|
120
|
+
return [
|
|
121
|
+
...(text.includes("material-symbols-outlined") ? [] : ['the screens never use "material-symbols-outlined"']),
|
|
122
|
+
...[...new Set(icons)].filter((icon) => !text.includes(icon)).map((icon) => `the screens never show the collection's icon "${icon}"`),
|
|
123
|
+
];
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const recordProblems = (design, designMd) => {
|
|
127
|
+
if (designMd === null) return ["DESIGN.md is missing: it tells later steps which design the screens follow"];
|
|
128
|
+
return designMd.includes(design.option) ? [] : [`DESIGN.md does not name the design "${design.option}"`];
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Every way the app falls short of the chosen design. `builtCss` is the build's stylesheets joined (null when there
|
|
133
|
+
* are none); `tailwindTheme` is the app's node_modules/tailwindcss/theme.css; `template` the chosen template's text;
|
|
134
|
+
* `sources` the screens' files as { path, text }; `icons` every copied collection's icon; `views` says whether any of
|
|
135
|
+
* them has a kanban and a calendar ({ kanban, calendar }).
|
|
136
|
+
*/
|
|
137
|
+
export function designProblems({ answer, packageJson, builtCss, tailwindTheme, template, sources, designMd, icons, views }) {
|
|
138
|
+
const design = designOf(answer);
|
|
139
|
+
if (design === null) return [`.blueprint/answers.json: the design "${answer}" is not one this pack has`];
|
|
140
|
+
return [
|
|
141
|
+
...dependencyProblems(packageJson),
|
|
142
|
+
...builtProblems(design, builtCss, template, tailwindTheme, views ?? {}),
|
|
143
|
+
...usageProblems(sources, icons ?? []),
|
|
144
|
+
...recordProblems(design, designMd),
|
|
145
|
+
];
|
|
146
|
+
}
|