Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
name: Deploy

# Build-only strict MkDocs build so a broken vendored page or nav entry
# fails CI instead of deploying. See scripts/vendor-docs.sh and core's
# ADR-101 for the vendoring contract.
name: Knowledge base

on:
pull_request:
paths:
- ".github/workflows/knowledge-base.yml"
- "knowledge/**"
- "mkdocs.yml"
- "scripts/vendor-knowledge.sh"
push:
branches: [main]
paths:
- ".github/workflows/knowledge-base.yml"
- "knowledge/**"
- "mkdocs.yml"
- "scripts/vendor-knowledge.sh"
workflow_dispatch:

permissions:
Expand All @@ -28,19 +35,17 @@ jobs:
python-version: "3.12"
- name: Install MkDocs
run: pip install mkdocs==1.6.1 mkdocs-material==9.7.6
- name: Vendor product docs
run: ./scripts/vendor-docs.sh
- name: Fetch org star count
run: ./scripts/fetch-org-stats.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build site (strict)
- name: Vendor authoritative knowledge
run: ./scripts/vendor-knowledge.sh
- name: Build knowledge base
run: mkdocs build --strict
- uses: actions/upload-pages-artifact@v3
- if: github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@v3
with:
path: site

deploy:
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
environment:
Expand Down
48 changes: 43 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,44 @@
site/
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# Build-time vendored content (see core's ADR-101 — never committed).
.vendor/
docs/core/*
!docs/core/.gitkeep
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/.vinext/
/out/

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
next-env.d.ts
/dist/
/.wrangler/
/outputs/
/work/
/knowledge/vendor/
/site/
5 changes: 5 additions & 0 deletions .openai/hosting.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"project_id": "appgprj_6a660ef8c7e4819196927967748b9a75",
"d1": null,
"r2": null
}
115 changes: 29 additions & 86 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,96 +1,39 @@
# AsDecided documentation site
# AsDecided on the web

The [`asdecided`](https://github.com/asdecided) organization documentation
site, served at https://asdecided.github.io/. It uses the same MkDocs Material
foundation as the native core documentation so every product reads as one
brand.
Deployed by GitHub Actions on every push to `main`.
Two public surfaces for [AsDecided](https://github.com/asdecided):

## How content gets here
- The product site is the concise introduction to AsDecided. It is deployed
through ChatGPT Sites.
- GitHub Pages is the searchable knowledge base for the specification,
concepts, guides, and reference material.

Sections under `/core/` (and future product sections) are **vendored,
not authored here**: `scripts/vendor-docs.sh` sparse-checks out each source
repo's `docs/` directory into `docs/<slug>/` before `mkdocs build`. Nothing
vendored is committed — `docs/core/` is gitignored. Because it lands in
the same MkDocs `docs_dir` as everything else, the source repos' existing
relative links between pages (`[relationships.md](relationships.md)`, `cli.md#schema`,
etc.) resolve correctly with no rewriting.
## Product site

The rationale and contract are recorded in core's
[ADR-101](https://github.com/asdecided/core/blob/main/decisions/decisions/adr-101-org-docs-site-and-topology.md).

The site's own visual identity (light theme, Inter for prose, JetBrains
Mono for code, the lamplighter at icon size only) is recorded in core's
[ADR-102](https://github.com/asdecided/core/blob/main/decisions/decisions/adr-102-org-site-brand-direction.md):
it deliberately *rhymes with, but does not match*, core's
`rac-localview` product-UI theme (dark, mono-everywhere) — three shared
constants (the amber accent, mono-for-code, the lamplighter), everything
else per surface. Brand assets (`docs/fonts/`,
`docs/images/favicon.png` + `lamplighter.png`) are one-time copies of
`rac-localview`'s source files; this org site owns the canonical copies per
ADR-092 ("the brand lives at the org").

Adding a new product section:

1. Add a `vendor_repo` call to `scripts/vendor-docs.sh`.
2. Add its pages to the `nav:` list in `mkdocs.yml`.
3. Add a card to "The ecosystem" grid in `docs/index.md`.

**The homepage hero is temporarily spec-led** (headline, CTAs, and eyebrow
all point at the specification announcement essay, per an explicit "for now"
request) instead of the previous product pitch — see the comment at the
top of `overrides/home.html` for what to restore and why.

## Org-wide star count

The hero shows a "★ N stars across AsDecided's open-source repos" line,
sourced by `scripts/fetch-org-stats.sh` (sums `stargazers_count` across the
org's public repos via the GitHub API) and exposed to `overrides/home.html`
through `mkdocs.yml`'s `extra.total_org_stars` (an `!ENV` tag reading the
`TOTAL_ORG_STARS` env var the script sets). Requires a `GITHUB_TOKEN` with
read access — the deploy workflow uses the default `secrets.GITHUB_TOKEN`.
Without a token (e.g. local `mkdocs serve`), the count defaults to 0 and the
hero hides the line entirely.

## Essays

`docs/essays.md` (nav label "Essays") is a **hand-curated index**, not an
auto-generated blog. An essay lives wherever it makes most sense — a spec
announcement lives inside the relevant section (e.g.
`docs/spec/why-strict.md`) rather than a generic `/blog/` — and gets one
card added to `docs/essays.md` linking to it. This is deliberate: essays
don't all belong under one directory, so there's nothing for a plugin to
auto-discover.

(An earlier pass tried MkDocs Material's built-in blog plugin for this; it
assumes every post lives under one `blog_dir`, which fought the actual
authoring pattern. See git history if a real dated/multi-author blog is
ever needed again — it's a small, well-understood revert.)
```console
npm ci
npm run dev
```

## Social previews (Open Graph / Twitter Card)
Build and verify the deployable site with:

`overrides/main.html` extends Material's real `main.html` — which is itself
just a one-line `{% extends "base.html" %}` pass-through, so this is a safe
override — to add `og:*` and `twitter:*` meta tags to every page via
`base.html`'s `extrahead` block (Material ships nothing here by default).
```console
npm run build
npm test
```

- **Title/description** come from each page's frontmatter (`title:`,
`description:`) when set, falling back to `config.site_name` /
`site_description` for pages without them (the homepage always uses
`site_name`, since `docs/index.md` has no `title:`).
- **Image** is one static card, `docs/images/social-card.png` (1200×630,
the standard OG size) — not per-page. It was composed as an HTML mockup
(brand tokens, the lamplighter, the product tagline) and rendered with
Playwright rather than a design tool; regenerate the same way if the
brand or tagline changes.
- Add a `description:` to a page's frontmatter for a tailored social
preview instead of the generic site description (see
`docs/spec/why-strict.md` for an example).
## Knowledge base

## Local development
The knowledge base has a small set of owned navigation pages under
`knowledge/`. Authoritative product documentation and specification material
are vendored at build time from
[`asdecided/core`](https://github.com/asdecided/core) and
[`asdecided/spec`](https://github.com/asdecided/spec), so this repository
organizes those sources without forking them.

```sh
pip install mkdocs==1.6.1 mkdocs-material==9.7.6
./scripts/vendor-docs.sh
mkdocs serve
```console
./scripts/vendor-knowledge.sh
uv run --with mkdocs==1.6.1 --with mkdocs-material==9.7.6 mkdocs serve
```

Pull requests build the knowledge base in strict mode. Merges to `main` deploy
it to GitHub Pages.
61 changes: 61 additions & 0 deletions app/BrandHero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use client";

import { useEffect, useRef } from "react";

export function BrandHero() {
const heroRef = useRef<HTMLElement>(null);

useEffect(() => {
const hero = heroRef.current;
if (!hero) return;

let frame = 0;

const update = () => {
frame = 0;
const revealDistance = Math.max(window.innerHeight * 0.42, 280);
const progress = Math.min(Math.max(window.scrollY / revealDistance, 0), 1);
hero.style.setProperty("--reveal", progress.toFixed(3));
};

const onScroll = () => {
if (!frame) frame = window.requestAnimationFrame(update);
};

update();
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll);

return () => {
window.removeEventListener("scroll", onScroll);
window.removeEventListener("resize", onScroll);
if (frame) window.cancelAnimationFrame(frame);
};
}, []);

return (
<section
className="brand-hero"
id="top"
ref={heroRef}
aria-labelledby="brand-tagline"
>
<div className="brand-hero-inner">
<img
className="brand-eyes"
src="/brand-mark.jpg"
alt="AsDecided stepped yellow eyes"
width="720"
height="280"
/>
<p className="brand-tagline" id="brand-tagline">
Build, as decided<span>.</span>
</p>
<span className="scroll-cue" aria-hidden="true">
Scroll
<span>↓</span>
</span>
</div>
</section>
);
}
102 changes: 102 additions & 0 deletions app/DecisionConsole.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"use client";

import { useState } from "react";

const queries = [
{
id: "deletion",
label: "Data deletion",
question: "Can I hard-delete a user when they close their account?",
artifact: "ADR-014",
title: "Soft-delete user records",
answer:
"No. User records remain recoverable for 30 days. New deletion work must preserve the restoration window.",
related: "REQ-021 · DESIGN-008",
},
{
id: "auth",
label: "Authentication",
question: "Should this service issue its own session tokens?",
artifact: "ADR-027",
title: "Centralize session issuance",
answer:
"No. Session tokens are issued by the identity boundary. Services validate them but do not mint replacements.",
related: "REQ-034 · DESIGN-011",
},
{
id: "storage",
label: "Event storage",
question: "Can I overwrite an event when its payload changes?",
artifact: "ADR-031",
title: "Append events; never mutate",
answer:
"No. Corrections append a compensating event so the audit trail and downstream replay remain deterministic.",
related: "REQ-041 · ROADMAP-006",
},
];

export function DecisionConsole() {
const [activeId, setActiveId] = useState(queries[0].id);
const active = queries.find((query) => query.id === activeId) ?? queries[0];

return (
<div className="decision-console">
<div className="console-tabs" role="tablist" aria-label="Example repository queries">
{queries.map((query) => (
<button
type="button"
role="tab"
aria-selected={active.id === query.id}
aria-controls="decision-result"
id={`tab-${query.id}`}
key={query.id}
onClick={() => setActiveId(query.id)}
>
<span className="tab-marker" aria-hidden="true" />
{query.label}
</button>
))}
</div>
<div
className="console-body"
id="decision-result"
role="tabpanel"
aria-labelledby={`tab-${active.id}`}
key={active.id}
>
<div className="query-pane">
<div className="console-label">
<span>AGENT QUERY</span>
<span>EXAMPLE</span>
</div>
<blockquote>“{active.question}”</blockquote>
<div className="query-route">
<span>search_artifacts</span>
<span aria-hidden="true">→</span>
<span>get_artifact</span>
<span aria-hidden="true">→</span>
<span>get_related</span>
</div>
</div>
<div className="result-pane">
<div className="console-label">
<span>CITED RESULT</span>
<span className="verified">VALID RECORD</span>
</div>
<p className="artifact-id">{active.artifact}</p>
<h3>{active.title}</h3>
<p className="artifact-answer">{active.answer}</p>
<p className="artifact-related">
Related <span>{active.related}</span>
</p>
</div>
</div>
<div className="console-footer">
<span>NO EMBEDDINGS</span>
<span>NO MODEL CALL</span>
<span>READ-ONLY MCP</span>
<span>DETERMINISTIC RESULT</span>
</div>
</div>
);
}
Loading
Loading