@passioncode-ai/passioncode 0.1.32 → 0.1.33
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/CHANGELOG.md +12 -0
- package/family.json +1 -1
- package/package.json +1 -1
- package/payload/.claude-plugin/marketplace.json +2 -2
- package/payload/manifest.json +8 -8
- package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/payload/plugins/fabric-agent-adapter/hooks/hooks.json +17 -0
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +21 -8
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/demo.html +110 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +198 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +253 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +28 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/themes.json +350 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +133 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard.md +4 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +7 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_layout.mjs +295 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +31 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +437 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +51 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +65 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +21 -3
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
- package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.33 - 2026-10-11
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Fabric Agent Adapter is pinned at `v0.9.0`** (it was `v0.8.3`). `building-fabric-services` ships the dashboard
|
|
8
|
+
kit for `fabric-dashboard/0.1` (Fabric Agent Contract DEC-0036): one stylesheet and one script that keep a
|
|
9
|
+
service's dashboard usable from 360 CSS px inside a host pane beside the agent console, seven WCAG AA themes in
|
|
10
|
+
light and dark that follow the host's `fabric_appearance` cookie, `dashboard_kit.py`, the layout probe
|
|
11
|
+
`check_dashboard_layout.mjs` and the probe rule `dashboard.declared`. The adapter plugin gains its first hook:
|
|
12
|
+
at session start it prints one line in a repository whose dashboard has no kit or an older one, and nothing
|
|
13
|
+
elsewhere. The default contract pin moves to `a83bec6`; bundles issued under the earlier revisions stay valid.
|
|
14
|
+
|
|
3
15
|
## 0.1.32 - 2026-10-10
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/family.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/passioncode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "The PassionCode.ai agent skill set \u2014 Fabric Agent Adapter, Observatory Log and the organisation's working rules \u2014 for Claude Code and every other agent on the machine, updated as one set.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"passioncode": "bin/passioncode.js"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"name": "fabric-agent-adapter",
|
|
12
12
|
"displayName": "Fabric Agent Adapter",
|
|
13
13
|
"source": "./plugins/fabric-agent-adapter",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.9.0",
|
|
15
15
|
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "PassionCode.ai",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"name": "passioncode",
|
|
36
36
|
"displayName": "PassionCode.ai",
|
|
37
37
|
"source": "./plugins/passioncode",
|
|
38
|
-
"version": "0.1.
|
|
38
|
+
"version": "0.1.33",
|
|
39
39
|
"description": "The organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
|
|
40
40
|
"author": {
|
|
41
41
|
"name": "PassionCode.ai",
|
package/payload/manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"family": "passioncode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"release": true,
|
|
5
5
|
"members": [
|
|
6
6
|
{
|
|
7
7
|
"name": "fabric-agent-adapter",
|
|
8
8
|
"displayName": "Fabric Agent Adapter",
|
|
9
9
|
"repo": "passioncode-ai/fabric-agent-adapter",
|
|
10
|
-
"ref": "v0.
|
|
11
|
-
"commit": "
|
|
12
|
-
"version": "0.
|
|
10
|
+
"ref": "v0.9.0",
|
|
11
|
+
"commit": "7e45e6be5f17e0a638acf41e3a6b499aa410127a",
|
|
12
|
+
"version": "0.9.0",
|
|
13
13
|
"via": "clone of https://***@github.com/passioncode-ai/fabric-agent-adapter.git",
|
|
14
14
|
"skills": [
|
|
15
15
|
"adapting-projects-to-fabric",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"legacyMarketplaces": [
|
|
23
23
|
"fabric-agent-adapter"
|
|
24
24
|
],
|
|
25
|
-
"contentHash": "sha256:
|
|
25
|
+
"contentHash": "sha256:991de28ab45fdcf9ee364ccbaefa9e7b147608b5928aae5d239e478244e078f4",
|
|
26
26
|
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
|
|
27
27
|
"author": {
|
|
28
28
|
"name": "PassionCode.ai",
|
|
@@ -61,16 +61,16 @@
|
|
|
61
61
|
"name": "passioncode",
|
|
62
62
|
"displayName": "PassionCode.ai",
|
|
63
63
|
"repo": "passioncode-ai/passioncode",
|
|
64
|
-
"ref": "v0.1.
|
|
64
|
+
"ref": "v0.1.33",
|
|
65
65
|
"commit": null,
|
|
66
|
-
"version": "0.1.
|
|
66
|
+
"version": "0.1.33",
|
|
67
67
|
"via": "this repository",
|
|
68
68
|
"skills": [
|
|
69
69
|
"working-in-passioncode"
|
|
70
70
|
],
|
|
71
71
|
"legacyPluginIds": [],
|
|
72
72
|
"legacyMarketplaces": [],
|
|
73
|
-
"contentHash": "sha256:
|
|
73
|
+
"contentHash": "sha256:3fed1a8d8775dc09826ff7e4edefb10a99827b97dd6cd0fe8345388b7e501a0d",
|
|
74
74
|
"description": "The organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
|
|
75
75
|
"author": {
|
|
76
76
|
"name": "PassionCode.ai",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "fabric-agent-adapter",
|
|
4
4
|
"displayName": "Fabric Agent Adapter",
|
|
5
5
|
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.9.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|
|
9
9
|
"url": "https://passioncode.ai/"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "One line at session start in a repository whose Fabric dashboard has no dashboard kit or an older one (fabric-dashboard/0.1, DEC-0036). Silent everywhere else; reads only, never writes, always exits 0.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "startup|resume",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/building-fabric-services/scripts/dashboard_kit.py\" notice",
|
|
11
|
+
"timeout": 5
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
5
5
|
compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.9.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
|
-
contract-commit: "
|
|
10
|
+
contract-commit: "a83bec6b47776af688402c18efec507143061f68"
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Adapting projects to Fabric
|
|
@@ -70,7 +70,7 @@ Use exactly:
|
|
|
70
70
|
|
|
71
71
|
- contract version `0.1.0`;
|
|
72
72
|
- repository `https://github.com/passioncode-ai/fabric-agent-contract`;
|
|
73
|
-
- commit `
|
|
73
|
+
- commit `a83bec6b47776af688402c18efec507143061f68`.
|
|
74
74
|
|
|
75
75
|
This is the default for new bundles. When checking an issued legacy bundle, preserve its
|
|
76
76
|
explicit supported lock and use that exact clean checkout. Never silently upgrade or
|
|
@@ -16,10 +16,10 @@ from urllib.parse import urlparse
|
|
|
16
16
|
|
|
17
17
|
CONTRACT_VERSION = "0.1.0"
|
|
18
18
|
CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
|
|
19
|
-
CONTRACT_COMMIT = "
|
|
19
|
+
CONTRACT_COMMIT = "a83bec6b47776af688402c18efec507143061f68"
|
|
20
20
|
# #region supported-contract-revisions — docs: README.md#contract-pin
|
|
21
21
|
# Only this declaration may name a legacy revision in live files.
|
|
22
|
-
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
22
|
+
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
23
23
|
# #endregion supported-contract-revisions
|
|
24
24
|
INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
|
|
25
25
|
MCP_REVISION = "2026-07-28"
|
|
@@ -9,17 +9,17 @@ description: >-
|
|
|
9
9
|
на модели». Covers
|
|
10
10
|
fabric-service/0.1: surfaces, token and one-time login, state, one copy, launchd, descriptor,
|
|
11
11
|
well-known document, events, the usage report (spend per model); online: the https guard, the token-gated well-known document, the
|
|
12
|
-
__Host- cookie, registering it on the operator's computer;
|
|
13
|
-
sample and
|
|
12
|
+
__Host- cookie, registering it on the operator's computer; Python, Node and dashboard
|
|
13
|
+
kits, a TLS sample, live and layout probes. NOT for a one-off script or cron job, a hosted product with no agent
|
|
14
14
|
behind it, the provider manifest (adapting-projects-to-fabric), or building Fabric Dashboards.
|
|
15
15
|
license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
16
16
|
compatibility: Python 3.9+ or Node.js 20+ for the kits; the probe needs Python 3.9+. launchd steps are macOS-only (Linux services use lifecycle manager none until a systemd adapter exists). Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
|
|
17
17
|
metadata:
|
|
18
18
|
author: PassionCode.ai
|
|
19
|
-
version: "0.
|
|
19
|
+
version: "0.9.0"
|
|
20
20
|
contract-version: "0.1.0"
|
|
21
21
|
extension: "fabric-service/0.1"
|
|
22
|
-
extension-commit: "
|
|
22
|
+
extension-commit: "a83bec6b47776af688402c18efec507143061f68"
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
# Building Fabric services
|
|
@@ -198,6 +198,17 @@ while the operator types or a dialog is open, one panel's failure never blanks t
|
|
|
198
198
|
others, and every error is a sentence with the next action. Read
|
|
199
199
|
[the dashboard reference](references/dashboard.md) before building the page.
|
|
200
200
|
|
|
201
|
+
The host shows the page in a pane beside the agent console that the operator can drag wide, so
|
|
202
|
+
the page must hold from **360 CSS px** up: no sideways document scroll, navigation that wraps or
|
|
203
|
+
folds into a More menu (never a tab strip that only scrolls sideways), wide tables and logs in
|
|
204
|
+
their own focusable scroll box, sticky chrome at most 120 px, and the host's colour scheme and
|
|
205
|
+
theme followed (`fabric-dashboard/0.1`, contract DEC-0036). **Build it from the dashboard kit**
|
|
206
|
+
(`assets/dashboard-kit/`: `dashboard_kit.py install <static dir>`, seven AA themes, light and dark)
|
|
207
|
+
and declare it with `extensions=fs.dashboard_extension()`. Read
|
|
208
|
+
[the dashboard kit reference](references/dashboard-kit.md) for the markup, the themes, the
|
|
209
|
+
declaration and how to upgrade a dashboard built before the kit — only with the operator's
|
|
210
|
+
go-ahead, which the plugin's session-start notice reminds you of.
|
|
211
|
+
|
|
201
212
|
The events feed is a **view over the log you already keep** — a jobs table, a JSONL
|
|
202
213
|
journal — not a second store. Each event is one sentence a person reads; set
|
|
203
214
|
`notify: true` only when the operator must decide, or something failed or degrades their
|
|
@@ -224,10 +235,12 @@ python3 <skill-dir>/scripts/check_service.py <id>[.<instance>]
|
|
|
224
235
|
It reports `PASS`, `FAIL` or `NOT_RUN` per rule (descriptor, port claim, well-known
|
|
225
236
|
shape, identity, latency, Host/Origin/cross-site guards, loopback bind, token file,
|
|
226
237
|
events auth and shape, single-use login, state outside code, instance lock, plist,
|
|
227
|
-
launchd pid equals answering pid, and the `interop.*` rules:
|
|
228
|
-
list, tools equal to the manifest, job tools, unknown job, trace
|
|
229
|
-
trace pairs) and exits 1 on any `FAIL`. A `NOT_RUN` is not a pass —
|
|
230
|
-
name it in the report.
|
|
238
|
+
launchd pid equals answering pid, `dashboard.declared` (ED-9), and the `interop.*` rules:
|
|
239
|
+
manifest link, capability list, tools equal to the manifest, job tools, unknown job, trace
|
|
240
|
+
propagation, event trace pairs) and exits 1 on any `FAIL`. A `NOT_RUN` is not a pass —
|
|
241
|
+
name it in the report. A service with a dashboard also runs the layout probe,
|
|
242
|
+
`node <skill-dir>/scripts/check_dashboard_layout.mjs --service <id>` (WebKit at 360, 480, 768
|
|
243
|
+
and 1280 px; needs Playwright, else `NOT_RUN`).
|
|
231
244
|
|
|
232
245
|
Then prove the lock: start a second copy by hand against the same data directory; it
|
|
233
246
|
must exit 75 while the first keeps serving. Prove recovery: `kill -9` the launchd pid;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-fd-appearance="scheme theme">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:">
|
|
7
|
+
<title>Example agent · dashboard kit demo</title>
|
|
8
|
+
<link rel="stylesheet" href="fabric-dashboard.css">
|
|
9
|
+
<script src="fabric-dashboard.js" defer></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<a class="fd-skip" href="#overview">Skip to content</a>
|
|
13
|
+
<div class="fd-chrome">
|
|
14
|
+
<header class="fd-header">
|
|
15
|
+
<div>
|
|
16
|
+
<p class="fd-title">Example agent</p>
|
|
17
|
+
<span class="fd-chip fd-s-ok"><span class="fd-dot"></span>Ready</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="fd-actions">
|
|
20
|
+
<label class="fd-sr-only" for="theme">Theme</label>
|
|
21
|
+
<select id="theme" class="fd-select" data-fd-theme-picker></select>
|
|
22
|
+
<label class="fd-sr-only" for="scheme">Colour scheme</label>
|
|
23
|
+
<select id="scheme" class="fd-select" data-fd-scheme-picker></select>
|
|
24
|
+
<button class="fd-btn fd-btn--primary" type="button">Run now</button>
|
|
25
|
+
</div>
|
|
26
|
+
</header>
|
|
27
|
+
<nav class="fd-nav" aria-label="Sections">
|
|
28
|
+
<ul class="fd-nav-list">
|
|
29
|
+
<li><a href="#overview" aria-current="page">Overview</a></li>
|
|
30
|
+
<li><a href="#jobs">Jobs</a></li>
|
|
31
|
+
<li><a href="#approvals">Approvals</a></li>
|
|
32
|
+
<li><a href="#calendar">Calendar</a></li>
|
|
33
|
+
<li><a href="#channels">Channels</a></li>
|
|
34
|
+
<li><a href="#budget">Budget</a></li>
|
|
35
|
+
<li><a href="#journal">Journal</a></li>
|
|
36
|
+
<li><a href="#inbox">Inbox</a></li>
|
|
37
|
+
<li><a href="#cases">Cases</a></li>
|
|
38
|
+
<li><a href="#playbooks">Playbooks</a></li>
|
|
39
|
+
<li><a href="#reports">Reports</a></li>
|
|
40
|
+
<li><a href="#log">Log</a></li>
|
|
41
|
+
<li><a href="#settings">Settings</a></li>
|
|
42
|
+
<li><a href="#help">Help</a></li>
|
|
43
|
+
</ul>
|
|
44
|
+
</nav>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<main class="fd-page">
|
|
48
|
+
<section id="overview">
|
|
49
|
+
<h2>Overview</h2>
|
|
50
|
+
<div class="fd-grid fd-grid--tiles">
|
|
51
|
+
<div class="fd-tile"><div class="fd-label">Jobs today</div><div class="fd-value">128</div></div>
|
|
52
|
+
<div class="fd-tile"><div class="fd-label">Waiting for you</div><div class="fd-value">3</div></div>
|
|
53
|
+
<div class="fd-tile"><div class="fd-label">Spend, 7 days</div><div class="fd-value">$4.21</div></div>
|
|
54
|
+
<div class="fd-tile"><div class="fd-label">Last run</div><div class="fd-value"><time>17:02</time></div></div>
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
57
|
+
|
|
58
|
+
<section id="jobs">
|
|
59
|
+
<h2>Jobs</h2>
|
|
60
|
+
<div class="fd-scroll" data-fd-label="Jobs table">
|
|
61
|
+
<table class="fd-table">
|
|
62
|
+
<caption class="fd-sr-only">Recent jobs</caption>
|
|
63
|
+
<thead><tr><th>Job</th><th>Capability</th><th>State</th><th>Started</th><th class="fd-num">Tokens</th><th class="fd-num">Cost</th><th>Trace</th></tr></thead>
|
|
64
|
+
<tbody>
|
|
65
|
+
<tr><td class="fd-mono">job-7f3a91</td><td>example.summarize</td><td><span class="fd-chip fd-s-info"><span class="fd-dot"></span>Running</span></td><td><time>2026-10-11 17:02</time></td><td class="fd-num">12 480</td><td class="fd-num">$0.031</td><td class="fd-mono">4bf92f3577b34da6a3ce929d0e0e4736</td></tr>
|
|
66
|
+
<tr><td class="fd-mono">job-7f3a90</td><td>example.translate</td><td><span class="fd-chip fd-s-ok"><span class="fd-dot"></span>Done</span></td><td><time>2026-10-11 16:40</time></td><td class="fd-num">8 002</td><td class="fd-num">$0.019</td><td class="fd-mono">00f067aa0ba902b74bf92f3577b34da6</td></tr>
|
|
67
|
+
<tr><td class="fd-mono">job-7f3a8f</td><td>example.review</td><td><span class="fd-chip fd-s-warn"><span class="fd-dot"></span>Needs you</span></td><td><time>2026-10-11 15:12</time></td><td class="fd-num">2 210</td><td class="fd-num">—</td><td class="fd-mono">a3ce929d0e0e47364bf92f3577b34da6</td></tr>
|
|
68
|
+
</tbody>
|
|
69
|
+
</table>
|
|
70
|
+
</div>
|
|
71
|
+
</section>
|
|
72
|
+
|
|
73
|
+
<section id="approvals">
|
|
74
|
+
<h2>Approvals</h2>
|
|
75
|
+
<div class="fd-grid">
|
|
76
|
+
<article class="fd-card fd-stack">
|
|
77
|
+
<h3>Publish the weekly report <span class="fd-chip fd-s-warn"><span class="fd-dot"></span>Waiting</span></h3>
|
|
78
|
+
<p class="fd-hint">Before → after and the hash the service checks when it applies the change.</p>
|
|
79
|
+
<pre class="fd-scroll" data-fd-label="Change preview">- status: draft
|
|
80
|
+
+ status: published sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</pre>
|
|
81
|
+
<div class="fd-row"><button class="fd-btn fd-btn--primary" type="button">Approve</button><button class="fd-btn" type="button">Reject</button></div>
|
|
82
|
+
</article>
|
|
83
|
+
<article class="fd-card">
|
|
84
|
+
<h3>Service</h3>
|
|
85
|
+
<dl class="fd-pairs">
|
|
86
|
+
<div><dt>Build</dt><dd class="fd-mono">3537587</dd></div>
|
|
87
|
+
<div><dt>Started</dt><dd><time>2026-10-11 09:00</time></dd></div>
|
|
88
|
+
<div><dt>Port</dt><dd class="fd-mono">8710</dd></div>
|
|
89
|
+
<div><dt>Instance</dt><dd class="fd-mono">default</dd></div>
|
|
90
|
+
</dl>
|
|
91
|
+
</article>
|
|
92
|
+
</div>
|
|
93
|
+
</section>
|
|
94
|
+
|
|
95
|
+
<section id="calendar"><h2>Calendar</h2><p class="fd-empty">Nothing is scheduled this week. <a href="#jobs">See the jobs</a></p></section>
|
|
96
|
+
<section id="channels"><h2>Channels</h2><div class="fd-banner fd-s-danger"><span class="fd-dot fd-s-danger"></span><span>Channel push failed: the provider rejected the key. Settings → Keys</span></div></section>
|
|
97
|
+
<section id="budget"><h2>Budget</h2><p>$4.21 of $100 this month.</p></section>
|
|
98
|
+
<section id="journal"><h2>Journal</h2><p>No decisions recorded today.</p></section>
|
|
99
|
+
<section id="inbox"><h2>Inbox</h2><p class="fd-empty">No comments waiting.</p></section>
|
|
100
|
+
<section id="cases"><h2>Cases</h2><p>Two candidates from yesterday's work log.</p></section>
|
|
101
|
+
<section id="playbooks"><h2>Playbooks</h2><p>All playbooks reviewed this month.</p></section>
|
|
102
|
+
<section id="reports"><h2>Reports</h2><p>The weekly report is due Monday.</p></section>
|
|
103
|
+
<section id="log"><h2>Log</h2><pre class="fd-scroll" data-fd-label="Service log">2026-10-11T17:02:11Z info job.started job-7f3a91 example.summarize trace=4bf92f3577b34da6a3ce929d0e0e4736 span=00f067aa0ba902b7
|
|
104
|
+
2026-10-11T16:40:57Z info job.finished job-7f3a90 example.translate tokens=8002 cost=0.019</pre></section>
|
|
105
|
+
<section id="settings"><h2>Settings</h2><label for="name">Display name</label> <input id="name" class="fd-input" value="Example agent"></section>
|
|
106
|
+
<section id="help"><h2>Help</h2><p>Every error is a sentence and the next action.</p></section>
|
|
107
|
+
</main>
|
|
108
|
+
<footer class="fd-foot">fabric-dashboard-kit demo · fabric-dashboard/0.1</footer>
|
|
109
|
+
</body>
|
|
110
|
+
</html>
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/* fabric-dashboard-kit@1.0.0 · fabric-dashboard/0.1 (Fabric Agent Contract DEC-0036)
|
|
2
|
+
A Fabric service dashboard shown inside a host pane: usable from 360 CSS px up, navigation always
|
|
3
|
+
reachable, wide content in its own scroll box, one vertical scroller, appearance from the host.
|
|
4
|
+
Copy this file next to fabric-dashboard.js; do not edit it in place. Put the page's own rules in
|
|
5
|
+
its own stylesheet, consuming var(--…) only. Themes are generated from themes.json by
|
|
6
|
+
`dashboard_kit.py build`; the block between the markers is overwritten. */
|
|
7
|
+
|
|
8
|
+
:root {
|
|
9
|
+
/* The floor (ED-1, ED-2) and the sticky chrome budget (ED-5). */
|
|
10
|
+
--fd-floor: 360px;
|
|
11
|
+
--fd-chrome-max: 120px;
|
|
12
|
+
--fd-chrome-h: 0px;
|
|
13
|
+
--fd-col: 320px;
|
|
14
|
+
--fd-page-max: 1440px;
|
|
15
|
+
--fd-gutter: clamp(12px, 3vw, 24px);
|
|
16
|
+
|
|
17
|
+
--r-control: 6px;
|
|
18
|
+
--r-card: 10px;
|
|
19
|
+
--r-pill: 999px;
|
|
20
|
+
--shadow-1: 0 4px 16px -8px rgba(26, 31, 43, 0.18);
|
|
21
|
+
--motion-ease: cubic-bezier(0.2, 0, 0, 1);
|
|
22
|
+
--dur-state: 0.18s;
|
|
23
|
+
--dur-hover: 0.12s;
|
|
24
|
+
--font-ui: -apple-system, "SF Pro", "Segoe UI", system-ui, sans-serif;
|
|
25
|
+
--font-data: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
26
|
+
--t-chip: 11px;
|
|
27
|
+
--t-label: 12px;
|
|
28
|
+
--t-body: 13px;
|
|
29
|
+
--t-card: 15px;
|
|
30
|
+
--t-section: 20px;
|
|
31
|
+
--t-page: 28px;
|
|
32
|
+
--space-1: 4px;
|
|
33
|
+
--space-2: 8px;
|
|
34
|
+
--space-3: 12px;
|
|
35
|
+
--space-4: 16px;
|
|
36
|
+
--space-5: 24px;
|
|
37
|
+
--space-6: 32px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* fabric-dashboard-kit:themes:begin */
|
|
41
|
+
:root, :root[data-theme="graphite"] { color-scheme: light; --bg: #f7f8fa; --panel: #ffffff; --panel-2: #f7f8fa; --ink: #1a1f2b; --muted: #5b6472; --border: #e6e9ef; --border-strong: #d7dce4; --edge: #828b98; --accent: #2f6feb; --accent-weak: #eaf0fe; --accent-ink: #ffffff; --ok: #1a7f37; --ok-weak: #e6f4ea; --warn: #9a6700; --warn-weak: #fff3d6; --danger: #d1242f; --danger-weak: #fde8e9; --info: #2f6feb; --info-weak: #eaf0fe; }
|
|
42
|
+
:root[data-scheme="dark"], :root[data-theme="graphite"][data-scheme="dark"] { color-scheme: dark; --bg: #0f1218; --panel: #161b24; --panel-2: #1b212c; --ink: #e8ecf3; --muted: #8a93a6; --border: #232a36; --border-strong: #2c3441; --edge: #697383; --accent: #4b8bff; --accent-weak: #1b2740; --accent-ink: #0f1218; --ok: #3fb960; --ok-weak: #12281a; --warn: #d9a93f; --warn-weak: #2b2210; --danger: #e5534b; --danger-weak: #2d1517; --info: #4b8bff; --info-weak: #1b2740; }
|
|
43
|
+
:root[data-theme="fabric"] { color-scheme: light; --bg: #f7f5f8; --panel: #ffffff; --panel-2: #f2eef4; --ink: #211725; --muted: #655769; --border: #e9e3ec; --border-strong: #d9d0de; --edge: #8f8296; --accent: #6b2185; --accent-weak: #f4e9f7; --accent-ink: #ffffff; --ok: #246b3b; --ok-weak: #e4f3e8; --warn: #8a4b0b; --warn-weak: #fff0dc; --danger: #a82f44; --danger-weak: #fde8ec; --info: #345da7; --info-weak: #e8efff; }
|
|
44
|
+
:root[data-theme="fabric"][data-scheme="dark"] { color-scheme: dark; --bg: #110c15; --panel: #1b1420; --panel-2: #231a29; --ink: #fff9f0; --muted: #b6aabc; --border: #2e2334; --border-strong: #3a2e41; --edge: #7a6a80; --accent: #d4a3ea; --accent-weak: #2f1d38; --accent-ink: #110c15; --ok: #79d49a; --ok-weak: #12291c; --warn: #f6ba75; --warn-weak: #332314; --danger: #ff969f; --danger-weak: #35191f; --info: #a6bfff; --info-weak: #18243d; }
|
|
45
|
+
:root[data-theme="paper"] { color-scheme: light; --bg: #faf6ee; --panel: #fffdf8; --panel-2: #f3ede1; --ink: #2b241a; --muted: #6b5f4d; --border: #ebe3d4; --border-strong: #ddd2bf; --edge: #8d806d; --accent: #8a4a12; --accent-weak: #f6eadb; --accent-ink: #ffffff; --ok: #2f6b2f; --ok-weak: #e7f1e2; --warn: #8a5a00; --warn-weak: #fbefd2; --danger: #b02a20; --danger-weak: #fbe5e1; --info: #2c5d8a; --info-weak: #e5eef6; }
|
|
46
|
+
:root[data-theme="paper"][data-scheme="dark"] { color-scheme: dark; --bg: #17140f; --panel: #1f1b15; --panel-2: #27221a; --ink: #f3ece0; --muted: #b3a690; --border: #302a21; --border-strong: #3d362b; --edge: #7b7161; --accent: #e3a565; --accent-weak: #33261a; --accent-ink: #17140f; --ok: #86c48a; --ok-weak: #15261a; --warn: #e0b25a; --warn-weak: #2e2412; --danger: #f0877c; --danger-weak: #331a17; --info: #8fb8e0; --info-weak: #172433; }
|
|
47
|
+
:root[data-theme="forest"] { color-scheme: light; --bg: #f4f8f4; --panel: #ffffff; --panel-2: #edf3ee; --ink: #16231a; --muted: #526356; --border: #dfe8e1; --border-strong: #cddbd0; --edge: #77897c; --accent: #1f7a45; --accent-weak: #e3f2e8; --accent-ink: #ffffff; --ok: #1f7a45; --ok-weak: #e3f2e8; --warn: #8a5d00; --warn-weak: #fbf0d4; --danger: #b8312a; --danger-weak: #fbe6e4; --info: #2a6496; --info-weak: #e4eef7; }
|
|
48
|
+
:root[data-theme="forest"][data-scheme="dark"] { color-scheme: dark; --bg: #0d1410; --panel: #131c16; --panel-2: #18231c; --ink: #e4efe7; --muted: #8fa595; --border: #1f2c23; --border-strong: #29382e; --edge: #637568; --accent: #5cc98a; --accent-weak: #173322; --accent-ink: #0d1410; --ok: #5cc98a; --ok-weak: #173322; --warn: #d8ad4a; --warn-weak: #2b2311; --danger: #ec6a5e; --danger-weak: #2f1714; --info: #7fb2e5; --info-weak: #152435; }
|
|
49
|
+
:root[data-theme="ocean"] { color-scheme: light; --bg: #f3f7f9; --panel: #ffffff; --panel-2: #ebf2f5; --ink: #132029; --muted: #4f6370; --border: #dde7ec; --border-strong: #ccdae1; --edge: #738892; --accent: #0b6e8a; --accent-weak: #e0f1f6; --accent-ink: #ffffff; --ok: #1b7a4a; --ok-weak: #e2f3ea; --warn: #8f5f00; --warn-weak: #fcf0d3; --danger: #c0352b; --danger-weak: #fce6e3; --info: #1f5fae; --info-weak: #e3edfa; }
|
|
50
|
+
:root[data-theme="ocean"][data-scheme="dark"] { color-scheme: dark; --bg: #0b1217; --panel: #111a21; --panel-2: #16212a; --ink: #e2edf3; --muted: #8aa2b1; --border: #1c2a34; --border-strong: #263741; --edge: #607480; --accent: #3fc0d8; --accent-weak: #123039; --accent-ink: #0b1217; --ok: #4fc58a; --ok-weak: #11291d; --warn: #dcac45; --warn-weak: #2c2310; --danger: #ef6b60; --danger-weak: #311714; --info: #6fa8f0; --info-weak: #13233a; }
|
|
51
|
+
:root[data-theme="ember"] { color-scheme: light; --bg: #fbf6f2; --panel: #ffffff; --panel-2: #f6ede6; --ink: #2a1b14; --muted: #6e5649; --border: #efe2d8; --border-strong: #e2d1c4; --edge: #958072; --accent: #b04a0c; --accent-weak: #fbebe0; --accent-ink: #ffffff; --ok: #2a7340; --ok-weak: #e4f2e7; --warn: #8a5800; --warn-weak: #fbefd3; --danger: #b42324; --danger-weak: #fbe4e3; --info: #2b5f9e; --info-weak: #e4edf8; }
|
|
52
|
+
:root[data-theme="ember"][data-scheme="dark"] { color-scheme: dark; --bg: #16100c; --panel: #1e1611; --panel-2: #271d16; --ink: #f6ebe3; --muted: #b8a194; --border: #30241c; --border-strong: #3e2f25; --edge: #7e6a5e; --accent: #ff8f4d; --accent-weak: #3a2112; --accent-ink: #16100c; --ok: #78c88e; --ok-weak: #132719; --warn: #e3b04f; --warn-weak: #2e2410; --danger: #f4776f; --danger-weak: #351816; --info: #86b2ef; --info-weak: #162338; }
|
|
53
|
+
:root[data-theme="contrast"] { color-scheme: light; --bg: #ffffff; --panel: #ffffff; --panel-2: #f2f2f2; --ink: #000000; --muted: #2b2b2b; --border: #000000; --border-strong: #000000; --edge: #000000; --accent: #0033cc; --accent-weak: #e6ecff; --accent-ink: #ffffff; --ok: #005c1f; --ok-weak: #e3f5e8; --warn: #6b4200; --warn-weak: #fff1d1; --danger: #b00010; --danger-weak: #ffe6e8; --info: #0033cc; --info-weak: #e6ecff; }
|
|
54
|
+
:root[data-theme="contrast"][data-scheme="dark"] { color-scheme: dark; --bg: #000000; --panel: #000000; --panel-2: #141414; --ink: #ffffff; --muted: #e0e0e0; --border: #ffffff; --border-strong: #ffffff; --edge: #ffffff; --accent: #7fb2ff; --accent-weak: #0d1f3d; --accent-ink: #000000; --ok: #5ee08a; --ok-weak: #0a2614; --warn: #ffd166; --warn-weak: #2e2306; --danger: #ff8a8a; --danger-weak: #3a0d0d; --info: #7fb2ff; --info-weak: #0d1f3d; }
|
|
55
|
+
@media (prefers-color-scheme: dark) {
|
|
56
|
+
:root:not([data-scheme="light"]) { color-scheme: dark; --bg: #0f1218; --panel: #161b24; --panel-2: #1b212c; --ink: #e8ecf3; --muted: #8a93a6; --border: #232a36; --border-strong: #2c3441; --edge: #697383; --accent: #4b8bff; --accent-weak: #1b2740; --accent-ink: #0f1218; --ok: #3fb960; --ok-weak: #12281a; --warn: #d9a93f; --warn-weak: #2b2210; --danger: #e5534b; --danger-weak: #2d1517; --info: #4b8bff; --info-weak: #1b2740; }
|
|
57
|
+
:root[data-theme="fabric"]:not([data-scheme="light"]) { color-scheme: dark; --bg: #110c15; --panel: #1b1420; --panel-2: #231a29; --ink: #fff9f0; --muted: #b6aabc; --border: #2e2334; --border-strong: #3a2e41; --edge: #7a6a80; --accent: #d4a3ea; --accent-weak: #2f1d38; --accent-ink: #110c15; --ok: #79d49a; --ok-weak: #12291c; --warn: #f6ba75; --warn-weak: #332314; --danger: #ff969f; --danger-weak: #35191f; --info: #a6bfff; --info-weak: #18243d; }
|
|
58
|
+
:root[data-theme="paper"]:not([data-scheme="light"]) { color-scheme: dark; --bg: #17140f; --panel: #1f1b15; --panel-2: #27221a; --ink: #f3ece0; --muted: #b3a690; --border: #302a21; --border-strong: #3d362b; --edge: #7b7161; --accent: #e3a565; --accent-weak: #33261a; --accent-ink: #17140f; --ok: #86c48a; --ok-weak: #15261a; --warn: #e0b25a; --warn-weak: #2e2412; --danger: #f0877c; --danger-weak: #331a17; --info: #8fb8e0; --info-weak: #172433; }
|
|
59
|
+
:root[data-theme="forest"]:not([data-scheme="light"]) { color-scheme: dark; --bg: #0d1410; --panel: #131c16; --panel-2: #18231c; --ink: #e4efe7; --muted: #8fa595; --border: #1f2c23; --border-strong: #29382e; --edge: #637568; --accent: #5cc98a; --accent-weak: #173322; --accent-ink: #0d1410; --ok: #5cc98a; --ok-weak: #173322; --warn: #d8ad4a; --warn-weak: #2b2311; --danger: #ec6a5e; --danger-weak: #2f1714; --info: #7fb2e5; --info-weak: #152435; }
|
|
60
|
+
:root[data-theme="ocean"]:not([data-scheme="light"]) { color-scheme: dark; --bg: #0b1217; --panel: #111a21; --panel-2: #16212a; --ink: #e2edf3; --muted: #8aa2b1; --border: #1c2a34; --border-strong: #263741; --edge: #607480; --accent: #3fc0d8; --accent-weak: #123039; --accent-ink: #0b1217; --ok: #4fc58a; --ok-weak: #11291d; --warn: #dcac45; --warn-weak: #2c2310; --danger: #ef6b60; --danger-weak: #311714; --info: #6fa8f0; --info-weak: #13233a; }
|
|
61
|
+
:root[data-theme="ember"]:not([data-scheme="light"]) { color-scheme: dark; --bg: #16100c; --panel: #1e1611; --panel-2: #271d16; --ink: #f6ebe3; --muted: #b8a194; --border: #30241c; --border-strong: #3e2f25; --edge: #7e6a5e; --accent: #ff8f4d; --accent-weak: #3a2112; --accent-ink: #16100c; --ok: #78c88e; --ok-weak: #132719; --warn: #e3b04f; --warn-weak: #2e2410; --danger: #f4776f; --danger-weak: #351816; --info: #86b2ef; --info-weak: #162338; }
|
|
62
|
+
:root[data-theme="contrast"]:not([data-scheme="light"]) { color-scheme: dark; --bg: #000000; --panel: #000000; --panel-2: #141414; --ink: #ffffff; --muted: #e0e0e0; --border: #ffffff; --border-strong: #ffffff; --edge: #ffffff; --accent: #7fb2ff; --accent-weak: #0d1f3d; --accent-ink: #000000; --ok: #5ee08a; --ok-weak: #0a2614; --warn: #ffd166; --warn-weak: #2e2306; --danger: #ff8a8a; --danger-weak: #3a0d0d; --info: #7fb2ff; --info-weak: #0d1f3d; }
|
|
63
|
+
}
|
|
64
|
+
/* fabric-dashboard-kit:themes:end */
|
|
65
|
+
|
|
66
|
+
@media (prefers-reduced-motion: reduce) {
|
|
67
|
+
:root { --dur-state: 0s; --dur-hover: 0s; }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* ---- base: the floor holds (ED-1, ED-2) ---- */
|
|
71
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
72
|
+
html { min-width: var(--fd-floor); background: var(--bg); color: var(--ink); -webkit-text-size-adjust: 100%; }
|
|
73
|
+
body { margin: 0; background: var(--bg); color: var(--ink); font: 400 var(--t-body)/1.5 var(--font-ui); overflow-wrap: break-word; }
|
|
74
|
+
img, svg, video, canvas { max-width: 100%; height: auto; }
|
|
75
|
+
h1, h2, h3 { margin: 0; text-wrap: balance; }
|
|
76
|
+
h1 { font-size: var(--t-section); font-weight: 700; }
|
|
77
|
+
h2 { font-size: var(--t-section); font-weight: 700; margin: var(--space-6) 0 var(--space-3); }
|
|
78
|
+
h3 { font-size: var(--t-card); font-weight: 600; margin: 0 0 var(--space-3); }
|
|
79
|
+
p { max-width: 65ch; }
|
|
80
|
+
a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--edge); text-underline-offset: 2px; }
|
|
81
|
+
a:hover { text-decoration-color: var(--ink); }
|
|
82
|
+
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
83
|
+
/* A jump to an anchor lands below the sticky chrome (ED-5); the script measures --fd-chrome-h. */
|
|
84
|
+
[id] { scroll-margin-top: calc(var(--fd-chrome-h) + var(--space-2)); }
|
|
85
|
+
.fd-mono, .fd-num, time { font-family: var(--font-data); font-variant-numeric: tabular-nums; }
|
|
86
|
+
.fd-muted { color: var(--muted); }
|
|
87
|
+
.fd-hint { color: var(--muted); font-size: var(--t-label); max-width: 65ch; margin: 0 0 var(--space-3); }
|
|
88
|
+
.fd-break { overflow-wrap: anywhere; }
|
|
89
|
+
.fd-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
90
|
+
.fd-skip { position: absolute; top: var(--space-2); left: var(--space-4); z-index: 20; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
91
|
+
.fd-skip:focus { width: auto; height: auto; clip: auto; overflow: visible; background: var(--panel); padding: var(--space-2); }
|
|
92
|
+
|
|
93
|
+
/* ---- page frame ---- */
|
|
94
|
+
.fd-page { max-width: var(--fd-page-max); margin: 0 auto; padding: var(--space-4) var(--fd-gutter) var(--space-6); }
|
|
95
|
+
/* Sticky only once the script runs: without it the navigation wraps and a wrapped bar must not pin
|
|
96
|
+
half the screen. The script keeps the pinned part at or under min(--fd-chrome-max, 25vh) (ED-5):
|
|
97
|
+
when header and navigation together are taller, the header scrolls away (a negative top) and the
|
|
98
|
+
navigation stays pinned. */
|
|
99
|
+
.fd-chrome { background: var(--panel); border-bottom: 1px solid var(--border); }
|
|
100
|
+
html.fd-js .fd-chrome { position: sticky; top: var(--fd-chrome-top, 0px); z-index: 10; }
|
|
101
|
+
.fd-header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-4);
|
|
102
|
+
padding: var(--space-3) var(--fd-gutter); min-width: 0; }
|
|
103
|
+
.fd-header > * { min-width: 0; }
|
|
104
|
+
.fd-title { font-size: var(--t-card); font-weight: 700; margin: 0; }
|
|
105
|
+
.fd-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
|
|
106
|
+
.fd-foot { color: var(--muted); font-size: var(--t-label); padding: var(--space-4) var(--fd-gutter); border-top: 1px solid var(--border); }
|
|
107
|
+
|
|
108
|
+
/* ---- navigation: always reachable (ED-3) ----
|
|
109
|
+
Without the script the list wraps. With it (.fd-nav--fold) the list stays one line and the
|
|
110
|
+
links that do not fit move into the More menu; nothing is left past a hidden scrollbar. */
|
|
111
|
+
.fd-nav { display: flex; align-items: center; gap: var(--space-1); padding: var(--space-1) var(--fd-gutter); min-width: 0; }
|
|
112
|
+
.fd-nav-list { display: flex; flex-wrap: wrap; gap: var(--space-1); list-style: none; margin: 0; padding: 0; min-width: 0; }
|
|
113
|
+
.fd-nav--fold .fd-nav-list { flex: 1 1 auto; flex-wrap: nowrap; overflow: hidden; }
|
|
114
|
+
.fd-nav a, .fd-more > summary { display: block; padding: var(--space-1) var(--space-3); border-radius: var(--r-control); color: var(--muted);
|
|
115
|
+
text-decoration: none; white-space: nowrap; cursor: pointer;
|
|
116
|
+
transition: background-color var(--dur-hover) var(--motion-ease), color var(--dur-hover) var(--motion-ease); }
|
|
117
|
+
.fd-nav a:hover, .fd-more > summary:hover { background: var(--panel-2); color: var(--ink); }
|
|
118
|
+
.fd-nav a[aria-current] { background: var(--accent-weak); color: var(--ink); box-shadow: inset 0 -2px 0 var(--accent); }
|
|
119
|
+
.fd-more { position: relative; flex: none; }
|
|
120
|
+
.fd-more[hidden] { display: none; }
|
|
121
|
+
.fd-more > summary { list-style: none; border: 1px solid var(--edge); }
|
|
122
|
+
.fd-more > summary::-webkit-details-marker { display: none; }
|
|
123
|
+
.fd-more > summary::after { content: " \25BE"; }
|
|
124
|
+
.fd-more-list { position: absolute; right: 0; top: calc(100% + var(--space-1)); z-index: 30; list-style: none; margin: 0; padding: var(--space-1);
|
|
125
|
+
min-width: 12rem; max-width: min(20rem, calc(100vw - 2 * var(--fd-gutter))); max-height: 60vh; overflow-y: auto;
|
|
126
|
+
background: var(--panel); border: 1px solid var(--edge); border-radius: var(--r-card); box-shadow: var(--shadow-1); }
|
|
127
|
+
.fd-more-list a { white-space: normal; }
|
|
128
|
+
|
|
129
|
+
/* ---- layout: fluid, no fixed widths ---- */
|
|
130
|
+
.fd-grid { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--fd-col)), 1fr)); }
|
|
131
|
+
.fd-grid--tiles { --fd-col: 160px; }
|
|
132
|
+
.fd-grid--wide { --fd-col: 480px; }
|
|
133
|
+
.fd-stack > * + * { margin-top: var(--space-3); }
|
|
134
|
+
.fd-row { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); align-items: center; min-width: 0; }
|
|
135
|
+
.fd-row > * { min-width: 0; }
|
|
136
|
+
/* A label-value list: two columns while they fit, one below that. */
|
|
137
|
+
.fd-pairs { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); gap: var(--space-1) var(--space-4); margin: 0; }
|
|
138
|
+
.fd-pairs > div { display: flex; justify-content: space-between; gap: var(--space-3); border-bottom: 1px solid var(--border); padding: var(--space-1) 0; min-width: 0; }
|
|
139
|
+
.fd-pairs dt { color: var(--muted); }
|
|
140
|
+
.fd-pairs dd { margin: 0; text-align: right; overflow-wrap: anywhere; }
|
|
141
|
+
|
|
142
|
+
/* ---- wide content scrolls in its own box (ED-4) ----
|
|
143
|
+
Edge shades show which side has more (the local/scroll background pair), the WebKit scrollbar
|
|
144
|
+
stays visible instead of overlaying, and the script makes the box focusable with a name. */
|
|
145
|
+
.fd-scroll { overflow-x: auto; max-width: 100%; border: 1px solid var(--border); border-radius: var(--r-card);
|
|
146
|
+
background: var(--panel); /* a browser without color-mix() keeps a plain box */
|
|
147
|
+
background:
|
|
148
|
+
linear-gradient(90deg, var(--panel) 30%, transparent) left center / 40px 100% no-repeat local,
|
|
149
|
+
linear-gradient(270deg, var(--panel) 30%, transparent) right center / 40px 100% no-repeat local,
|
|
150
|
+
linear-gradient(90deg, color-mix(in srgb, var(--ink) 18%, transparent), transparent) left center / 14px 100% no-repeat scroll,
|
|
151
|
+
linear-gradient(270deg, color-mix(in srgb, var(--ink) 18%, transparent), transparent) right center / 14px 100% no-repeat scroll,
|
|
152
|
+
var(--panel);
|
|
153
|
+
scrollbar-width: thin; scrollbar-color: var(--edge) transparent; }
|
|
154
|
+
.fd-scroll::-webkit-scrollbar { height: 10px; width: 10px; }
|
|
155
|
+
.fd-scroll::-webkit-scrollbar-thumb { background: var(--edge); border-radius: var(--r-pill); border: 2px solid var(--panel); }
|
|
156
|
+
.fd-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
157
|
+
.fd-scroll:focus-visible { outline-offset: -2px; }
|
|
158
|
+
.fd-scroll[data-fd-overflow] { border-color: var(--edge); }
|
|
159
|
+
pre.fd-scroll, .fd-scroll pre { margin: 0; padding: var(--space-3); font: var(--t-body)/1.5 var(--font-data); white-space: pre; }
|
|
160
|
+
.fd-table { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; }
|
|
161
|
+
/* Inside its own box a table keeps its natural width and the box scrolls; words are not broken. */
|
|
162
|
+
.fd-scroll > .fd-table { width: max-content; min-width: 100%; }
|
|
163
|
+
.fd-scroll .fd-table td, .fd-scroll .fd-table th { overflow-wrap: normal; }
|
|
164
|
+
.fd-table th, .fd-table td { text-align: left; padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
165
|
+
.fd-table th { background: var(--panel-2); color: var(--muted); font-size: var(--t-label); font-weight: 600; white-space: nowrap; }
|
|
166
|
+
.fd-table tbody tr:hover { background: var(--panel-2); }
|
|
167
|
+
.fd-table td.fd-num, .fd-table th.fd-num { text-align: right; }
|
|
168
|
+
|
|
169
|
+
/* ---- atoms ---- */
|
|
170
|
+
.fd-card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--space-4); min-width: 0; }
|
|
171
|
+
.fd-card > h3:first-child { display: flex; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; }
|
|
172
|
+
.fd-tile { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--space-3); min-width: 0; }
|
|
173
|
+
.fd-tile .fd-label { color: var(--muted); font-size: var(--t-label); }
|
|
174
|
+
.fd-tile .fd-value { font: 600 var(--t-section)/1.2 var(--font-data); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
175
|
+
/* Status: the word in --ink, the colour in the dot and the fill; never colour alone. */
|
|
176
|
+
.fd-chip { display: inline-flex; align-items: center; gap: var(--space-1); max-width: 100%; font: var(--t-chip)/1.6 var(--font-data);
|
|
177
|
+
padding: 0 var(--space-2); border: 1px solid var(--border-strong); border-radius: var(--r-pill); color: var(--ink); background: var(--panel);
|
|
178
|
+
white-space: nowrap; }
|
|
179
|
+
.fd-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex: none; }
|
|
180
|
+
.fd-s-ok { background: var(--ok-weak); } .fd-s-ok .fd-dot, .fd-dot.fd-s-ok { background: var(--ok); }
|
|
181
|
+
.fd-s-warn { background: var(--warn-weak); } .fd-s-warn .fd-dot, .fd-dot.fd-s-warn { background: var(--warn); }
|
|
182
|
+
.fd-s-danger { background: var(--danger-weak); } .fd-s-danger .fd-dot, .fd-dot.fd-s-danger { background: var(--danger); }
|
|
183
|
+
.fd-s-info { background: var(--info-weak); } .fd-s-info .fd-dot, .fd-dot.fd-s-info { background: var(--info); }
|
|
184
|
+
.fd-banner { display: flex; gap: var(--space-2); align-items: flex-start; padding: var(--space-3); border: 1px solid var(--edge); border-radius: var(--r-card); }
|
|
185
|
+
.fd-btn { font: 600 var(--t-body) var(--font-ui); padding: var(--space-2) var(--space-3); border-radius: var(--r-control); cursor: pointer;
|
|
186
|
+
border: 1px solid var(--edge); background: var(--panel); color: var(--ink); max-width: 100%;
|
|
187
|
+
transition: background-color var(--dur-hover) var(--motion-ease), border-color var(--dur-hover) var(--motion-ease); }
|
|
188
|
+
.fd-btn:hover { background: var(--panel-2); }
|
|
189
|
+
.fd-btn--primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
|
|
190
|
+
.fd-btn--primary:hover { background: var(--accent); filter: brightness(1.06); }
|
|
191
|
+
.fd-btn--danger { border-color: var(--danger); }
|
|
192
|
+
.fd-btn:disabled { cursor: not-allowed; opacity: 0.55; }
|
|
193
|
+
.fd-input, .fd-select, .fd-textarea { font: var(--t-body) var(--font-ui); color: var(--ink); background: var(--panel);
|
|
194
|
+
border: 1px solid var(--edge); border-radius: var(--r-control); padding: var(--space-2); max-width: 100%; min-width: 0; }
|
|
195
|
+
.fd-empty { color: var(--muted); padding: var(--space-4); text-align: center; }
|
|
196
|
+
|
|
197
|
+
/* ---- appearance owned by the host (ED-6) ---- */
|
|
198
|
+
html[data-fd-host-appearance] [data-fd-appearance-control] { display: none !important; }
|