@specship/specship-win32-arm64 0.11.8 → 0.12.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/lib/dist/bin/specship.js +141 -66
- package/lib/dist/bin/specship.js.map +1 -1
- package/lib/dist/commands/specship/check.md +5 -0
- package/lib/dist/commands/specship/spec.md +235 -15
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +1 -0
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/enforce/enforce.d.ts +15 -0
- package/lib/dist/enforce/enforce.d.ts.map +1 -1
- package/lib/dist/enforce/enforce.js +43 -1
- package/lib/dist/enforce/enforce.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +6 -0
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/index.d.ts +9 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +53 -9
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +35 -0
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/installer/instructions-template.d.ts +2 -2
- package/lib/dist/installer/instructions-template.js +3 -3
- package/lib/dist/installer/targets/claude.d.ts +10 -0
- package/lib/dist/installer/targets/claude.d.ts.map +1 -1
- package/lib/dist/installer/targets/claude.js +33 -7
- package/lib/dist/installer/targets/claude.js.map +1 -1
- package/lib/dist/mcp/designer-tools.js +1 -1
- package/lib/dist/mcp/server-instructions.d.ts +1 -1
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +1 -1
- package/lib/dist/mcp/spec-tools.d.ts +10 -0
- package/lib/dist/mcp/spec-tools.d.ts.map +1 -1
- package/lib/dist/mcp/spec-tools.js +228 -2
- package/lib/dist/mcp/spec-tools.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts +12 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +34 -1
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/reflect/index.d.ts +1 -1
- package/lib/dist/reflect/index.d.ts.map +1 -1
- package/lib/dist/reflect/index.js +2 -1
- package/lib/dist/reflect/index.js.map +1 -1
- package/lib/dist/reflect/miner.d.ts +8 -0
- package/lib/dist/reflect/miner.d.ts.map +1 -1
- package/lib/dist/reflect/miner.js +64 -36
- package/lib/dist/reflect/miner.js.map +1 -1
- package/lib/dist/resolution/brief-link-resolver.d.ts +20 -0
- package/lib/dist/resolution/brief-link-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/brief-link-resolver.js +65 -1
- package/lib/dist/resolution/brief-link-resolver.js.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.d.ts +16 -2
- package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.js +19 -1
- package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
- package/lib/dist/server/cli.js +5 -49
- package/lib/dist/server/ingest/ingestor.js +17 -17
- package/lib/dist/server/ingest/pricing-backfill.js +55 -0
- package/lib/dist/server/ingest/pricing.js +3 -0
- package/lib/dist/server/ingest/project-paths.js +152 -0
- package/lib/dist/server/project-registry.js +15 -2
- package/lib/dist/server/routes/claude.js +47 -18
- package/lib/dist/server/routes/events.js +20 -1
- package/lib/dist/server/routes/projects.js +6 -3
- package/lib/dist/server/routes/spec.js +22 -2
- package/lib/dist/server/server.js +38 -4
- package/lib/dist/server/ssr/md.mjs +83 -0
- package/lib/dist/server/ssr/public/app.css +1 -0
- package/lib/dist/server/ssr/render.mjs +564 -0
- package/lib/dist/server/ssr/routes.js +73 -0
- package/lib/dist/server/static-handler.js +30 -0
- package/lib/dist/workflows/defaults/claude-design-implement.yaml +3 -3
- package/lib/dist/workflows/defaults/spec-implement.yaml +41 -9
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +3 -3
- package/package.json +1 -1
- package/lib/dist/commands/specship/design-implement.md +0 -84
- package/lib/dist/commands/specship/design-loop.md +0 -125
- package/lib/dist/commands/ss-behaviour.md +0 -116
- package/lib/dist/web/chunk-2AJCHB7P.js +0 -1
- package/lib/dist/web/chunk-2CPLUFCH.js +0 -2
- package/lib/dist/web/chunk-2I7L37NS.js +0 -1
- package/lib/dist/web/chunk-2IVSOVQZ.js +0 -1
- package/lib/dist/web/chunk-2NAWAJB5.js +0 -1
- package/lib/dist/web/chunk-2OJBIPE4.js +0 -1
- package/lib/dist/web/chunk-3E2WB6D5.js +0 -1
- package/lib/dist/web/chunk-3EBFYSCH.js +0 -2
- package/lib/dist/web/chunk-3QCQ4BXS.js +0 -1
- package/lib/dist/web/chunk-42XVAQ6I.js +0 -1
- package/lib/dist/web/chunk-4G7XU55L.js +0 -1
- package/lib/dist/web/chunk-4IMMPEYM.js +0 -1
- package/lib/dist/web/chunk-4TJQJPCZ.js +0 -1
- package/lib/dist/web/chunk-4WZIHTPC.js +0 -1
- package/lib/dist/web/chunk-4YVSYOSD.js +0 -1
- package/lib/dist/web/chunk-5BQIOYKW.js +0 -1
- package/lib/dist/web/chunk-5HGWHUJA.js +0 -1
- package/lib/dist/web/chunk-5NTGUSXJ.js +0 -1
- package/lib/dist/web/chunk-5Y244R4G.js +0 -1
- package/lib/dist/web/chunk-6RRDPT5Z.js +0 -1
- package/lib/dist/web/chunk-6VKB2ZWM.js +0 -1
- package/lib/dist/web/chunk-7656MA24.js +0 -1
- package/lib/dist/web/chunk-7DMFVTU4.js +0 -1
- package/lib/dist/web/chunk-7KWBINA7.js +0 -1
- package/lib/dist/web/chunk-7MUNFW56.js +0 -1
- package/lib/dist/web/chunk-7SMPKVEP.js +0 -1
- package/lib/dist/web/chunk-AZJVTPLU.js +0 -1
- package/lib/dist/web/chunk-AZUUBUEJ.js +0 -1
- package/lib/dist/web/chunk-BLBRMCN2.js +0 -1
- package/lib/dist/web/chunk-BMIAXD2V.js +0 -2
- package/lib/dist/web/chunk-BPCJLNBS.js +0 -47
- package/lib/dist/web/chunk-BUXWEHIY.js +0 -1
- package/lib/dist/web/chunk-CYXQILZI.js +0 -1
- package/lib/dist/web/chunk-DLQPZWSI.css +0 -1
- package/lib/dist/web/chunk-DOKZOFKM.js +0 -1
- package/lib/dist/web/chunk-DTWIJ3GJ.js +0 -1
- package/lib/dist/web/chunk-DYRFLPJA.js +0 -1
- package/lib/dist/web/chunk-E27QAKAB.js +0 -1
- package/lib/dist/web/chunk-E3J3CXR5.js +0 -1
- package/lib/dist/web/chunk-E73OX2P7.js +0 -1
- package/lib/dist/web/chunk-EAXRKDLV.js +0 -1
- package/lib/dist/web/chunk-EBKKDHYI.js +0 -1
- package/lib/dist/web/chunk-EE7V7Q5P.js +0 -1
- package/lib/dist/web/chunk-EKY2FUHU.js +0 -1
- package/lib/dist/web/chunk-EP6XOPXH.js +0 -1
- package/lib/dist/web/chunk-ESGDLJOJ.js +0 -1
- package/lib/dist/web/chunk-ETJG7NCY.js +0 -1
- package/lib/dist/web/chunk-EUUEFEDI.js +0 -1
- package/lib/dist/web/chunk-EXBUKHH2.js +0 -17
- package/lib/dist/web/chunk-F5TBUYDH.js +0 -1
- package/lib/dist/web/chunk-FC3R7YHR.js +0 -4
- package/lib/dist/web/chunk-FGNZDHTL.js +0 -11
- package/lib/dist/web/chunk-FIJW2UNJ.js +0 -1
- package/lib/dist/web/chunk-FMV5PXRC.js +0 -5
- package/lib/dist/web/chunk-G7RXFHYO.js +0 -1
- package/lib/dist/web/chunk-G7VZT5KB.js +0 -3
- package/lib/dist/web/chunk-GRZYXPSO.js +0 -7
- package/lib/dist/web/chunk-GYGPS3AN.js +0 -1
- package/lib/dist/web/chunk-H723TYYD.js +0 -2
- package/lib/dist/web/chunk-H7AF7YS4.js +0 -1
- package/lib/dist/web/chunk-HDZDQILN.js +0 -1
- package/lib/dist/web/chunk-HMK6UO6N.js +0 -1
- package/lib/dist/web/chunk-HSJNHNOH.js +0 -6
- package/lib/dist/web/chunk-IHEE5NYJ.js +0 -1
- package/lib/dist/web/chunk-IOB4JJT4.js +0 -1
- package/lib/dist/web/chunk-ISNEBICW.js +0 -1
- package/lib/dist/web/chunk-J2GZVLHH.js +0 -1
- package/lib/dist/web/chunk-JGDK6YKG.js +0 -1
- package/lib/dist/web/chunk-JLOJCDIY.js +0 -1
- package/lib/dist/web/chunk-JPSYO24K.js +0 -1
- package/lib/dist/web/chunk-JTFXTIPE.js +0 -903
- package/lib/dist/web/chunk-K7UD24Z4.js +0 -1
- package/lib/dist/web/chunk-KYSTCHAH.js +0 -1
- package/lib/dist/web/chunk-LB6JPLX2.js +0 -1
- package/lib/dist/web/chunk-LNSVDHCI.js +0 -1
- package/lib/dist/web/chunk-LVGIY3SO.js +0 -1
- package/lib/dist/web/chunk-LXLHIHEN.js +0 -1
- package/lib/dist/web/chunk-MNFTHCSJ.js +0 -1
- package/lib/dist/web/chunk-N6SS4G6S.js +0 -1
- package/lib/dist/web/chunk-NKPK3BR3.js +0 -1
- package/lib/dist/web/chunk-NTBJG6SJ.js +0 -1
- package/lib/dist/web/chunk-NUDB3Q2Y.js +0 -3
- package/lib/dist/web/chunk-OTQR5VFM.js +0 -1
- package/lib/dist/web/chunk-OXEF5E3E.js +0 -1
- package/lib/dist/web/chunk-P62EJGSF.js +0 -1
- package/lib/dist/web/chunk-PCW6R7OP.js +0 -1
- package/lib/dist/web/chunk-PGGJPDJG.js +0 -1
- package/lib/dist/web/chunk-POCVU4VK.js +0 -2
- package/lib/dist/web/chunk-PS6HRYQQ.js +0 -3
- package/lib/dist/web/chunk-PUYSJNJR.js +0 -1
- package/lib/dist/web/chunk-PZSU53SS.js +0 -1
- package/lib/dist/web/chunk-Q2RVFS45.js +0 -1
- package/lib/dist/web/chunk-Q7L6LLAK.js +0 -1
- package/lib/dist/web/chunk-QCMKJIWY.js +0 -1
- package/lib/dist/web/chunk-QH6CF3M3.js +0 -1
- package/lib/dist/web/chunk-QIZ65CGB.js +0 -1
- package/lib/dist/web/chunk-QPA2REUE.js +0 -1
- package/lib/dist/web/chunk-QQ5LD7PI.js +0 -1
- package/lib/dist/web/chunk-QR6L3KAC.js +0 -1
- package/lib/dist/web/chunk-R2DLK4HO.js +0 -1
- package/lib/dist/web/chunk-RD6TVPOT.js +0 -1
- package/lib/dist/web/chunk-RKY4EJYJ.js +0 -1
- package/lib/dist/web/chunk-RONYWVY7.js +0 -1
- package/lib/dist/web/chunk-RXKXYF2C.js +0 -1
- package/lib/dist/web/chunk-SQPYSOV6.js +0 -1
- package/lib/dist/web/chunk-SWKJRNYY.js +0 -1
- package/lib/dist/web/chunk-SYGIPC2P.js +0 -4
- package/lib/dist/web/chunk-T7AZ65JP.js +0 -1
- package/lib/dist/web/chunk-TCZDVOHD.js +0 -1
- package/lib/dist/web/chunk-TPTITA3V.js +0 -1
- package/lib/dist/web/chunk-TR335633.js +0 -1
- package/lib/dist/web/chunk-TWVVIWKO.js +0 -1
- package/lib/dist/web/chunk-U67LRNER.js +0 -1
- package/lib/dist/web/chunk-UR5KDXPX.js +0 -1
- package/lib/dist/web/chunk-UR6O2GEH.js +0 -1
- package/lib/dist/web/chunk-USVLLFVR.js +0 -1
- package/lib/dist/web/chunk-UTNMGWTP.js +0 -1
- package/lib/dist/web/chunk-V74ZPP4B.js +0 -1
- package/lib/dist/web/chunk-VECWMHJP.js +0 -1
- package/lib/dist/web/chunk-VUACT35R.js +0 -3
- package/lib/dist/web/chunk-VZI7H4SZ.js +0 -1
- package/lib/dist/web/chunk-WB6YHOD4.js +0 -1
- package/lib/dist/web/chunk-WBJ5VEB5.js +0 -1
- package/lib/dist/web/chunk-WBT64AWV.js +0 -1
- package/lib/dist/web/chunk-WFXJIXZE.js +0 -4
- package/lib/dist/web/chunk-WTGYRH3Z.js +0 -298
- package/lib/dist/web/chunk-WXTCVDTP.js +0 -1
- package/lib/dist/web/chunk-XAUQEKZX.js +0 -1
- package/lib/dist/web/chunk-XCDHWLVH.js +0 -1
- package/lib/dist/web/chunk-Y3H6FFUZ.js +0 -1
- package/lib/dist/web/chunk-Y4F5ULGJ.js +0 -1
- package/lib/dist/web/chunk-YEGKAAEE.js +0 -1
- package/lib/dist/web/chunk-YM2KU57F.js +0 -1
- package/lib/dist/web/chunk-YNYVOPWF.js +0 -1
- package/lib/dist/web/chunk-YRERBP6T.js +0 -1
- package/lib/dist/web/chunk-YV5LB22R.js +0 -1
- package/lib/dist/web/chunk-YWIRU745.js +0 -1
- package/lib/dist/web/chunk-YXYPTZ6E.js +0 -1
- package/lib/dist/web/chunk-ZLV4VCDG.js +0 -3
- package/lib/dist/web/chunk-ZTVI5KFF.js +0 -1
- package/lib/dist/web/favicon-16.png +0 -0
- package/lib/dist/web/favicon-180.png +0 -0
- package/lib/dist/web/favicon-32.png +0 -0
- package/lib/dist/web/favicon-512.png +0 -0
- package/lib/dist/web/favicon-small.svg +0 -15
- package/lib/dist/web/favicon.ico +0 -0
- package/lib/dist/web/favicon.svg +0 -20
- package/lib/dist/web/icon-192.png +0 -0
- package/lib/dist/web/icon-512.png +0 -0
- package/lib/dist/web/index.html +0 -146
- package/lib/dist/web/main-ESADRXN2.css +0 -1
- package/lib/dist/web/main-FY3HQ4PX.js +0 -1
- package/lib/dist/web/manifest.webmanifest +0 -15
- package/lib/dist/web/media/codicon-LN6W7LCM.ttf +0 -0
- package/lib/dist/web/styles-KSOPUVDA.css +0 -1
- package/lib/dist/web/sw.js +0 -69
|
@@ -16,10 +16,10 @@ import { writeSseHead } from './sse.js';
|
|
|
16
16
|
import { promises as fs, watch } from 'node:fs';
|
|
17
17
|
import os from 'node:os';
|
|
18
18
|
import path from 'node:path';
|
|
19
|
-
import {
|
|
19
|
+
import { createSlugResolver } from '../ingest/project-paths.js';
|
|
20
20
|
const projectsBus = new EventEmitter();
|
|
21
21
|
projectsBus.setMaxListeners(0); // many SSE subscribers + internal
|
|
22
|
-
export async function enumerate(claudeRoot) {
|
|
22
|
+
export async function enumerate(claudeRoot, resolverOpts) {
|
|
23
23
|
let entries;
|
|
24
24
|
try {
|
|
25
25
|
entries = await fs.readdir(claudeRoot, { withFileTypes: true });
|
|
@@ -27,12 +27,15 @@ export async function enumerate(claudeRoot) {
|
|
|
27
27
|
catch {
|
|
28
28
|
return [];
|
|
29
29
|
}
|
|
30
|
+
// Slug decoding is lossy ('-'/'.'/'_' → '-'); resolve real paths from
|
|
31
|
+
// ~/.claude.json + transcript cwds instead (REQ-SLUGRES-002).
|
|
32
|
+
const resolveSlug = createSlugResolver(resolverOpts);
|
|
30
33
|
const out = [];
|
|
31
34
|
for (const ent of entries) {
|
|
32
35
|
if (!ent.isDirectory())
|
|
33
36
|
continue;
|
|
34
37
|
const slug = ent.name;
|
|
35
|
-
const decoded =
|
|
38
|
+
const decoded = resolveSlug(slug);
|
|
36
39
|
const projDir = path.join(claudeRoot, slug);
|
|
37
40
|
let sessionCount = 0;
|
|
38
41
|
let lastModifiedMs = 0;
|
|
@@ -98,8 +98,28 @@ export async function registerSpecRoutes(app) {
|
|
|
98
98
|
const cg = await resolveCg(app, req, reply);
|
|
99
99
|
if (!cg)
|
|
100
100
|
return;
|
|
101
|
-
const
|
|
102
|
-
|
|
101
|
+
const sq = cg.getSpecQueries();
|
|
102
|
+
const docs = sq.getAllSpecs();
|
|
103
|
+
// Rolled-up link state per spec id (REQ-DASHUX-003): worst state wins
|
|
104
|
+
// (broken > orphaned > drifted), else verified only when every link is,
|
|
105
|
+
// else implemented. One bulk pass — the tree colors 500+ rows from this
|
|
106
|
+
// map without per-spec detail calls.
|
|
107
|
+
const statesById = new Map();
|
|
108
|
+
for (const l of sq.getAllLinks()) {
|
|
109
|
+
const arr = statesById.get(l.specId) ?? [];
|
|
110
|
+
arr.push(l.state);
|
|
111
|
+
statesById.set(l.specId, arr);
|
|
112
|
+
}
|
|
113
|
+
const linkStates = {};
|
|
114
|
+
for (const [id, states] of statesById) {
|
|
115
|
+
linkStates[id] =
|
|
116
|
+
states.includes('broken') ? 'broken'
|
|
117
|
+
: states.includes('orphaned') ? 'orphaned'
|
|
118
|
+
: states.includes('drifted') ? 'drifted'
|
|
119
|
+
: states.every((s) => s === 'verified') ? 'verified'
|
|
120
|
+
: 'implemented';
|
|
121
|
+
}
|
|
122
|
+
return { specs: docs, linkStates };
|
|
103
123
|
});
|
|
104
124
|
// Spec lifecycle funnel: idea → spec → implemented (REQ-FUNNEL-006). Computed
|
|
105
125
|
// on the dynamically-loaded SpecShip instance — no runtime import of the
|
|
@@ -18,8 +18,9 @@ import Fastify from 'fastify';
|
|
|
18
18
|
import cors from '@fastify/cors';
|
|
19
19
|
import { startWatcher, primaryProjectMatcher } from './ingest/index.js';
|
|
20
20
|
import { backfillDisplaced } from './ingest/impact-backfill.js';
|
|
21
|
+
import { recostUnpricedPrompts } from './ingest/pricing-backfill.js';
|
|
21
22
|
import { ProjectRegistry } from './project-registry.js';
|
|
22
|
-
import { makeStaticHandler } from './static-handler.js';
|
|
23
|
+
import { makeStaticHandler, isAssetPath } from './static-handler.js';
|
|
23
24
|
import { registerGraphRoutes } from './routes/graph.js';
|
|
24
25
|
import { registerSpecRoutes } from './routes/spec.js';
|
|
25
26
|
import { registerWorkflowRoutes } from './routes/workflow.js';
|
|
@@ -86,14 +87,22 @@ export async function createServer(options) {
|
|
|
86
87
|
// Lazy-load specship. Used as the open() impl for the registry, and
|
|
87
88
|
// (only when a primary path is set) to open the primary instance below.
|
|
88
89
|
const cgMod = await loadSpecShip();
|
|
89
|
-
|
|
90
|
+
// maxOpen must exceed the /api/events cross-project sweep size (10) plus
|
|
91
|
+
// the primary and a user-picked project, or the sweep churns open/close
|
|
92
|
+
// cycles through the LRU on every poll.
|
|
93
|
+
const registry = new ProjectRegistry({ verbose, maxOpen: 16 }, (p) => cgMod.SpecShip.open(p));
|
|
90
94
|
// Primary project (optional). When set, specship-scoped routes default to
|
|
91
95
|
// it when no `?project=<slug>` is provided, and the analytics ingest
|
|
92
96
|
// hooks into its SQLite handle.
|
|
93
97
|
let primaryCg = null;
|
|
94
98
|
if (options.projectRoot) {
|
|
95
99
|
primaryCg = await registry.get(options.projectRoot);
|
|
96
|
-
if (
|
|
100
|
+
if (primaryCg) {
|
|
101
|
+
// Routes capture this instance by reference — it must never be LRU
|
|
102
|
+
// evicted (eviction closes the SQLite handle and every request 500s).
|
|
103
|
+
registry.pin(options.projectRoot);
|
|
104
|
+
}
|
|
105
|
+
else if (verbose) {
|
|
97
106
|
console.error(`[specship-server] primary project ${options.projectRoot} not initialized — booting projectless`);
|
|
98
107
|
}
|
|
99
108
|
}
|
|
@@ -134,6 +143,16 @@ export async function createServer(options) {
|
|
|
134
143
|
catch (err) {
|
|
135
144
|
console.error('[specship-server] specship-impact backfill failed (non-fatal):', err instanceof Error ? err.message : String(err));
|
|
136
145
|
}
|
|
146
|
+
// Re-cost prompts ingested while their model family was unpriced,
|
|
147
|
+
// e.g. fable sessions stuck at $0 (REQ-DASHINT-001.A2). Idempotent.
|
|
148
|
+
try {
|
|
149
|
+
const recosted = recostUnpricedPrompts(dbHandle);
|
|
150
|
+
if (verbose && recosted > 0)
|
|
151
|
+
console.error(`[specship-server] re-costed ${recosted} unpriced prompts`);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
console.error('[specship-server] pricing backfill failed (non-fatal):', err instanceof Error ? err.message : String(err));
|
|
155
|
+
}
|
|
137
156
|
}
|
|
138
157
|
}
|
|
139
158
|
else if (!primaryCg && options.ingest !== false && verbose) {
|
|
@@ -174,6 +193,14 @@ export async function createServer(options) {
|
|
|
174
193
|
await registerMaintainabilityRoutes(app);
|
|
175
194
|
await registerDomainRoutes(app);
|
|
176
195
|
await registerChatRoutes(app);
|
|
196
|
+
// SSR dashboard (DASH-LEAN-DOC / REQ-DASHLEAN-004): explicit page routes
|
|
197
|
+
// rendered in-process from the /api handlers above (via app.inject), plus the
|
|
198
|
+
// dashboard's static assets. Registered AFTER /api and BEFORE the SPA
|
|
199
|
+
// fallback so the explicit page routes win.
|
|
200
|
+
if (options.ssr) {
|
|
201
|
+
const { registerSsrRoutes } = await import('./ssr/routes.js');
|
|
202
|
+
await registerSsrRoutes(app);
|
|
203
|
+
}
|
|
177
204
|
// Optional: serve the built Angular UI from `webDir` and fall back to
|
|
178
205
|
// index.html for client-side routes. Must register AFTER the /api/*
|
|
179
206
|
// routes so they take precedence over the SPA wildcard.
|
|
@@ -198,7 +225,10 @@ export async function createServer(options) {
|
|
|
198
225
|
// 2. Fall through to index.html for any other GET so Angular's
|
|
199
226
|
// client-side router can take over (`/memory`, `/graph`, …).
|
|
200
227
|
// Non-GET methods and `/api/*` paths still 404 cleanly so the UI
|
|
201
|
-
// can surface them.
|
|
228
|
+
// can surface them. A MISSING asset path (e.g. a stale build's old
|
|
229
|
+
// content-hashed bundle) must also 404, never receive the shell —
|
|
230
|
+
// HTML under an asset URL poisons the offline service worker's cache
|
|
231
|
+
// (REQ-OFFLINE-005).
|
|
202
232
|
app.setNotFoundHandler((request, reply) => {
|
|
203
233
|
if (request.method !== 'GET') {
|
|
204
234
|
reply.code(404).send({ error: 'not found' });
|
|
@@ -213,6 +243,10 @@ export async function createServer(options) {
|
|
|
213
243
|
reply.code(200).type(hit.contentType).send(hit.body);
|
|
214
244
|
return;
|
|
215
245
|
}
|
|
246
|
+
if (isAssetPath(request.url)) {
|
|
247
|
+
reply.code(404).send({ error: 'not found' });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
216
250
|
reply.code(200).type('text/html').send(cachedIndex);
|
|
217
251
|
});
|
|
218
252
|
if (verbose)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, dependency-free Markdown → HTML for the SSR dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Scope is deliberately small: the subset spec bodies actually use — headings,
|
|
5
|
+
* bold/italic/inline-code, fenced code, unordered/ordered lists, and paragraphs.
|
|
6
|
+
* Everything is HTML-escaped first, so rendering is safe for untrusted spec
|
|
7
|
+
* text (no raw HTML passthrough). This is the SSR counterpart to the Angular
|
|
8
|
+
* app's `render-md.ts`; keeping it dependency-free is what keeps the web-ssr
|
|
9
|
+
* tree under the REQ-DASHLEAN-002 package ceiling.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export function escapeHtml(s) {
|
|
13
|
+
return String(s)
|
|
14
|
+
.replace(/&/g, '&')
|
|
15
|
+
.replace(/</g, '<')
|
|
16
|
+
.replace(/>/g, '>')
|
|
17
|
+
.replace(/"/g, '"')
|
|
18
|
+
.replace(/'/g, ''');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Strip embedded `<!-- id: REQ-X -->` markers — structural noise, not prose. */
|
|
22
|
+
export function stripSpecMarkers(s) {
|
|
23
|
+
return String(s).replace(/<!--[\s\S]*?-->/g, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function inline(s) {
|
|
27
|
+
// Operate on already-escaped text so the markup we add is the only HTML.
|
|
28
|
+
return s
|
|
29
|
+
.replace(/`([^`]+)`/g, (_, c) => `<code>${c}</code>`)
|
|
30
|
+
.replace(/\*\*([^*]+)\*\*/g, (_, c) => `<strong>${c}</strong>`)
|
|
31
|
+
.replace(/(^|[^*])\*([^*]+)\*/g, (_, p, c) => `${p}<em>${c}</em>`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function renderMd(src) {
|
|
35
|
+
const lines = escapeHtml(stripSpecMarkers(src ?? '')).split('\n');
|
|
36
|
+
const out = [];
|
|
37
|
+
let i = 0;
|
|
38
|
+
let para = [];
|
|
39
|
+
const flushPara = () => {
|
|
40
|
+
if (para.length) { out.push(`<p>${inline(para.join(' ')).trim()}</p>`); para = []; }
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
while (i < lines.length) {
|
|
44
|
+
const line = lines[i];
|
|
45
|
+
|
|
46
|
+
// fenced code block
|
|
47
|
+
const fence = line.match(/^```(.*)$/);
|
|
48
|
+
if (fence) {
|
|
49
|
+
flushPara();
|
|
50
|
+
const body = [];
|
|
51
|
+
i++;
|
|
52
|
+
while (i < lines.length && !/^```/.test(lines[i])) { body.push(lines[i]); i++; }
|
|
53
|
+
i++; // closing fence
|
|
54
|
+
out.push(`<pre><code>${body.join('\n')}</code></pre>`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// heading
|
|
59
|
+
const h = line.match(/^(#{1,6})\s+(.*)$/);
|
|
60
|
+
if (h) { flushPara(); const lvl = h[1].length; out.push(`<h${lvl}>${inline(h[2].trim())}</h${lvl}>`); i++; continue; }
|
|
61
|
+
|
|
62
|
+
// list (consume a run of bullet / ordered lines)
|
|
63
|
+
if (/^\s*([-*]|\d+\.)\s+/.test(line)) {
|
|
64
|
+
flushPara();
|
|
65
|
+
const ordered = /^\s*\d+\.\s+/.test(line);
|
|
66
|
+
const items = [];
|
|
67
|
+
while (i < lines.length && /^\s*([-*]|\d+\.)\s+/.test(lines[i])) {
|
|
68
|
+
items.push(`<li>${inline(lines[i].replace(/^\s*([-*]|\d+\.)\s+/, '').trim())}</li>`);
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
out.push(ordered ? `<ol>${items.join('')}</ol>` : `<ul>${items.join('')}</ul>`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// blank line ends a paragraph
|
|
76
|
+
if (/^\s*$/.test(line)) { flushPara(); i++; continue; }
|
|
77
|
+
|
|
78
|
+
para.push(line.trim());
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
flushPara();
|
|
82
|
+
return out.join('\n');
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@charset "UTF-8";@layer properties;@layer theme,base,components,utilities;@layer theme{:root,:host{--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing: .25rem;--ease-out: cubic-bezier(0, 0, .2, 1);--ease-in-out: cubic-bezier(.4, 0, .2, 1);--default-transition-duration: .15s;--default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);--default-font-family: var(--font-sans);--default-mono-font-family: var(--font-mono)}}@layer base{*,:after,:before,::backdrop,::file-selector-button{box-sizing:border-box;margin:0;padding:0;border:0 solid}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;tab-size:4;font-family:var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings, normal);font-variation-settings:var(--default-font-variation-settings, normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings, normal);font-variation-settings:var(--default-mono-font-variation-settings, normal);font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea,::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;border-radius:0;background-color:transparent;opacity:1}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px){::placeholder{color:currentcolor}@supports (color: color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]),::file-selector-button{appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer utilities{.collapse{visibility:collapse}.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.static{position:static}.top-18{top:calc(var(--spacing) * 18)}.top-60{top:calc(var(--spacing) * 60)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.flex-shrink{flex-shrink:1}.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.flex-wrap{flex-wrap:wrap}.gap-2{gap:calc(var(--spacing) * 2)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-7{gap:calc(var(--spacing) * 7)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-10{gap:calc(var(--spacing) * 10)}.gap-12{gap:calc(var(--spacing) * 12)}.gap-16{gap:calc(var(--spacing) * 16)}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.border{border-style:var(--tw-border-style);border-width:1px}.capitalize{text-transform:capitalize}.ring{--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.invert{--tw-invert: invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function));transition-duration:var(--tw-duration, var(--default-transition-duration))}.ease-in-out{--tw-ease: var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease: var(--ease-out);transition-timing-function:var(--ease-out)}}:root{color-scheme:dark;--bg-canvas: #0E1014;--bg-canvas-2: #0B0D11;--bg-panel: #161A21;--bg-panel-2: #1A1F28;--bg-elevated: #20262F;--bg-hover: rgba(255,255,255,.04);--bg-active: rgba(255,255,255,.07);--border-subtle: rgba(255,255,255,.07);--border-strong: rgba(255,255,255,.12);--border-focus: #3B82F6;--text-primary: #E8EBF0;--text-secondary: #9AA3B2;--text-muted: #5C6573;--text-faint: #424B59;--accent: #3B82F6;--accent-strong: #2563EB;--accent-soft: rgba(59,130,246,.14);--accent-line: rgba(59,130,246,.35);--node-code: #A586F5;--node-spec: #5B93F2;--node-test: #46C26B;--node-route: #29D2BE;--node-code-soft: rgba(165,134,245,.14);--node-spec-soft: rgba(91,147,242,.14);--node-test-soft: rgba(70,194,107,.14);--node-route-soft: rgba(41,210,190,.14);--node-text: #ffffff;--dot-grid: rgba(255,255,255,.045);--success: #46C26B;--success-soft: rgba(70,194,107,.13);--warn: #E5A50A;--warn-soft: rgba(229,165,10,.14);--error: #F2555A;--error-soft: rgba(242,85,90,.14);--info: #58A6FF;--info-soft: rgba(88,166,255,.13);--font-ui: "Geist", system-ui, -apple-system, sans-serif;--font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;--fs-title: 22px;--fs-base: 13px;--fs-sm: 12px;--fs-xs: 11px;--fs-big: 28px;--r-sm: 5px;--r-md: 8px;--r-lg: 12px;--r-pill: 999px;--shadow-pop: 0 12px 32px -8px rgba(0,0,0,.6), 0 2px 8px rgba(0,0,0,.4);--shadow-panel: 0 1px 0 rgba(255,255,255,.02) inset;--sidebar-w: 232px;--sidebar-w-collapsed: 56px;--status-h: 30px}[data-theme=light]{color-scheme:light;--bg-canvas: #F4F6F8;--bg-canvas-2: #E9ECF1;--bg-panel: #FFFFFF;--bg-panel-2: #F8FAFC;--bg-elevated: #FFFFFF;--bg-hover: rgba(15,23,42,.045);--bg-active: rgba(15,23,42,.07);--border-subtle: rgba(15,23,42,.1);--border-strong: rgba(15,23,42,.18);--border-focus: #2563EB;--text-primary: #161B22;--text-secondary: #4A5566;--text-muted: #7B8696;--text-faint: #A9B2C0;--accent: #2563EB;--accent-strong: #2563EB;--accent-soft: rgba(37,99,235,.1);--accent-line: rgba(37,99,235,.3);--node-code: #7C56E0;--node-spec: #2F6FE0;--node-test: #1E9E4C;--node-route: #0E9C8E;--node-code-soft: rgba(124,86,224,.12);--node-spec-soft: rgba(47,111,224,.12);--node-test-soft: rgba(30,158,76,.12);--node-route-soft: rgba(14,156,142,.12);--node-text: #1A2230;--dot-grid: rgba(15,23,42,.07);--success: #1E9E4C;--success-soft: rgba(30,158,76,.12);--warn: #B27500;--warn-soft: rgba(178,117,0,.13);--error: #DC4A4F;--error-soft: rgba(220,74,79,.12);--info: #2563EB;--info-soft: rgba(37,99,235,.11);--shadow-pop: 0 12px 32px -10px rgba(15,23,42,.22), 0 2px 8px rgba(15,23,42,.1);--shadow-panel: 0 1px 0 rgba(15,23,42,.02) inset}*{box-sizing:border-box}html,body{margin:0;padding:0;height:100%;background:var(--bg-canvas);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--fs-base);line-height:1.45;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-feature-settings:"cv01","cv03","ss03";overflow:hidden}#root{height:100%}::selection{background:var(--accent-soft)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#ffffff14;border-radius:999px;border:2px solid transparent;background-clip:content-box}::-webkit-scrollbar-thumb:hover{background:#ffffff29;background-clip:content-box}.mono{font-family:var(--font-mono);font-feature-settings:"zero"}.tnum{font-variant-numeric:tabular-nums}.btn{font-family:var(--font-ui);font-size:var(--fs-base);font-weight:500;border:1px solid transparent;border-radius:var(--r-sm);padding:6px 12px;display:inline-flex;align-items:center;gap:7px;cursor:pointer;white-space:nowrap;transition:background .1s ease,border-color .1s ease,color .1s ease;-webkit-user-select:none;user-select:none}.btn svg{width:14px;height:14px;flex-shrink:0}.btn-primary{background:var(--accent-strong);color:#fff}.btn-primary:hover{background:var(--accent)}.btn-secondary{background:var(--bg-elevated);color:var(--text-primary);border-color:var(--border-subtle)}.btn-secondary:hover{background:#283039;border-color:var(--border-strong)}.btn-ghost{background:transparent;color:var(--text-secondary)}.btn-ghost:hover{background:var(--bg-hover);color:var(--text-primary)}.btn-destructive{background:var(--error-soft);color:var(--error);border-color:#f2555a4d}.btn-destructive:hover{background:#f2555a38}.btn-sm{padding:4px 9px;font-size:var(--fs-sm)}.btn-xs{padding:2px 7px;font-size:var(--fs-xs);border-radius:4px}.btn:disabled{opacity:.4;cursor:not-allowed}.pill{display:inline-flex;align-items:center;gap:5px;font-size:var(--fs-xs);font-weight:600;padding:2px 8px;border-radius:var(--r-pill);letter-spacing:.01em;white-space:nowrap;line-height:1.5}.pill svg{width:11px;height:11px}.pill-dot{width:6px;height:6px;border-radius:50%}.card{background:var(--bg-panel);border:1px solid var(--border-subtle);border-radius:var(--r-lg)}.card-pad{padding:14px}.input{background:var(--bg-canvas);border:1px solid var(--border-subtle);border-radius:var(--r-sm);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--fs-base);padding:6px 10px;outline:none;transition:border-color .1s ease,box-shadow .1s ease}.input:focus{border-color:var(--border-focus);box-shadow:0 0 0 3px var(--accent-soft)}.input::placeholder{color:var(--text-muted)}.eyebrow{font-size:var(--fs-xs);font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:var(--text-muted)}:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.row{display:flex;align-items:center}.col{display:flex;flex-direction:column}.gap-2{gap:2px}.gap-4{gap:4px}.gap-6{gap:6px}.gap-8{gap:8px}.gap-10{gap:10px}.gap-12{gap:12px}.gap-16{gap:16px}.grow{flex:1}.muted{color:var(--text-muted)}.secondary{color:var(--text-secondary)}.tabular{font-variant-numeric:tabular-nums}.scroll-y{overflow-y:auto;overflow-x:hidden}@keyframes pulsePill{0%{box-shadow:0 0 0 0 currentColor}to{box-shadow:0 0 0 8px transparent}}@keyframes slideInRight{0%{opacity:0;transform:translate(14px)}to{opacity:1;transform:translate(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes runningDash{to{stroke-dashoffset:-16}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes skeleton{0%{opacity:.5}50%{opacity:.9}to{opacity:.5}}.skel{background:var(--bg-elevated);border-radius:5px;animation:skeleton 1.4s ease-in-out infinite}@media(prefers-reduced-motion:reduce){*{animation-duration:.001ms!important;transition-duration:.001ms!important}}@property --tw-rotate-x{syntax: "*"; inherits: false;}@property --tw-rotate-y{syntax: "*"; inherits: false;}@property --tw-rotate-z{syntax: "*"; inherits: false;}@property --tw-skew-x{syntax: "*"; inherits: false;}@property --tw-skew-y{syntax: "*"; inherits: false;}@property --tw-border-style{syntax: "*"; inherits: false; initial-value: solid;}@property --tw-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: "*"; inherits: false;}@property --tw-shadow-alpha{syntax: "<percentage>"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: "*"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: "<percentage>"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: "*"; inherits: false;}@property --tw-ring-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: "*"; inherits: false;}@property --tw-inset-ring-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: "*"; inherits: false;}@property --tw-ring-offset-width{syntax: "<length>"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: "*"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-blur{syntax: "*"; inherits: false;}@property --tw-brightness{syntax: "*"; inherits: false;}@property --tw-contrast{syntax: "*"; inherits: false;}@property --tw-grayscale{syntax: "*"; inherits: false;}@property --tw-hue-rotate{syntax: "*"; inherits: false;}@property --tw-invert{syntax: "*"; inherits: false;}@property --tw-opacity{syntax: "*"; inherits: false;}@property --tw-saturate{syntax: "*"; inherits: false;}@property --tw-sepia{syntax: "*"; inherits: false;}@property --tw-drop-shadow{syntax: "*"; inherits: false;}@property --tw-drop-shadow-color{syntax: "*"; inherits: false;}@property --tw-drop-shadow-alpha{syntax: "<percentage>"; inherits: false; initial-value: 100%;}@property --tw-drop-shadow-size{syntax: "*"; inherits: false;}@property --tw-ease{syntax: "*"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x: initial;--tw-rotate-y: initial;--tw-rotate-z: initial;--tw-skew-x: initial;--tw-skew-y: initial;--tw-border-style: solid;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-blur: initial;--tw-brightness: initial;--tw-contrast: initial;--tw-grayscale: initial;--tw-hue-rotate: initial;--tw-invert: initial;--tw-opacity: initial;--tw-saturate: initial;--tw-sepia: initial;--tw-drop-shadow: initial;--tw-drop-shadow-color: initial;--tw-drop-shadow-alpha: 100%;--tw-drop-shadow-size: initial;--tw-ease: initial}}}
|