Side A adds substantive infrastructure and functionality: CI/CD deployment pipeline, Dockerfile, fly.toml, GitHub token auth for private git mirroring, a real /watch dashboard with SSE-driven audit trail and process state, plus new tests covering ranking edge cases (single contributor, missing API key) and audit event broadcasting. Side B is purely CSS polish (padding, border-radius, focus outlines, color tweaks) across two theme files with no new tests, functionality, or lasting architectural value.
constitution · epochs · watch · epoch 3
c_7a129e904906 (tommy-mor) vs c_6864b1ca8ce6 (tommy-mor)
download prompt · raw event · cmp_1d39a3a8168fb4
council reasoning
A ships production deployment (Dockerfile, fly.toml, CI deploy), a real /watch audit UI with SSE progress/state APIs, multi-repo/contributor config, git auth and emission reliability fixes, plus tests—lasting product and ops capability. B only restyles existing vote/pin controls in two CSS themes (padding, borders, focus rings) with no behavior, API, or architecture change.
Side A adds substantial new functionality and operational infrastructure: production deployment (Docker, Fly, GitHub Actions), a live audit dashboard with SSE event streaming, status APIs, authenticated Git access, improved repository discovery efficiency, startup/retry logic, and accompanying integration/unit tests. Side B is almost entirely CSS refinements for the voting UI—improving layout, styling, focus states, and accessibility—but it does not introduce comparable core behavior or architecture.
sides
A — c_7a129e904906 (tommy-mor)
message
[4e327784] deploy live constitution dashboard Expose auditable progress and event streaming, configure the production roots and runtime, and make tested main-branch commits the deployment authority. Co-authored-by: Cursor <cursoragent@cursor.com>
diff preview
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..c9d63a722beba0a0297fc853089332c460ab78dd
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+.git
+.venv
+.hypothesis
+__pycache__
+tests
+*.json
+*.bsp
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000000000000000000000000000000000000..76dcdf82d53177c1e47d86b23a54523239d232a6
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,49 @@
+name: Test and deploy
+
+on:
+ push:
+ branches: [main]
+
+concurrency:
+ group: production
+ cancel-in-progress: false
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: astral-sh/setup-uv@v6
+ with:
+ enable-cache: true
+
+ - name: Run Python tests
+ run: uv run pytest -q
+
+ - name: Install Babashka
+ run: |
+ curl -fsSL https://raw.githubusercontent.com/babashka/babashka/master/install \
+ | sudo bash -s -- --dir /usr/local/bin
+
+ - name: Run process integration tests
+ run: bb TEST.sh
+
+ deploy:
+ needs: test
+ runs-on: ubuntu-latest
+ environment:
+ name: production
+ url: https://token.slug.social
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: superfly/flyctl-actions/setup-flyctl@master
+
+ - name: Deploy to Fly
+ run: flyctl deploy --remote-only
+ env:
+ FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..c9a5c00782371c19ad5ab5c58cf6f5a8ffec0141
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends git ca-certificates \
+ && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+COPY pyproject.toml uv.lock ./
+RUN uv sync --frozen --no-install-project
+
+COPY constitution.py ./
+
+ENV PATH="/app/.venv/bin:${PATH}" \
+ PYTHONUNBUFFERED="1"
+
+EXPOSE 8080
+CMD ["python", "constitution.py"]
diff --git a/constitution.py b/constitution.py
index 4bee9f83663ab7fb36db95129b92b64b4ef57258..a58257e1881b21d1d6fa8e68a3faa222e4f661ef 100644
--- a/constitution.py
+++ b/constitution.py
@@ -24,12 +24,12 @@ A daily GitHub Action backs up the JSONL ledger to the same repo.
Run: uv run constitution.py
"""
-from decimal import Decimal, getcontext
+from decimal import Decimal, getcontext, DefaultContext
from datetime import datetime, timezone
from fastapi import FastAPI, Request, Response
from fastapi.responses import PlainTextResponse, HTMLResponse
from starlette.middleware.sessions import SessionMiddleware
-import json, time, os, asyncio, httpx, pathlib, subprocess, hashlib, re, fcntl
+import json, time, os, asyncio, httpx, pathlib, subprocess, hashlib, re, fcntl, base64
import sympy as sp # type: ignore[reportMissingImports]
from tenacity import retry, retry_if_exception, stop_after_attempt, wait_exponential
from evaleval import (
@@ -37,6 +37,7 @@ from evaleval import (
exec_event, One, Two, Three, Selector, MORPH, PREPEND,
)
+DefaultContext.prec = 50
getcontext().prec = 50
app = FastAPI()
@@ -129,14 +130,47 @@ OPENROUTER_BASE_URL = os.environ.get("OPENROUTER_BASE_URL", "https://openrouter.
# using the exact same source; their normalized values are committed to every
# discovery event.
DEFAULT_REPOSITORIES = [
+ {
+ "id": "constitution",
+ "url": "https://github.com/sortersocial/constitution.git",
+ "refs": ["refs/heads/**"],
+ },
{
"id": "slug",
- "url": "https://github.com/tommy-mor/slug.git",
+ "url": "https://github.com/sortersocial/slug.git",
+ "refs": ["refs/heads/**"],
+ },
+ {
+ "id": "sorter",
+ "url": "https://github.com/sorterisntonline/sorter.git",
+ "refs": ["refs/heads/**"],
+ },
+ {
+ "id": "sorter2",
+ "url": "https://github.com/sortersocial/sorter2.git",
+ "refs": ["refs/heads/**"],
+ },
+ {
+ "id": "sorter-oldest",
+ "url": "https://github.com/tommy-mor/sorter.git",
"refs": ["refs/heads/**"],
},
]
DEFAULT_CONTRIBUTORS = {
"tommy-mor": ["thmorriss@gmail.com"],
+ "christopher-whitman": [
+ "chris@cwwhitman.com",
+ "7566903+cwwhitman@users.noreply.github.com",
+ ],
+ "jake-chvatal": [
+ "jake+github@uln.industries",
+ "jakechvatal@gmail.com",
+ "jake@isnt.online",
+ ],
+ "lara": ["me@lara.lv"],
+ "nat-reid": ["nathanielreid@gmail.com"],
+ "zod": ["jason.p.mcel@gmail.com", "me@zod.tf"],
+ "jovan": ["jovan@slug.social", "jovan@getcivicai.com"],
}
REPOSITORIES = json.loads(
@@ -147,6 +181,7 @@ CONTRIBUTORS = json.loads(
)
GIT_MIRROR_DIR = pathlib.Path(os.environ.get("GIT_MIRROR_DIR", "/data/git"))
GIT_TIMEOUT_SECONDS = int(os.environ.get("GIT_TIMEOUT_SECONDS", "120"))
+GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
# Council model IDs: slug.social garden rank under this parent (bodies = OpenRouter URLs), then top-up from OpenRouter list.
SLUG_SOCIAL_BASE_URL = os.environ.get("SLUG_SOCIAL_BASE_URL", "https://slug.social").rstrip("/")
@@ -661,20 +696,30 @@ def _git(repo: pathlib.Path | None, *args: str, input_bytes: bytes | None = None
if repo is not None:
command += ["-C", str(repo)]
command += list(args)
+ git_env = {
+ **os.environ,
+ "GIT_CONFIG_NOSYSTEM": "1",
+ "GIT_CONFIG_GLOBAL": os.devnull,
+ "GIT_NO_REPLACE_OBJECTS": "1",
+ "LC_ALL": "C",
+ "TZ": "UTC",
+ }
+ if GITHUB_TOKEN:
+ credential = base64.b64encode(
+ f"x-access-token:{GITHUB_TOKEN}".encode()
+ ).decode()
+ git_env.update({
+ "GIT_CONFIG_COUNT": "1",
+ "GIT_CONFIG_KEY_0": "http.https://github.com/.extraHeader",
+ "GIT_CONFIG_VALUE_0": f"Authorization: Basic {credential}",
+ })
try:
result = subprocess.run(
command,
input=input_bytes,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
- env={
- **os.environ,
- "GIT_CONFIG_NOSYSTEM": "1",
- "GIT_CONFIG_GLOBAL": os.devnull,
- "GIT_NO_REPLACE_OBJECTS": "1",
- "LC_ALL": "C",
- "TZ": "UTC",
- },
+ env=git_env,
timeout=GIT_TIMEOUT_SECONDS,
check=False,
)
@@ -844,9 +889,15 @@ def _build_discovery(epoch_n: int, boundary_ms: int, events: list) -> GitDiscove
canonical_location = min(
locations[qualified_oid], key=lambda x: (x[0], x[1])
)
+ # One commit may be reachable from dozens of refs in the same mirror.
+ # Verify its object once per repository, not once per source ref.
+ object_locations = {
+ (str(m), raw_oid): (m, raw_oid)
+ for _, _, m, raw_oid in locations[qualified_oid]
+ }
object_hashes = {
hashlib.sha256(_git(m, "cat-file", "commit", raw_oid)).hexdigest()
- for _, _, m, raw_oid in locations[qualified_oid]
+ for m, raw_oid in object_locations.values()
}
if len(object_hashes) != 1:
raise RuntimeError(f"conflicting Git objects share OID {qualified_oid}")
@@ -994,11 +1045,55 @@ async def discover_repositories(epoch_n: int, boundary_ms: int) -> GitDiscovery:
SSE_CLIENTS = []
+AUDIT_HISTORY = []
+AUDIT_SEQUENCE = 0
+PROCESS_STATE = {
+ "running": False,
+ "phase": "idle",
+ "progress": 100,
+ "message": "Waiting for the next epoch",
+}
+
+
+def _sse_event(event_name: str, payload: dict) -> str:
+ return (
+ f"event: {event_name}\n"
+ f"data: {json.dumps(payload, separators=(',', ':'))}\n\n"
+ )
+
+
+async def broadcast_audit(
+ kind: str,
+ message: str,
+ *,
+ progress: int | None = None,
+ phase: str | None = None,
+) -> dict:
+ global AUDIT_SEQUENCE
+ AUDIT_SEQUENCE += 1
+ if progress is not None:
+ PROCESS_STATE["progress"] = max(0, min(100, int(progress)))
+ if phase is not None:
+ PROCESS_STATE["phase"] = phase
+ PROCESS_STATE["message"] = message
+ payload = {
+ "id": AUDIT_SEQUENCE,
+ "timestamp_ms": int(time.time() * 1000),
+ "kind": kind,
+ "message": message,
+ **PROCESS_STATE,
+ }
+ AUDIT_HISTORY.append(payload)
+ del AUDIT_HISTORY[:-200]
+ wire = _sse_event("audit", payload)
+ for queue in list(SSE_CLIENTS):
+ await queue.put(wire)
+ return payload
async def broadcast_js(js: str):
"""Send a JS snippet to all connected SSE clients."""
- for queue in SSE_CLIENTS:
+ for queue in list(SSE_CLIENTS):
await queue.put(js)
@@ -1006,10 +1101,29 @@ async def rank_commits(commits: list[dict]):
if not commits:
return {}, []
- models = await fetch_top_models(n=3)
contributors = sorted(set(c["contributor"] for c in commits))
- if len(contributors) > 1 and not models:
+ if len(contributors) == 1:
+ await broadcast_audit(
+ "ranking",
+ f"Only {contributors[0]} is eligible; rank is 1.0",
+ progress=90,
+ phase="finalizing",
+ )
+ return {contributors[0]: Decimal("1")}, []
+ if not (OPENROUTER_API_KEY or "").strip():
+ raise RuntimeError(
+ "OPENROUTER_API_KEY is required when multiple contributors need ranking"
+ )
+
+ models = await fetch_top_models(n=3)
+ if not models:
raise RuntimeError("no council models available for contributor ranking")
+ await broadcast_audit(
+ "council",
+ f"Council selected: {', '.join(models)}",
+ progress=35,
+ phase="ranking",
+ )
await broadcast_js(exec_event(Three[Selector("#emission-log")][PREPEND][
["div.log-council", f"Council: {', '.join(models)} — {len(commits)} commits"]
]))
@@ -1035,6 +1149,11 @@ async def rank_commits(commits: list[dict]):
async def compare_fn(i, j):
a1, a2 = authors[i], authors[j]
+ await broadcast_audit(
+ "comparison",
+ f"Comparing {a1} with {a2}",
+ phase="ranking",
+ )
await broadcast_js(exec_event(Three[Selector("#emission-status")][MORPH][
["div#emission-status", f"Comparing {a1} vs {a2}…"]
]))
@@ -1050,6 +1169,11 @@ async def rank_commits(commits: list[dict]):
if winner_weight <= 0 or loser_weight <= 0:
raise ValueError("ratio weights must be positive")
results.append((w, l, winner_weight, loser_weight))
+ await broadcast_audit(
+ "vote",
+ f"{model}: {authors[w]} over {authors[l]} ({result['ratio']})",
+ phase="ranking",
+ )
await broadcast_js(exec_event(Three[Selector("#emission-log")][PREPEND][
["div.log-vote",
["span.model", model], " — ",
@@ -1059,6 +1183,11 @@ async def rank_commits(commits: list[dict]):
]
]))
except Exception as e:
+ await broadcast_audit(
+ "error",
+ f"{model} failed: {e}",
+ phase="error",
+ )
await broadcast_js(exec_event(Three[Selector("#emission-log")][PREPEND][
["div.log-error", f"⚠ {model}: {e}"]
]))
@@ -1068,8 +1197,13 @@ async def rank_commits(commits: list[dict]):
async def progress_fn(ev):
if ev["phase"] == "spanning_tree":
label = f"Spanning tree: {ev['step']}/{ev['total']}"
+
… preview truncated; 28,089 characters omittedB — c_6864b1ca8ce6 (tommy-mor)
message
[51e73d38] voting looks better?
diff preview
diff --git a/server/static/theme_default.css b/server/static/theme_default.css
index 5f60ff7a8cf9045d8d43228baad8ba80ad057f2a..ec0fbe7acee0aa2802f978f14a9b0fc86e78c5b8 100644
--- a/server/static/theme_default.css
+++ b/server/static/theme_default.css
@@ -844,18 +844,25 @@ a.ont-vote-compare-btn {
color: var(--ui);
cursor: pointer;
font-size: 11px;
- padding: 2px 8px;
+ padding: 3px 10px;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 4px;
font-family: inherit;
+ border-radius: 2px;
}
button.ont-pin-btn:hover,
a.ont-vote-compare-btn:hover { color: var(--signal); }
+button.ont-pin-btn:focus-visible,
+a.ont-vote-compare-btn:focus-visible {
+ outline: 2px solid var(--link);
+ outline-offset: 2px;
+}
button.ont-pin-btn-active {
border-color: var(--link);
color: var(--signal);
+ background: color-mix(in srgb, var(--link) 12%, var(--g4));
}
.ont-garden-child-actions {
display: inline-flex;
@@ -868,12 +875,33 @@ a.ont-garden-vote-ico,
span.ont-garden-pinned-here {
font-size: 13px;
line-height: 1;
- padding: 0 2px;
- border: none;
- background: transparent;
+ padding: 2px 6px;
+ min-width: 26px;
+ min-height: 26px;
+ box-sizing: border-box;
+ border: var(--bv) solid;
+ border-color: var(--hi) var(--lo) var(--lo) var(--hi);
+ border-radius: 2px;
+ background: var(--g3);
cursor: pointer;
text-decoration: none;
color: inherit;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+a.ont-garden-vote-ico:hover {
+ color: var(--signal);
+ background: var(--g4);
+}
+span.ont-garden-pinned-here {
+ border-color: var(--link);
+ color: var(--signal);
+ cursor: default;
+}
+button.ont-garden-pin-ico:focus-visible {
+ outline: 2px solid var(--link);
+ outline-offset: 2px;
}
body.view-ontology-light ol.ont-ranking-list li,
body.view-ontology-light ul.ont-group-list li {
@@ -889,8 +917,19 @@ body.view-ontology-light ul.ont-group-list li .item-link {
/* Vote compare page */
.vote-compare-shell {
- margin: 12px 0;
+ margin: 12px 0 20px;
max-width: 720px;
+ padding: 14px 16px 18px;
+ background: var(--g2);
+ border: var(--bv-lg) solid;
+ border-color: var(--hi) var(--lo) var(--lo) var(--hi);
+}
+body.view-vote-compare .vote-compare-shell > h2 {
+ margin-top: 0;
+ font-size: 12px;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--meta);
}
.vote-compare-pair {
display: flex;
@@ -899,12 +938,20 @@ body.view-ontology-light ul.ont-group-list li .item-link {
gap: 10px 16px;
margin: 10px 0;
}
+.vote-compare-item {
+ text-decoration: none;
+}
.vote-compare-item code {
font-size: 13px;
}
+.vote-compare-item:hover code {
+ color: var(--signal);
+}
.vote-compare-vs {
color: var(--meta);
- font-size: 12px;
+ font-size: 11px;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
}
.vote-compare-slider-label {
display: flex;
@@ -917,7 +964,7 @@ body.view-ontology-light ul.ont-group-list li .item-link {
#vote-preference-slider {
flex: 1 1 180px;
min-width: 120px;
- accent-color: var(--ui);
+ accent-color: var(--link);
}
.vote-thread-picker {
margin: 10px 0;
@@ -931,9 +978,17 @@ body.view-ontology-light ul.ont-group-list li .item-link {
}
#vote-thread-select {
min-width: 160px;
+ background: var(--g3);
+ border: var(--bv) solid;
+ border-color: var(--hi) var(--lo) var(--lo) var(--hi);
+ color: var(--ui);
+ font-size: 12px;
+ padding: 3px 8px;
}
#vote-edge-history-region {
margin: 14px 0 18px;
+ padding-bottom: 8px;
+ border-bottom: 1px dashed var(--lo);
}
.vote-compare-preview-wrap {
margin: 12px 0;
@@ -946,8 +1001,37 @@ body.view-ontology-light ul.ont-group-list li .item-link {
#vote-compare-preview {
min-height: 48px;
}
+.vote-explain-label {
+ display: block;
+ font-size: 12px;
+ color: var(--meta);
+ margin: 12px 0 4px;
+}
+body.view-vote-compare #vote-explain {
+ width: 100%;
+ max-width: 100%;
+ box-sizing: border-box;
+ background: var(--g3);
+ border: var(--bv) solid;
+ border-color: var(--hi) var(--lo) var(--lo) var(--hi);
+ color: var(--prose);
+ font: inherit;
+ line-height: 1.45;
+ padding: 8px 10px;
+}
+body.view-vote-compare #vote-explain:focus {
+ outline: none;
+ border-color: var(--link);
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--link) 22%, transparent);
+}
+body.view-vote-compare #vote-compare-form button[type="submit"] {
+ margin-top: 6px;
+}
.vote-edge-history-title {
- font-size: 13px;
+ font-size: 12px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--meta);
margin: 16px 0 8px;
}
ol.vote-edge-history {
@@ -968,6 +1052,8 @@ li.vote-edge-history-row {
.vote-edge-bar {
margin-top: 4px;
max-width: 100%;
+ border-radius: 2px;
+ overflow: hidden;
}
.vote-edge-reason {
margin-top: 4px;
diff --git a/server/static/theme_retro_craft.css b/server/static/theme_retro_craft.css
index 3853e9edaf4c77c62dcf350ff28920448dd943a8..092d4b3540b052cc86870179e4c264541c4446cc 100644
--- a/server/static/theme_retro_craft.css
+++ b/server/static/theme_retro_craft.css
@@ -587,3 +587,337 @@ body.view-ontology #controls {
padding-right: 1.25rem;
}
}
+
+/* ----------------------------------------------------------------
+ Ontology garden: pin HUD, row pins, vote-compare
+ (Global craft `code` + `button[type=submit]` are tuned for dark
+ thread pages; these overrides match the cream ontology shell.)
+ ---------------------------------------------------------------- */
+body.view-ontology #slug-pin-hud.slug-pin-hud {
+ margin-left: auto;
+ max-width: min(42vw, 280px);
+ font-family: var(--font-ui);
+ font-size: 0.72rem;
+ color: #5c574e;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+body.view-ontology .slug-pin-hud-link {
+ color: #1a4a8c;
+ text-decoration: none;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+body.view-ontology .slug-pin-hud-link:hover {
+ color: #0d3d82;
+ text-decoration: underline;
+}
+body.view-ontology .slug-pin-hud-glyph {
+ font-size: 0.95rem;
+ line-height: 1;
+}
+
+body.view-ontology .ont-item-meta {
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.35rem;
+}
+body.view-ontology .ont-item-title {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+body.view-ontology .ont-item-pin-zone {
+ flex: 0 0 auto;
+ margin-left: auto;
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+}
+
+body.view-ontology button.ont-pin-btn,
+body.view-ontology a.ont-vote-compare-btn {
+ background: #ebe6dc;
+ border: 1px solid #c8c4bc;
+ border-radius: 2px;
+ color: #3d3a34;
+ cursor: pointer;
+ font-family: var(--font-ui);
+ font-size: 0.72rem;
+ letter-spacing: 0.04em;
+ padding: 0.25rem 0.55rem;
+ text-decoration: none;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.3rem;
+}
+body.view-ontology button.ont-pin-btn:hover,
+body.view-ontology a.ont-vote-compare-btn:hover {
+ border-color: #a68e6b;
+ color: #1a1814;
+}
+body.view-ontology button.ont-pin-btn-active {
+ border-color: #1a4a8c;
+ background: color-mix(in srgb, #1a4a8c 12%, #ebe6dc);
+ color: #0d2d5c;
+}
+body.view-ontology button.ont-pin-btn:focus-visible,
+body.view-ontology a.ont-vote-compare-btn:focus-visible {
+ outline: 2px solid #1a4a8c;
+ outline-offset: 2px;
+}
+
+body.view-ontology .ont-garden-child-actions {
+ display: inline-flex;
+ align-items: center;
+ margin-right: 0.35rem;
+ vertical-align: middle;
+ gap: 0.1rem;
+}
+body.view-ontology button.ont-garden-pin-ico,
+body.view-ontology a.ont-garden-vote-ico,
+body.view-ontology span.ont-garden-pinned-here {
+ font-size: 0.95rem;
+ line-height: 1;
+ padding: 0.1rem 0.35rem;
+ min-width: 1.65rem;
+ min-height: 1.65rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid #c8c4bc;
+ border-radius: 2px;
+ background: #f7f3eb;
+ cursor: pointer;
+ text-decoration: none;
+ color: #1a1814;
+}
+body.view-ontology a.ont-garden-vote-ico:hover {
+ border-color: #a68e6b;
+ background: #ebe6dc;
+}
+body.view-ontology span.ont-garden-pinned-here {
+ border-color: #1a4a8c;
+ background: color-mix(in srgb, #1a4a8c 10%, #f7f3eb);
+ cursor: default;
+}
+body.view-ontology form.ont-garden-pin-form {
+ display: inline;
+ margin: 0;
+ padding: 0;
+}
+body.view-ontology button.ont-garden-pin-ico:focus-visible {
+ outline: 2px solid #1a4a8c;
+ outline-offset: 2px;
+}
+
+body.view-ontology ol.ont-ranking-list li,
+body.view-ontology ul.ont-group-list li {
+ display: flex;
+ align-items: baseline;
+ gap: 0.35rem;
+}
+body.view-ontology ol.ont-ranking-list li .item-link,
+body.view-ontology ul.ont-group-list li .item-link {
+ flex: 1;
+ min-width: 0;
+}
+
+body.view-ontology code,
+body.view-ontology .vote-compare-item code,
+body.view-ontology .vote-edge-meta code {
+ background: #ebe6dc;
+ color: #1a1814;
+ border: 1px solid #d4cfc4;
+ padding: 0.12em 0.35em;
+ font-size: 0.88em;
+}
+
+body.view-ontology div.ratio-bar {
+ background: #e3ded4;
+ border: 1px solid #c8c4bc;
+}
+body.view-ontology div.ratio-left,
+body.view-ontology div.ratio-right {
+ background: #cec9bf;
+}
+body.view-ontology div.ratio-left.current,
+body.view-ontology div.ratio-right.current {
+ background: #9a7b4a;
+}
+
+body.view-ontology.view-vote-compare .vote-compare-shell {
+ margin: 0.75rem 0 1.5rem;
+ max-width: 36rem;
+ padding: 0.85rem 1rem 1.1rem;
+ background: #f7f3eb;
+ border: 1px solid #c8c4bc;
+ border-radius: 3px;
+ box-shadow: 0 1px 0 rgba(26, 24, 20, 0.06);
+}
+body.view-ontology.view-vote-compare .vote-compare-shell > h2 {
+ margin-top: 0;
+ font-size: 0.7rem;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: #5c574e;
+}
+body.view-ontology .vote-compare-pair {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.65rem 1rem;
+ margin: 0.65rem 0 0.85rem;
+}
+body.view-ontology .vote-compare-vs {
+ color: #8a857a;
+ font-family: var(--font-ui);
+ font-size: 0.72rem;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+}
+body.view-ontology .vote-compare-item {
+ text-decoration: none;
+}
+body.view-ontology .vote-compare-item:hover code {
+ border-color: #a68e6b;
+ background: #f0ebe3;
+}
+
+body.view-ontology .vote-thread-picker {
+ margin: 0.85rem 0;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.5rem 0.75rem;
+}
+body.view-ontology .vote-thread-picker-label {
+ font-family: var(--font-ui);
+ font-size: 0.72rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: #5c574e;
+}
+body.view-ontology #vote-thread-select {
+ min-width: 10rem;
+ background: #f7f3eb;
+ border: 1px solid #c8c4bc;
+ color: #1a1814;
+ font-family: var(--font-ui);
+ font-size: 0.78rem;
+ padding: 0.3rem 0.45rem;
+}
+
+body.view-ontology .vote-compare-slider-label {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.5rem 0.65rem;
+ margin: 0.85rem 0;
+ font-family: var(--font-ui);
+ font-size: 0.72rem;
+ color: #5c574e;
+}
+body.view-ontology #vote-preference-slider {
+ flex: 1 1 11rem;
+ min-width: 8rem;
+ accent-color: #9a7b4a;
+}
+
+body.view-ontology .vote-explain-label {
+ display: block;
+ font-family: var(--font-ui);
+ font-size: 0.72rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: #5c574e;
+ margin: 0.65rem 0 0.35rem;
+}
+body.view-ontology #vote-compare-form textarea,
+body.view-ontology.view-vote-compare textarea#vote-explain {
+ background: #fdfcfa;
+ border: 1px solid #c8c4bc;
+ color: #1a1814;
+ font-family: var(--font-prose);
+ font-size: 0.9rem;
+ line-height: 1.45;
+ width: 100%;
+ max-width: 100%;
+ padding: 0.55rem 0.65rem;
+ margin-top: 0.25rem;
+}
+body.view-ontology #vote-compare-form textarea:focus {
+ border-color: #a68e6b;
+ outline: none;
+ box-shadow: 0 0 0 2px color-mix(in srgb, #a68e6b 28%, transparent);
+}
+
+body.view-ontology #vote-compare-form button[type="s
… preview truncated; 1,584 characters omittedHardlinks — judgments / attempts / prompt
judgments
attempts
Prompt text is loaded only by the download route.