@wenathlan/argan 2.0.94 → 2.0.118
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 +20 -0
- package/docs/.gitkeep +0 -0
- package/package.json +24 -3
- package/tests/.gitkeep +0 -0
- package/tests/design.test.ts +130 -0
- package/tests/dnssec.test.ts +0 -0
- package/tests/family.test.ts +89 -0
- package/tests/zonediff.test.ts +0 -0
package/README.md
CHANGED
|
@@ -14,3 +14,23 @@ Argan is the DNS and gateway library of the DevThink family: authoritative zones
|
|
|
14
14
|
pnpm install
|
|
15
15
|
pnpm dev
|
|
16
16
|
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# The family contract (wave 6-a)
|
|
21
|
+
|
|
22
|
+
- `family.ts` — the single table of the eight siblings beside argan with the
|
|
23
|
+
spec §12 accent of each one; the shell rail foot, the landing footer and
|
|
24
|
+
`tests/family.test.ts` all consume this one table.
|
|
25
|
+
- `familyurl.ts` — the verbatim relative-url contract (`"../" + slug + "/"`)
|
|
26
|
+
that keeps every cross-app link mount-proof.
|
|
27
|
+
- `restore:entry` — the entry-face guard: a no-op while `index.html` rides the
|
|
28
|
+
canonical source face (the `./App.tsx` script plus the font triad link), a
|
|
29
|
+
restore from HEAD otherwise, and a refusal (exit 1, nothing touched) while
|
|
30
|
+
HEAD itself is not the canonical face. Run it after any local `vite build` —
|
|
31
|
+
the build rewrites the entry to the hashed face by design. Until the
|
|
32
|
+
source-face fix lands in a commit, HEAD carries the stale pre-triad face, so
|
|
33
|
+
the guard refuses instead of restoring an older face over the fix.
|
|
34
|
+
- The root build emits hashed bundles into the app folder (publish contract
|
|
35
|
+
`.`); `.gitignore` bars every hashed emit (`index-*`, `favicon-*`,
|
|
36
|
+
`manifest-*`, `icon-*`), so the tree keeps the source faces only.
|
package/docs/.gitkeep
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenathlan/argan",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.118",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/wenathlan/devthink"
|
|
7
7
|
},
|
|
8
|
-
"description": "argan is the DNS and gateway library of the wenathlan family: zones, records, dnssec and handshake
|
|
8
|
+
"description": "argan is the DNS and gateway library of the wenathlan family: zones, records, dnssec and handshake \u2014 the system network library whose site shares the unified database. The envelope distributes the documentation set, the zero-build site and the tests directory as one package.",
|
|
9
9
|
"keywords": [
|
|
10
10
|
"argan",
|
|
11
11
|
"dns",
|
|
@@ -17,6 +17,24 @@
|
|
|
17
17
|
"static-site"
|
|
18
18
|
],
|
|
19
19
|
"license": "GPL-3.0-only",
|
|
20
|
+
"engines": {
|
|
21
|
+
"bare": "^1.18.0",
|
|
22
|
+
"bun": "^1.4.2",
|
|
23
|
+
"chrome": "^16.0.912",
|
|
24
|
+
"cnpm": "^9.4.0",
|
|
25
|
+
"corepack": "^0.35.0",
|
|
26
|
+
"deno": "^2.9.5",
|
|
27
|
+
"ecmascript": "^5.0.0",
|
|
28
|
+
"firefox": ">=0.8.0",
|
|
29
|
+
"iojs": "^1.0.0",
|
|
30
|
+
"node": "^26.8.2",
|
|
31
|
+
"npm": "^12.1.0",
|
|
32
|
+
"pnpm": "^12.6.0",
|
|
33
|
+
"teleport": ">=0.2.0",
|
|
34
|
+
"typescript": "^5.8.0",
|
|
35
|
+
"vscode": "^1.118.0",
|
|
36
|
+
"yarn": "^4.17.1"
|
|
37
|
+
},
|
|
20
38
|
"files": [
|
|
21
39
|
"README.md",
|
|
22
40
|
"docs",
|
|
@@ -38,7 +56,9 @@
|
|
|
38
56
|
"start": "vite preview --host",
|
|
39
57
|
"preview": "vite preview --host",
|
|
40
58
|
"check": "tsc --noEmit",
|
|
41
|
-
"
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"restore:entry": "node -e \"const{readFileSync}=require('node:fs');const{execSync}=require('node:child_process');const script=/script[^>]*App\\.tsx/;const triad=/family=Inter[\\s\\S]*Geist\\+Mono/;const face=(h)=>script.test(h)&&triad.test(h);if(face(readFileSync('index.html','utf8'))){console.log('index.html already rides the source face');process.exit(0)}if(!face(execSync('git show HEAD:./index.html',{encoding:'utf8'}))){console.error('refusing: HEAD index.html is not the canonical source face yet (script App.tsx + the font triad) - land the source-face fix first; nothing was touched');process.exit(1)}execSync('git checkout -- index.html',{stdio:'inherit'});console.log('index.html restored from HEAD (the source face)')\""
|
|
42
62
|
},
|
|
43
63
|
"dependencies": {
|
|
44
64
|
"@hono/node-server": "^2.1.3",
|
|
@@ -54,6 +74,7 @@
|
|
|
54
74
|
"@capacitor/cli": "^8.5.2",
|
|
55
75
|
"@capacitor/core": "^8.5.2",
|
|
56
76
|
"@capacitor/ios": "^8.5.2",
|
|
77
|
+
"@fluentui/react-icons": "^2.0.344",
|
|
57
78
|
"@hookform/resolvers": "^5.9.1",
|
|
58
79
|
"@libsql/client": "^0.18.0",
|
|
59
80
|
"@opencode-ai/sdk": "^1.18.34",
|
package/tests/.gitkeep
CHANGED
|
File without changes
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// # design.test — the design contract of the argan surface, runnable with the
|
|
2
|
+
// vitest runner:
|
|
3
|
+
// pnpm test
|
|
4
|
+
// The file reads the shipped sources (stylesheet, entry face, brand mark,
|
|
5
|
+
// app identity, route anchor) and pins the family laws: no banned hue, no
|
|
6
|
+
// pill, no all-properties transition, no zero-scale entry (the windows
|
|
7
|
+
// dtPopIntro bounce excepted), the trylle font stacks, the source-face entry,
|
|
8
|
+
// the reduced-motion stop, tabular numerals, the sticky-footer law, the honest
|
|
9
|
+
// page set of the application identity and the first-run flow contract (the
|
|
10
|
+
// entry gate, the shared onboarding state, the db mirror).
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { describe, expect, it } from "vitest";
|
|
13
|
+
|
|
14
|
+
function read(path: string): string {
|
|
15
|
+
return readFileSync(new URL(path, import.meta.url), "utf8");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const CSS = read("../Sol/sol.css");
|
|
19
|
+
const ENTRY = read("../index.html");
|
|
20
|
+
const MARK = read("../Sol/shell/Shell.tsx");
|
|
21
|
+
const TOML = read("../devthink.toml");
|
|
22
|
+
const SOL = read("../Sol/Sol.tsx");
|
|
23
|
+
const FAVICON = read("../favicon.svg");
|
|
24
|
+
const ICON = read("../icon.svg");
|
|
25
|
+
const MANIFEST = read("../manifest.webmanifest");
|
|
26
|
+
|
|
27
|
+
const BANNED = [/3b82f6/i, /2563eb/i, /f97316/i];
|
|
28
|
+
|
|
29
|
+
describe("design contract", () => {
|
|
30
|
+
it("ships no banned hue in the stylesheet, the entry, the mark or the icons", () => {
|
|
31
|
+
for (const source of [CSS, ENTRY, MARK, FAVICON, ICON, MANIFEST]) {
|
|
32
|
+
for (const banned of BANNED) {
|
|
33
|
+
expect(banned.test(source), `banned hue ${banned} found`).toBe(false);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("carries the argan jade #34d8a8 as the one signal (mark story #14b98c)", () => {
|
|
39
|
+
expect(CSS).toMatch(/--sol-primary: #34d8a8/);
|
|
40
|
+
expect(CSS).toMatch(/--arg-jade: #34d8a8/);
|
|
41
|
+
expect(MARK).toMatch(/#14b98c/);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("keeps the pill out of the stylesheet (no 999px, no stadium radius)", () => {
|
|
45
|
+
expect(CSS.includes("999px"), "the pill radius is retired").toBe(false);
|
|
46
|
+
expect(CSS.includes("--r-pill"), "no pill token survives").toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("never ships an all-properties transition", () => {
|
|
50
|
+
expect(/transition:\s*all/.test(CSS), "transition: all is a reprovação").toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("never enters an element from zero scale (the windows dtPopIntro bounce excepted)", () => {
|
|
54
|
+
const sansPopIntro = CSS.replace(/@keyframes dtPopIntro \{[\s\S]*?\n\}/g, "");
|
|
55
|
+
expect(
|
|
56
|
+
/scale\(0\)/.test(sansPopIntro),
|
|
57
|
+
"scale(0) entries are a reprovação (the dtPopIntro bounce of the windows recipe excepted)",
|
|
58
|
+
).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("declares the trylle font stacks (Inter reading, Geist Mono detail)", () => {
|
|
62
|
+
expect(CSS).toMatch(/--font-sans: "Inter Variable"/);
|
|
63
|
+
expect(CSS).toMatch(/--font-mono: "Geist Mono Variable"/);
|
|
64
|
+
expect(ENTRY).toMatch(/family=Inter/);
|
|
65
|
+
expect(ENTRY).toMatch(/Geist\+Mono/);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("keeps the entry on the source face (./App.tsx, canonical assets, no hashed ghosts)", () => {
|
|
69
|
+
expect(ENTRY).toMatch(/script[^>]*\.\/App\.tsx/);
|
|
70
|
+
expect(/-[0-9a-f]{8}\.(js|css)/.test(ENTRY), "no hashed bundle rides the entry").toBe(false);
|
|
71
|
+
expect(ENTRY).toMatch(/href="\.\/favicon\.svg"/);
|
|
72
|
+
expect(ENTRY).toMatch(/href="\.\/manifest\.webmanifest"/);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("guards the reduced-motion stop on every layer", () => {
|
|
76
|
+
const blocks = CSS.match(/@media \(prefers-reduced-motion: reduce\)/g) ?? [];
|
|
77
|
+
expect(blocks.length >= 4, `expected the hard stop on every layer, found ${blocks.length}`).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("keeps tabular numerals on the platform numbers", () => {
|
|
81
|
+
expect(CSS).toMatch(/font-variant-numeric:\s*tabular-nums/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("rests the title-bar mark on the landing (logo discipline)", () => {
|
|
85
|
+
expect(CSS).toMatch(/data-landing/);
|
|
86
|
+
expect(MARK).toMatch(/data-landing/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("pins the footer to the window floor (the sticky-footer law)", () => {
|
|
90
|
+
expect(CSS).toMatch(/\.winapp__stage > main\.shell \{[^}]*flex-direction: column/);
|
|
91
|
+
expect(CSS).toMatch(/\.r3a-foot \{[^}]*margin-top: auto/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("declares the honest page set (home, zones, dnssec, gateway, settings, notfound)", () => {
|
|
95
|
+
expect(TOML).toMatch(/pages = \["home", "zones", "dnssec", "gateway", "settings", "notfound"\]/);
|
|
96
|
+
for (const route of ['"/zones"', '"/dnssec"', '"/gateway"', '"/settings"', '"/404"']) {
|
|
97
|
+
expect(SOL.includes(`path=${route}`), `route ${route} must mount`).toBe(true);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("keeps the entry flow mounted (intro, onboarding) before the fundamentals", () => {
|
|
102
|
+
expect(SOL).toMatch(/path="\/intro"/);
|
|
103
|
+
expect(SOL).toMatch(/path="\/onboarding"/);
|
|
104
|
+
expect(SOL.indexOf('"/intro"')).toBeLessThan(SOL.indexOf('"/"'));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("carries the flow contract sources (the entry gate behind the home anchor, the shared state module)", () => {
|
|
108
|
+
const GATE = read("../Sol/home/entrygate.tsx");
|
|
109
|
+
const STATE = read("../onboardingstate.ts");
|
|
110
|
+
const HOME = read("../Sol/home/home.tsx");
|
|
111
|
+
const ONBOARDING = read("../Sol/onboarding/onboarding.tsx");
|
|
112
|
+
expect(GATE).toMatch(/navigate\("\/intro"/);
|
|
113
|
+
expect(GATE).toMatch(/navigate\("\/onboarding"/);
|
|
114
|
+
expect(HOME).toMatch(/EntryGate/);
|
|
115
|
+
expect(ONBOARDING).toMatch(/markOnboardingSeen/);
|
|
116
|
+
expect(STATE).toMatch(/argan\.onboarding\.seen/);
|
|
117
|
+
expect(STATE).toMatch(/\/api\/v1\/onboarding/);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("catalogues the first-run state in the db layer (prisma model + sqlite mirror)", () => {
|
|
121
|
+
const PRISMA = read("../schema.prisma");
|
|
122
|
+
const DB = read("../db.ts");
|
|
123
|
+
expect(PRISMA).toMatch(/model OnboardingState \{/);
|
|
124
|
+
expect(PRISMA).toMatch(/introseen\s+Boolean\s+@default\(false\)/);
|
|
125
|
+
expect(PRISMA).toMatch(/onboardingseen\s+Boolean\s+@default\(false\)/);
|
|
126
|
+
expect(DB).toMatch(/CREATE TABLE IF NOT EXISTS onboarding_state/);
|
|
127
|
+
expect(DB).toMatch(/getonboardingstate/);
|
|
128
|
+
expect(DB).toMatch(/setonboardingstage/);
|
|
129
|
+
});
|
|
130
|
+
});
|
package/tests/dnssec.test.ts
CHANGED
|
File without changes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// # family.test — the family table the chrome rides, runnable with the
|
|
2
|
+
// vitest runner:
|
|
3
|
+
// pnpm test
|
|
4
|
+
// Every test watches the promise the row makes: the eight siblings beside
|
|
5
|
+
// argan are listed once, every slug resolves through the familyurl contract
|
|
6
|
+
// into a climb-one-level url, every member carries the spec §12 accent with
|
|
7
|
+
// its own dot ink, and no accent ever collides with the banned palette.
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { describe, it } from "vitest";
|
|
10
|
+
import { FAMILY_ACCENTS, FAMILY_SLUGS, familyrow } from "../family.ts";
|
|
11
|
+
import { familyurl } from "../familyurl.ts";
|
|
12
|
+
|
|
13
|
+
const BANNED = new Set(["#3b82f6", "#2563eb", "#f97316"]);
|
|
14
|
+
|
|
15
|
+
describe("family table", () => {
|
|
16
|
+
it("lists the eight siblings beside argan, never argan itself", () => {
|
|
17
|
+
assert.deepEqual(FAMILY_SLUGS, [
|
|
18
|
+
"cadria",
|
|
19
|
+
"debonair",
|
|
20
|
+
"forge",
|
|
21
|
+
"foundry",
|
|
22
|
+
"getry",
|
|
23
|
+
"saddle",
|
|
24
|
+
"stealthhead",
|
|
25
|
+
"vault",
|
|
26
|
+
]);
|
|
27
|
+
assert.ok(!FAMILY_SLUGS.includes("argan"));
|
|
28
|
+
assert.ok(!FAMILY_SLUGS.includes("devthink"));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("answers an accent for every listed sibling", () => {
|
|
32
|
+
for (const slug of FAMILY_SLUGS) {
|
|
33
|
+
const member = FAMILY_ACCENTS[slug];
|
|
34
|
+
assert.ok(member, `${slug} must carry an accent`);
|
|
35
|
+
assert.equal(member.slug, slug);
|
|
36
|
+
assert.match(member.accent, /^#[0-9a-f]{6}$/);
|
|
37
|
+
assert.match(member.dotInk, /^#[0-9a-f]{6}$/);
|
|
38
|
+
}
|
|
39
|
+
// the table holds exactly the siblings — no orphan accents
|
|
40
|
+
assert.deepEqual(Object.keys(FAMILY_ACCENTS).sort(), [...FAMILY_SLUGS].sort());
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("resolves every member through the familyurl contract", () => {
|
|
44
|
+
for (const slug of FAMILY_SLUGS) {
|
|
45
|
+
assert.equal(familyurl(slug), `../${slug}/`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("strips decorative slashes and normalizes the slug", () => {
|
|
50
|
+
assert.equal(familyurl("/cadria/"), "../cadria/");
|
|
51
|
+
assert.equal(familyurl(" getry "), "../ getry /");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("keeps the spec §12 accents stable", () => {
|
|
55
|
+
assert.equal(FAMILY_ACCENTS.cadria.accent, "#f472b6");
|
|
56
|
+
assert.equal(FAMILY_ACCENTS.debonair.accent, "#d9962e");
|
|
57
|
+
assert.equal(FAMILY_ACCENTS.forge.accent, "#a2cb3a");
|
|
58
|
+
assert.equal(FAMILY_ACCENTS.foundry.accent, "#2fb8b5");
|
|
59
|
+
assert.equal(FAMILY_ACCENTS.getry.accent, "#8b5cf6");
|
|
60
|
+
assert.equal(FAMILY_ACCENTS.saddle.accent, "#e86f2d");
|
|
61
|
+
assert.equal(FAMILY_ACCENTS.stealthhead.accent, "#e8563f");
|
|
62
|
+
assert.equal(FAMILY_ACCENTS.vault.accent, "#a3d7e6");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("never ships a banned hue in the row", () => {
|
|
66
|
+
for (const member of familyrow()) {
|
|
67
|
+
assert.ok(!BANNED.has(member.accent), `${member.slug} rides a banned accent`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("builds the rail row in slug order without gaps", () => {
|
|
72
|
+
const row = familyrow();
|
|
73
|
+
assert.equal(row.length, FAMILY_SLUGS.length);
|
|
74
|
+
assert.deepEqual(
|
|
75
|
+
row.map((member) => member.slug),
|
|
76
|
+
[...FAMILY_SLUGS],
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("keeps every dot ink readable on its own accent (the ink is darker)", () => {
|
|
81
|
+
for (const member of familyrow()) {
|
|
82
|
+
const lum = (hex: string): number => {
|
|
83
|
+
const channel = (offset: number) => Number.parseInt(hex.slice(offset, offset + 2), 16) / 255;
|
|
84
|
+
return 0.2126 * channel(1) + 0.7152 * channel(3) + 0.0722 * channel(5);
|
|
85
|
+
};
|
|
86
|
+
assert.ok(lum(member.dotInk) < lum(member.accent), `${member.slug} dot ink must be darker than its accent`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
package/tests/zonediff.test.ts
CHANGED
|
File without changes
|