Overview
- The governance register is currently unreachable. No live data can be - shown. + The governance register is currently unreachable. No live data can be shown.
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000..cb6e507
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,2 @@
+# Bulk reformats. `git config blame.ignoreRevsFile .git-blame-ignore-revs`
+5983e7ef7fcff2be90e5660cfab296dee4bcd7c1 # prettier, 87 files
diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml
index 6b1008e..781407b 100644
--- a/.github/workflows/auto-merge.yml
+++ b/.github/workflows/auto-merge.yml
@@ -18,10 +18,10 @@ name: Auto-merge
on:
workflow_run:
- workflows: ['CI']
+ workflows: ["CI"]
types: [completed]
schedule:
- - cron: '*/10 * * * *'
+ - cron: "*/10 * * * *"
workflow_dispatch: {}
permissions:
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..b698663
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,31 @@
+# Build output and vendored trees — formatting these is noise.
+node_modules
+.next
+dist
+build
+out
+coverage
+.turbo
+.vercel
+*.min.js
+*.min.css
+
+# Generated during a build, so it is absent locally and present in CI — which
+# makes a clean local --check no evidence at all. Contentlayer's output also
+# uses import assertions, which prettier's parser rejects outright.
+.contentlayer
+.astro
+.svelte-kit
+storybook-static
+test-results
+playwright-report
+
+# Lockfiles are generated; prettier would rewrite them wholesale.
+package-lock.json
+pnpm-lock.yaml
+yarn.lock
+
+# Markdown is deliberately out of scope for now. Prettier rewraps prose, which
+# is where it is most opinionated and least useful, and it would bury the real
+# diff. Remove this line when you want docs formatted too.
+*.md
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..a2f11f0
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,9 @@
+{
+ "semi": true,
+ "singleQuote": false,
+ "printWidth": 100,
+ "tabWidth": 2,
+ "trailingComma": "all",
+ "arrowParens": "always",
+ "endOfLine": "lf"
+}
diff --git a/i18n/de.json b/i18n/de.json
index 55c2e75..8a9009d 100644
--- a/i18n/de.json
+++ b/i18n/de.json
@@ -1,31 +1,31 @@
{
- "solon": {
- "title": "SOLON",
- "tagline": "Bitcoin-native Governance für das digitale Zeitalter",
- "subtag": "Transparente Entscheidungen und Schatzkammern für Organisationen aus Menschen und KI-Agenten.",
- "cta_primary": "Letzte Abstimmung ansehen",
- "cta_secondary": "Schatzkammer ansehen",
- "bullets": {
- "transparency": "Watch-only-Schatzkammern — Solon hält keine Schlüssel, keine Gelder",
- "democracy": "Bitcoin-signierte Stimmen, von jedem überprüfbar",
- "global": "Unveränderliches, öffentliches Audit-Protokoll"
- }
- },
- "home": {
- "made_in": "Hergestellt in der Schweiz",
- "pillars_section": {
- "title": "Was Solon regiert",
- "subtitle": "Schatzkammer, Abstimmung, Entscheidungen und Audit — als ein System entworfen, jede Aussage am laufenden System überprüfbar."
- },
- "demo_section": {
- "title": "Transparenz in Aktion",
- "subtitle": "Stimmen sind Bitcoin-signierte Nachrichten. Die Schatzkammer ist watch-only on-chain. Das Audit-Protokoll ist unveränderlich und öffentlich."
+ "solon": {
+ "title": "SOLON",
+ "tagline": "Bitcoin-native Governance für das digitale Zeitalter",
+ "subtag": "Transparente Entscheidungen und Schatzkammern für Organisationen aus Menschen und KI-Agenten.",
+ "cta_primary": "Letzte Abstimmung ansehen",
+ "cta_secondary": "Schatzkammer ansehen",
+ "bullets": {
+ "transparency": "Watch-only-Schatzkammern — Solon hält keine Schlüssel, keine Gelder",
+ "democracy": "Bitcoin-signierte Stimmen, von jedem überprüfbar",
+ "global": "Unveränderliches, öffentliches Audit-Protokoll"
+ }
},
- "cta": {
- "title": "Regieren im Offenen",
- "subtitle": "Jede Entscheidung, jede Stimme, jeder Satoshi — signiert, protokolliert und von jedem überprüfbar.",
- "primary": "Dashboard öffnen",
- "secondary": "Funktionen ansehen"
+ "home": {
+ "made_in": "Hergestellt in der Schweiz",
+ "pillars_section": {
+ "title": "Was Solon regiert",
+ "subtitle": "Schatzkammer, Abstimmung, Entscheidungen und Audit — als ein System entworfen, jede Aussage am laufenden System überprüfbar."
+ },
+ "demo_section": {
+ "title": "Transparenz in Aktion",
+ "subtitle": "Stimmen sind Bitcoin-signierte Nachrichten. Die Schatzkammer ist watch-only on-chain. Das Audit-Protokoll ist unveränderlich und öffentlich."
+ },
+ "cta": {
+ "title": "Regieren im Offenen",
+ "subtitle": "Jede Entscheidung, jede Stimme, jeder Satoshi — signiert, protokolliert und von jedem überprüfbar.",
+ "primary": "Dashboard öffnen",
+ "secondary": "Funktionen ansehen"
+ }
}
- }
-}
\ No newline at end of file
+}
diff --git a/i18n/en.json b/i18n/en.json
index 1a5ab07..3faa382 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1,31 +1,31 @@
{
- "solon": {
- "title": "SOLON",
- "tagline": "Bitcoin-Native Governance for the Digital Age",
- "subtag": "Transparent decision-making and treasuries for organizations of humans and AI agents.",
- "cta_primary": "See the latest vote",
- "cta_secondary": "See the treasury",
- "bullets": {
- "transparency": "Watch-only treasuries — Solon holds no keys, no funds",
- "democracy": "Bitcoin-signed votes anyone can re-verify",
- "global": "Append-only audit trail, public by default"
- }
- },
- "home": {
- "made_in": "Made in Switzerland",
- "pillars_section": {
- "title": "What Solon Governs",
- "subtitle": "Treasury, voting, decisions, and audit — designed as one system, with every claim verifiable against the running platform."
- },
- "demo_section": {
- "title": "See Transparency in Action",
- "subtitle": "Votes are Bitcoin-signed messages. The treasury is watch-only on-chain. The audit trail is append-only and public."
+ "solon": {
+ "title": "SOLON",
+ "tagline": "Bitcoin-Native Governance for the Digital Age",
+ "subtag": "Transparent decision-making and treasuries for organizations of humans and AI agents.",
+ "cta_primary": "See the latest vote",
+ "cta_secondary": "See the treasury",
+ "bullets": {
+ "transparency": "Watch-only treasuries — Solon holds no keys, no funds",
+ "democracy": "Bitcoin-signed votes anyone can re-verify",
+ "global": "Append-only audit trail, public by default"
+ }
},
- "cta": {
- "title": "Govern in the open",
- "subtitle": "Every decision and every vote — signed, recorded, and re-verifiable by anyone.",
- "primary": "Open the Dashboard",
- "secondary": "See the features"
+ "home": {
+ "made_in": "Made in Switzerland",
+ "pillars_section": {
+ "title": "What Solon Governs",
+ "subtitle": "Treasury, voting, decisions, and audit — designed as one system, with every claim verifiable against the running platform."
+ },
+ "demo_section": {
+ "title": "See Transparency in Action",
+ "subtitle": "Votes are Bitcoin-signed messages. The treasury is watch-only on-chain. The audit trail is append-only and public."
+ },
+ "cta": {
+ "title": "Govern in the open",
+ "subtitle": "Every decision and every vote — signed, recorded, and re-verifiable by anyone.",
+ "primary": "Open the Dashboard",
+ "secondary": "See the features"
+ }
}
- }
-}
\ No newline at end of file
+}
diff --git a/i18n/fr.json b/i18n/fr.json
index c7f9445..2067c15 100644
--- a/i18n/fr.json
+++ b/i18n/fr.json
@@ -1,31 +1,31 @@
{
- "solon": {
- "title": "SOLON",
- "tagline": "Gouvernance native Bitcoin pour l'ère numérique",
- "subtag": "Décisions et trésoreries transparentes pour des organisations d'humains et d'agents IA.",
- "cta_primary": "Voir le dernier vote",
- "cta_secondary": "Voir la trésorerie",
- "bullets": {
- "transparency": "Trésoreries en lecture seule — Solon ne détient ni clés ni fonds",
- "democracy": "Votes signés Bitcoin, vérifiables par tous",
- "global": "Journal d'audit public et immuable"
- }
- },
- "home": {
- "made_in": "Conçu en Suisse",
- "pillars_section": {
- "title": "Ce que Solon gouverne",
- "subtitle": "Trésorerie, vote, décisions et audit — conçus comme un seul système, chaque affirmation vérifiable sur la plateforme en marche."
- },
- "demo_section": {
- "title": "La transparence en action",
- "subtitle": "Les votes sont des messages signés Bitcoin. La trésorerie est en lecture seule on-chain. Le journal d'audit est immuable et public."
+ "solon": {
+ "title": "SOLON",
+ "tagline": "Gouvernance native Bitcoin pour l'ère numérique",
+ "subtag": "Décisions et trésoreries transparentes pour des organisations d'humains et d'agents IA.",
+ "cta_primary": "Voir le dernier vote",
+ "cta_secondary": "Voir la trésorerie",
+ "bullets": {
+ "transparency": "Trésoreries en lecture seule — Solon ne détient ni clés ni fonds",
+ "democracy": "Votes signés Bitcoin, vérifiables par tous",
+ "global": "Journal d'audit public et immuable"
+ }
},
- "cta": {
- "title": "Gouverner au grand jour",
- "subtitle": "Chaque décision, chaque vote, chaque satoshi — signé, enregistré et vérifiable par tous.",
- "primary": "Ouvrir le tableau de bord",
- "secondary": "Voir les fonctionnalités"
+ "home": {
+ "made_in": "Conçu en Suisse",
+ "pillars_section": {
+ "title": "Ce que Solon gouverne",
+ "subtitle": "Trésorerie, vote, décisions et audit — conçus comme un seul système, chaque affirmation vérifiable sur la plateforme en marche."
+ },
+ "demo_section": {
+ "title": "La transparence en action",
+ "subtitle": "Les votes sont des messages signés Bitcoin. La trésorerie est en lecture seule on-chain. Le journal d'audit est immuable et public."
+ },
+ "cta": {
+ "title": "Gouverner au grand jour",
+ "subtitle": "Chaque décision, chaque vote, chaque satoshi — signé, enregistré et vérifiable par tous.",
+ "primary": "Ouvrir le tableau de bord",
+ "secondary": "Voir les fonctionnalités"
+ }
}
- }
-}
\ No newline at end of file
+}
diff --git a/i18n/it.json b/i18n/it.json
index 10948aa..2eb948f 100644
--- a/i18n/it.json
+++ b/i18n/it.json
@@ -1,31 +1,31 @@
{
- "solon": {
- "title": "SOLON",
- "tagline": "Governance nativa Bitcoin per l'era digitale",
- "subtag": "Decisioni e tesorerie trasparenti per organizzazioni di persone e agenti IA.",
- "cta_primary": "Vedi l'ultimo voto",
- "cta_secondary": "Vedi la tesoreria",
- "bullets": {
- "transparency": "Tesorerie in sola lettura — Solon non detiene chiavi né fondi",
- "democracy": "Voti firmati Bitcoin, verificabili da chiunque",
- "global": "Registro di audit pubblico e immutabile"
- }
- },
- "home": {
- "made_in": "Realizzato in Svizzera",
- "pillars_section": {
- "title": "Cosa governa Solon",
- "subtitle": "Tesoreria, voto, decisioni e audit — progettati come un unico sistema, ogni affermazione verificabile sulla piattaforma."
- },
- "demo_section": {
- "title": "La trasparenza in azione",
- "subtitle": "I voti sono messaggi firmati Bitcoin. La tesoreria è in sola lettura on-chain. Il registro di audit è immutabile e pubblico."
+ "solon": {
+ "title": "SOLON",
+ "tagline": "Governance nativa Bitcoin per l'era digitale",
+ "subtag": "Decisioni e tesorerie trasparenti per organizzazioni di persone e agenti IA.",
+ "cta_primary": "Vedi l'ultimo voto",
+ "cta_secondary": "Vedi la tesoreria",
+ "bullets": {
+ "transparency": "Tesorerie in sola lettura — Solon non detiene chiavi né fondi",
+ "democracy": "Voti firmati Bitcoin, verificabili da chiunque",
+ "global": "Registro di audit pubblico e immutabile"
+ }
},
- "cta": {
- "title": "Governare alla luce del sole",
- "subtitle": "Ogni decisione, ogni voto, ogni satoshi — firmato, registrato e verificabile da chiunque.",
- "primary": "Apri la dashboard",
- "secondary": "Vedi le funzionalità"
+ "home": {
+ "made_in": "Realizzato in Svizzera",
+ "pillars_section": {
+ "title": "Cosa governa Solon",
+ "subtitle": "Tesoreria, voto, decisioni e audit — progettati come un unico sistema, ogni affermazione verificabile sulla piattaforma."
+ },
+ "demo_section": {
+ "title": "La trasparenza in azione",
+ "subtitle": "I voti sono messaggi firmati Bitcoin. La tesoreria è in sola lettura on-chain. Il registro di audit è immutabile e pubblico."
+ },
+ "cta": {
+ "title": "Governare alla luce del sole",
+ "subtitle": "Ogni decisione, ogni voto, ogni satoshi — firmato, registrato e verificabile da chiunque.",
+ "primary": "Apri la dashboard",
+ "secondary": "Vedi le funzionalità"
+ }
}
- }
-}
\ No newline at end of file
+}
diff --git a/next.config.js b/next.config.js
index 40d7a42..4a3a161 100644
--- a/next.config.js
+++ b/next.config.js
@@ -3,7 +3,7 @@ const nextConfig = {
// Standalone output is required by the self-hosted Hetzner deploy
// (scripts/hetzner/deploy.sh stages the standalone build, same as every
// sibling app on the box).
- output: 'standalone',
+ output: "standalone",
};
-module.exports = nextConfig;
\ No newline at end of file
+module.exports = nextConfig;
diff --git a/package-lock.json b/package-lock.json
index 53a8c16..7f87093 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -35,12 +35,16 @@
"eslint": "10.9.1",
"eslint-config-next": "16.3.2",
"postcss": "8.5.26",
+ "prettier": "3.9.6",
"prisma": "7.9.1",
"puppeteer": "25.8.0",
"ts-node": "10.9.2",
"tsx": "^4.23.12",
"typescript": "5.5.3",
"vitest": "^4.1.11"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@alloc/quick-lru": {
@@ -8837,6 +8841,22 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.9.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
+ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/prisma": {
"version": "7.9.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-7.9.1.tgz",
diff --git a/package.json b/package.json
index dd22b3e..181eb93 100644
--- a/package.json
+++ b/package.json
@@ -12,13 +12,15 @@
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"design:check": "node scripts/design-system-check.js",
- "verify": "npm run lint && npm run typecheck && npm run design:check && npm run test",
+ "verify": "npm run format:check && npm run lint && npm run typecheck && npm run design:check && npm run test",
"test": "vitest run",
"test:e2e": "playwright test",
"test:puppeteer": "BASE_URL=${BASE_URL:-http://localhost:3000} node tests/puppeteer/smoke.mjs",
"test:puppeteer:mega": "BASE_URL=${BASE_URL:-http://localhost:3000} node tests/puppeteer/mega-menu.mjs",
"prisma:generate": "prisma generate",
- "prisma:push": "prisma db push"
+ "prisma:push": "prisma db push",
+ "format": "prettier --write .",
+ "format:check": "prettier --check ."
},
"dependencies": {
"@fleet/design-tokens": "github:catomean/design-tokens#v1.1.0",
@@ -48,6 +50,7 @@
"eslint": "10.9.1",
"eslint-config-next": "16.3.2",
"postcss": "8.5.26",
+ "prettier": "3.9.6",
"prisma": "7.9.1",
"puppeteer": "25.8.0",
"ts-node": "10.9.2",
diff --git a/playwright.config.ts b/playwright.config.ts
index 9d34351..5d883c4 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -1,17 +1,16 @@
-import { defineConfig, devices } from '@playwright/test';
+import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
- testDir: './tests/e2e',
+ testDir: "./tests/e2e",
fullyParallel: true,
- reporter: 'list',
+ reporter: "list",
use: {
- baseURL: process.env.BASE_URL || 'http://localhost:3000',
- trace: 'on-first-retry',
+ baseURL: process.env.BASE_URL || "http://localhost:3000",
+ trace: "on-first-retry",
},
projects: [
- { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
- { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
- { name: 'webkit', use: { ...devices['Desktop Safari'] } },
+ { name: "chromium", use: { ...devices["Desktop Chrome"] } },
+ { name: "firefox", use: { ...devices["Desktop Firefox"] } },
+ { name: "webkit", use: { ...devices["Desktop Safari"] } },
],
});
-
diff --git a/prisma.config.ts b/prisma.config.ts
index a854118..a5f030c 100644
--- a/prisma.config.ts
+++ b/prisma.config.ts
@@ -1,9 +1,9 @@
-import 'dotenv/config';
-import { defineConfig, env } from '@prisma/config';
+import "dotenv/config";
+import { defineConfig, env } from "@prisma/config";
export default defineConfig({
- schema: 'prisma/schema.prisma',
+ schema: "prisma/schema.prisma",
datasource: {
- url: env('DATABASE_URL'),
+ url: env("DATABASE_URL"),
},
});
diff --git a/scripts/add-member.ts b/scripts/add-member.ts
index 183975f..290a466 100644
--- a/scripts/add-member.ts
+++ b/scripts/add-member.ts
@@ -50,11 +50,15 @@ async function main() {
process.exit(1);
}
if (type === "AGENT" && !values.system) {
- console.error("agents need --system (e.g. orangecat:cat) — which system attests this member's votes");
+ console.error(
+ "agents need --system (e.g. orangecat:cat) — which system attests this member's votes",
+ );
process.exit(1);
}
if (type === "AGENT" && values["oc-actor"]) {
- console.error("--oc-actor is for HUMAN members only — agents are recognized by their API key, not a login");
+ console.error(
+ "--oc-actor is for HUMAN members only — agents are recognized by their API key, not a login",
+ );
process.exit(1);
}
@@ -65,7 +69,9 @@ async function main() {
}
const existing = await prisma.member.findUnique({
- where: { organizationId_bitcoinAddress: { organizationId: organization.id, bitcoinAddress: address } },
+ where: {
+ organizationId_bitcoinAddress: { organizationId: organization.id, bitcoinAddress: address },
+ },
});
if (existing) {
console.log(`member already registered: ${existing.id} (${existing.displayName})`);
@@ -113,7 +119,9 @@ async function main() {
process.exit(1);
}
const plaintext = `sk_solon_${randomBytes(24).toString("hex")}`;
- await prisma.agentApiKey.create({ data: { memberId: member.id, keyHash: sha256Hex(plaintext) } });
+ await prisma.agentApiKey.create({
+ data: { memberId: member.id, keyHash: sha256Hex(plaintext) },
+ });
console.log(`API key (shown once, store it in the agent's env now):\n${plaintext}`);
}
}
diff --git a/scripts/add-treasury-source.ts b/scripts/add-treasury-source.ts
index 3b4b6d3..d9bf95c 100644
--- a/scripts/add-treasury-source.ts
+++ b/scripts/add-treasury-source.ts
@@ -57,9 +57,7 @@ async function main() {
address,
},
});
- console.log(
- `treasury source created: ${source.id} (${source.label} ${source.address})`,
- );
+ console.log(`treasury source created: ${source.id} (${source.label} ${source.address})`);
}
main()
diff --git a/scripts/agent-vote.ts b/scripts/agent-vote.ts
index 0a83635..d3b6ee6 100644
--- a/scripts/agent-vote.ts
+++ b/scripts/agent-vote.ts
@@ -37,7 +37,9 @@ async function main() {
const keyEnv = values["key-env"] ?? "SOLON_AGENT_PRIVKEY";
const privateKeyHex = process.env[keyEnv];
if (!privateKeyHex) {
- console.error(`env ${keyEnv} is not set — the agent's private key lives in its own env, nowhere else`);
+ console.error(
+ `env ${keyEnv} is not set — the agent's private key lives in its own env, nowhere else`,
+ );
process.exit(1);
}
diff --git a/scripts/design-system-check.js b/scripts/design-system-check.js
index 348c547..049aca2 100644
--- a/scripts/design-system-check.js
+++ b/scripts/design-system-check.js
@@ -5,71 +5,74 @@
* no palette utilities, no arbitrary hex, no pillowy radii, no shadows
* standing in for hierarchy.
*/
-const fs = require('fs');
-const path = require('path');
+const fs = require("fs");
+const path = require("path");
const ROOT = process.cwd();
-const TARGETS = ['src'];
-const EXTENSIONS = new Set(['.ts', '.tsx']);
-const SKIP_NAMES = new Set(['opengraph-image.tsx']);
+const TARGETS = ["src"];
+const EXTENSIONS = new Set([".ts", ".tsx"]);
+const SKIP_NAMES = new Set(["opengraph-image.tsx"]);
const FORBIDDEN = [
{
pattern: /\b(?:text|bg|border|ring|divide)-(?:slate|gray|zinc|neutral)-\d+\b/,
- message: 'Use text-fg-primary / text-fg-secondary / bg-surface-* / border-default.',
+ message: "Use text-fg-primary / text-fg-secondary / bg-surface-* / border-default.",
},
{
pattern: /(?:bg|text|border)-\[[#']/,
- message: 'No arbitrary hex. Define a CSS var in globals.css.',
+ message: "No arbitrary hex. Define a CSS var in globals.css.",
},
{
pattern: /\b(?:text|bg|border|ring)-white\/\d+\b/,
- message: 'Use bg-surface-* / border-* tokens, not white with an opacity.',
+ message: "Use bg-surface-* / border-* tokens, not white with an opacity.",
},
{
// The vocabulary is shared with OrangeCat and FleetCrown. These were Solon's
// private, blue-tinted palette — the reason the three sites looked like three
// companies. They are gone from tailwind.config.js; this keeps them gone.
- pattern: /\b(?:text|bg|border|ring|from|to|via|divide)-(?:navy|navy-light|navy-dark|solon-[a-z]+)\b/,
+ pattern:
+ /\b(?:text|bg|border|ring|from|to|via|divide)-(?:navy|navy-light|navy-dark|solon-[a-z]+)\b/,
message:
- 'Legacy Solon palette. Use the shared tokens: bg-surface-*, text-fg-*, bg-accent, text-bitcoin.',
+ "Legacy Solon palette. Use the shared tokens: bg-surface-*, text-fg-*, bg-accent, text-bitcoin.",
},
{
// Radii are named by role so all three products round corners the same way.
pattern: /\brounded-(?:sm|md|lg|xl|2xl|3xl|full)\b/,
- message: 'Use rounded-control / rounded-surface / rounded-pill.',
+ message: "Use rounded-control / rounded-surface / rounded-pill.",
},
{
pattern: /\bshadow-(?:sm|md|lg|xl|2xl|navy|card)\b/,
- message: 'Hierarchy is border + type, not drop shadow.',
+ message: "Hierarchy is border + type, not drop shadow.",
},
{
pattern: /\b(?:bg-gradient|linear-gradient)\b/,
- message: 'No component gradients. Brand surfaces live in globals.css.',
+ message: "No component gradients. Brand surfaces live in globals.css.",
},
{
// max-w-7xl was the old per-page container. Width is one decision now.
pattern: /\bmax-w-7xl\b/,
- message: 'Use the .section-shell class (or max-w-shell) so every page lines up.',
+ message: "Use the .section-shell class (or max-w-shell) so every page lines up.",
},
{
// The display face (Instrument Serif) ships ONE weight. Asking for a bolder
// one makes the browser synthesize a fake bold, which looks cheap and is
// invisible in code review. Weight belongs to the face, in the token
// package, not to the component — that is what keeps a face swap one line.
- pattern: /\bfont-display\b[^"']*\bfont-(?:medium|semibold|bold|extrabold|black)\b|\bfont-(?:medium|semibold|bold|extrabold|black)\b[^"']*\bfont-display\b/,
+ pattern:
+ /\bfont-display\b[^"']*\bfont-(?:medium|semibold|bold|extrabold|black)\b|\bfont-(?:medium|semibold|bold|extrabold|black)\b[^"']*\bfont-display\b/,
message:
- 'Do not set a weight next to font-display — the display face has one weight, owned by @fleet/design-tokens.',
+ "Do not set a weight next to font-display — the display face has one weight, owned by @fleet/design-tokens.",
},
{
// .font-display already applies the face's tracking. Re-declaring it means
// a future face swap silently keeps the OLD face's tracking.
- pattern: /\bfont-display\b[^"']*\btracking-display\b|\btracking-display\b[^"']*\bfont-display\b/,
- message: 'Redundant: .font-display already applies --tracking-display.',
+ pattern:
+ /\bfont-display\b[^"']*\btracking-display\b|\btracking-display\b[^"']*\bfont-display\b/,
+ message: "Redundant: .font-display already applies --tracking-display.",
},
{
pattern: /font-\[['"]?[A-Z]/,
- message: 'Never name a typeface in a component. Use font-display / font-sans / font-mono.',
+ message: "Never name a typeface in a component. Use font-display / font-sans / font-mono.",
},
{
// Size floor. The display face is a high-contrast serif: its thin strokes
@@ -77,9 +80,10 @@ const FORBIDDEN = [
// LIGHTER than the 16px sans paragraph under it and the hierarchy inverts.
// Display type starts at text-2xl; below that use the sans at font-semibold.
// The uppercase, open-tracked wordmark is the sanctioned exception (.wordmark).
- pattern: /\bfont-display\b[^"']*\btext-(?:xs|sm|base|lg|xl)\b|\btext-(?:xs|sm|base|lg|xl)\b[^"']*\bfont-display\b/,
+ pattern:
+ /\bfont-display\b[^"']*\btext-(?:xs|sm|base|lg|xl)\b|\btext-(?:xs|sm|base|lg|xl)\b[^"']*\bfont-display\b/,
message:
- 'Display face below its size floor — use text-2xl+ with font-display, or the sans at font-semibold.',
+ "Display face below its size floor — use text-2xl+ with font-display, or the sans at font-semibold.",
},
];
@@ -97,7 +101,7 @@ const FORBIDDEN = [
const DISPLAY_SCALE = /\btext-(?:2xl|3xl|4xl|5xl|6xl|7xl|display-[123])\b/;
function missingDisplayFace(line) {
for (const m of line.matchAll(/className=(?:"([^"]*)"|\{`([^`]*)`\})/g)) {
- const cls = m[1] ?? m[2] ?? '';
+ const cls = m[1] ?? m[2] ?? "";
if (!DISPLAY_SCALE.test(cls)) continue;
if (/\bfont-(?:display|mono)\b/.test(cls)) continue;
if (/\bwordmark\b/.test(cls)) continue;
@@ -118,7 +122,7 @@ const TOKEN_REDEFINITION =
function walk(dir, files = []) {
if (!fs.existsSync(dir)) return files;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
- if (entry.name === 'node_modules' || entry.name === '.next') continue;
+ if (entry.name === "node_modules" || entry.name === ".next") continue;
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walk(full, files);
else if (EXTENSIONS.has(path.extname(entry.name)) && !SKIP_NAMES.has(entry.name)) {
@@ -132,7 +136,7 @@ const violations = [];
for (const target of TARGETS) {
for (const file of walk(path.join(ROOT, target))) {
const rel = path.relative(ROOT, file);
- const lines = fs.readFileSync(file, 'utf8').split('\n');
+ const lines = fs.readFileSync(file, "utf8").split("\n");
lines.forEach((line, i) => {
for (const rule of FORBIDDEN) {
if (rule.pattern.test(line)) {
@@ -144,7 +148,7 @@ for (const target of TARGETS) {
file: rel,
line: i + 1,
message:
- 'Heading at display size without font-display. Above text-2xl the display face is the default — add font-display (or font-mono if this is data).',
+ "Heading at display size without font-display. Above text-2xl the display face is the default — add font-display (or font-mono if this is data).",
source: line.trim(),
});
}
@@ -153,17 +157,17 @@ for (const target of TARGETS) {
}
// globals.css must stay free of token definitions — the package owns them.
-const GLOBALS = path.join(ROOT, 'src/app/globals.css');
+const GLOBALS = path.join(ROOT, "src/app/globals.css");
if (fs.existsSync(GLOBALS)) {
- fs.readFileSync(GLOBALS, 'utf8')
- .split('\n')
+ fs.readFileSync(GLOBALS, "utf8")
+ .split("\n")
.forEach((line, i) => {
if (TOKEN_REDEFINITION.test(line)) {
violations.push({
- file: 'src/app/globals.css',
+ file: "src/app/globals.css",
line: i + 1,
message:
- 'Token redefined locally. Tokens live in @fleet/design-tokens so all three products share them.',
+ "Token redefined locally. Tokens live in @fleet/design-tokens so all three products share them.",
source: line.trim(),
});
}
@@ -177,4 +181,4 @@ if (violations.length) {
}
process.exit(1);
}
-console.log('design-system check: ok');
+console.log("design-system check: ok");
diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx
index 8db5955..dcfd4a4 100644
--- a/src/app/(dashboard)/dashboard/page.tsx
+++ b/src/app/(dashboard)/dashboard/page.tsx
@@ -72,8 +72,7 @@ export default async function DashboardOverview() {
- The governance register is currently unreachable. No live data can be
- shown.
+ The governance register is currently unreachable. No live data can be shown.
Overview
- No organization is registered yet. Once one exists, its votes, - treasury, and audit trail appear here. + No organization is registered yet. Once one exists, its votes, treasury, and audit trail + appear here.
)} {org &&- The voting register is currently unreachable. No session data can be - shown. + The voting register is currently unreachable. No session data can be shown.
- No voting session has been opened yet. When one opens, registered - members vote here by signing the canonical vote message with their own - Bitcoin wallet. + No voting session has been opened yet. When one opens, registered members vote here by + signing the canonical vote message with their own Bitcoin wallet.
- Most governance runs on trust: trust the treasurer, trust the - minutes, trust that the vote was counted. Solon replaces that - trust with verification. Votes are Bitcoin signed messages anyone - can re-check. Decisions are published as self-verifying documents. - The audit trail is append-only. The treasury is watch-only — Solon - never holds keys or funds. + Most governance runs on trust: trust the treasurer, trust the minutes, trust that the + vote was counted. Solon replaces that trust with verification. Votes are Bitcoin + signed messages anyone can re-check. Decisions are published as self-verifying + documents. The audit trail is append-only. The treasury is watch-only — Solon never + holds keys or funds.
- Solon is the governance pillar of a three-product stack, and it - practices what it ships: its first production organization governs - the stack itself, with AI agents from the sibling products - registered as voting members and humans holding the red lines. + Solon is the governance pillar of a three-product stack, and it practices what it + ships: its first production organization governs the stack itself, with AI agents from + the sibling products registered as voting members and humans holding the red lines.
- The aim is a community that can show its books, recount its - votes, and keep the door honest — one worth recognizing because - its claims survive being checked, not because it says so. + The aim is a community that can show its books, recount its votes, and keep the door + honest — one worth recognizing because its claims survive being checked, not because + it says so.
- Solon is operated as part of the OrangeCat stack and governed on its - own rails. The current voting members — including the sibling - products' agents, The Cat and Loki — and every policy they - govern are public on the ecosystem page. + Solon is operated as part of the OrangeCat stack and governed on its own rails. The + current voting members — including the sibling products' agents, The Cat and Loki — + and every policy they govern are public on the ecosystem page.
- The live governed state — members, policies, decisions — is - public. So is the code. + The live governed state — members, policies, decisions — is public. So is the code.