Glaze Companion Design (Exploration)
Objective
Decide whether Glaze (https://www.glaze.app/, Raycast's AI desktop-app builder for macOS) can benefit this project, and — for the one admissible integration — specify it precisely enough that building it cannot blur the fpf_reference interface contract.
Verdict
- Glaze is admissible only as an optional, read-only, native macOS client over the hosted public
fpf_reference MCP surface. It is one more consumer of the published interface contract, exactly like Codex or Claude Code.
- Glaze brings no value to the core: the deterministic vectorless runtime, the publication pipeline, and the agent-facing MCP surface. Glaze generates apps with a non-deterministic LLM step; the publication path here is deterministic and evidence-audited. A generated app may consume the contract; it must never participate in producing published artifacts.
- Priority: opportunistic, low. Nothing in this repo's runtime, CI, or published surfaces may depend on the companion existing.
Context
The hosted MCP endpoint and the static reference site already serve the two primary audiences (agents and humans in a browser). A native macOS companion is a plausible third client surface: menu-bar quick lookup, offline reading of already-fetched pages, and a windowed reader for people who live outside the browser. Glaze makes that buildable from a single prompt without adding Swift to anyone's plate, and its store gives a small distribution channel.
Glaze constraints as of this design (2026-07-07): macOS Tahoe + Apple Silicon only; apps are local-first and run without a server; apps can call external APIs and AI-model integrations; distribution is unlisted sharing or the public Glaze Store.
FPF Grounding
ask_fpf on the production runtime routed this exact decision ("external client surface as optional read-only consumer of a published interface contract") to route:boundary-unpacking, ordered entry A.6 → A.6.B → A.6.C, confidence 0.92. The route's acceptance check: mixed boundary statements get stable claim IDs in a claim register, and each atomic claim routes to one owner layer. The claim register below is this spec's implementation of that check.
Repo QA anchors reused: B.5.1 (this doc is exploration/shaping, separated from operation), A.10/G.6 (snapshot hash and builtAt as the evidence spine), E.19 (explicit, separate quality gates), C.24 (bounded tool-call behavior for the client).
The runtime snapshot used throughout this design: sha256:f916341a8b3711b847c8a701f8b71e0ee155d9e5517be3ed36363fa6621ccf2b, built 2026-07-03T21:18:14Z, fresh: true — identical hash reported by get_fpf_index_status and echoed in every payload captured in the Evidence section.
Claim Register
Design
Working name: FPF Companion. One dependency: the hosted fpf_reference MCP endpoint (the dated value is quoted once, in the build prompt below, under its SSOT caveat). Shape: a menu-bar quick-search plus a main window with five surfaces.
Status (Freshness Spine)
Maps get_fpf_index_status directly: a pill in the sidebar footer is green when the GLZ-3 gate passes, amber otherwise. The detail view lists sourcePath, sourceHash, builtAt, compilerMode: local_vectorless, and the artifact booleans. The pill's state gates the trust labeling on every other surface.
Ask
Maps ask_fpf — the human-facing twin of query_fpf_spec, whose structured envelope stays deliberately unused in this app. The app always sends mode explicitly (compact unless the user toggles verbose/proof): the server-side default is deployment-configured (FPF_QUERY_DEFAULT_MODE, verbose in production), so omitting mode must not be relied on for the quick-answer UX. Renders the markdown answer; ids as chips that open the Reader; a grounding footer (confidence, status, snapshot short-hash, builtAt) with citations, constraints, and gaps listed beneath it. status enum handling: ok renders normally; not_found offers Search; ambiguous renders candidateIds as suggestion chips; unsupported explains the bound; stale_snapshot_prevented triggers the stale banner and a status re-check.
Search
Maps search_fpf. Hit rows show kind badge (pattern / route / lexeme / preface), title, id, part, status, score, and the snippet; selecting a hit opens the Reader. Optional kind filter mirrors the schema enum.
Catalog
Maps browse_fpf_catalog with part / status / kind filters, paged by nextOffset, honoring didYouMean.part for near-miss filters.
Reader
Maps read_fpf_doc. Hover cards use mode: "preview" (headings + snippet, no body). Full pages request maxChars: 20000 first and offer "load full page" when truncated: true (markdownChars tells the user the full size). Header shows title, resolved nodeId, and an "open on fpf.sh" link derived from docRef.staticPath. Typing an ID (A.1.1) or route (route:project-alignment) anywhere jumps straight here via selector auto-resolution.
Offline Behavior
Local-first apps keep working offline: cached pages stay readable but are labeled with their snapshot short-hash plus "offline — freshness unverified", and the GLZ-3 gate re-runs on reconnect.
Glaze Build Prompt
The operational artifact — paste into Glaze as-is. One caveat first: the endpoint and server name below are quoted as of the design date; their SSOT is src/core/public-copy.ts (surfaced at mcp.fpf.sh and the Connect MCP page), and this dated exploration page sits outside the adoption-copy parity tests. Before pasting, confirm both values still match the SSOT — if they have moved, this prompt is stale, not authoritative.
Build a macOS app called "FPF Companion": a read-only reader and lookup client
for the First Principles Framework reference, backed by exactly one external
service, the hosted MCP endpoint:
https://mcp.fpf.sh/api/mcp/fpf_reference/mcp (server name: fpf_reference)
IMPORTANT — runtime transport: Glaze's own MCP integration is build-time
only (it equips the build agent, not the shipped app). The app itself must
implement a plain HTTP client for the endpoint above using the MCP
Streamable HTTP transport, with the full lifecycle:
1. every request sends header "Accept: application/json, text/event-stream";
2. POST a JSON-RPC 2.0 initialize request whose params include
protocolVersion ("2025-06-18"), capabilities ({} suffices for this
read-only client), and clientInfo ({ name: "fpf-companion", version });
then POST the notifications/initialized notification;
3. after initialize, send header "MCP-Protocol-Version: <the version
returned in the initialize result>" (currently 2025-06-18) on every
subsequent request;
4. if any response returns an Mcp-Session-Id header, echo it as a header
on every subsequent request;
5. then POST tools/call requests with the tool name and arguments; each
response arrives as Content-Type application/json (parse directly) or
text/event-stream (read the SSE stream until the JSON-RPC message whose
id matches the request id; earlier events may be notifications or
server requests — answer a ping request with an empty result, answer
any other server request with JSON-RPC error -32601 (method not
found), and skip only notifications the app does not handle) —
support both, as the Accept header promises;
6. handle failures without crashing: a JSON-RPC response may carry an
"error" member instead of "result" — surface its message in the UI as
a readable error state; an HTTP 404 received while sending
Mcp-Session-Id means the session expired — discard the session id and
re-run the initialize flow once before concluding anything is wrong;
other HTTP failures and timeouts (use a 15 s request timeout and at
most one retry, consistent with the bounded-request rule) switch the
app to the offline state of behavior 6, keeping cached content
readable and labeled.
Where this recipe is silent, follow the official MCP Streamable HTTP
transport and lifecycle spec (modelcontextprotocol.io, revision
2025-06-18) — it wins over any shortcut a code generator might prefer. In every tools/call response, the tool payload lives in the
JSON-RPC envelope's result.structuredContent — NOT directly on result
(result.content is only a human-readable text fallback); read all fields
below from structuredContent. Do not depend on any Glaze MCP server
connection existing at runtime — if the generated app cannot reach the
endpoint with its own HTTP client, the build has failed its primary
acceptance test: the app must function with this endpoint as its only
network dependency.
Call only these five tools through that client, nothing else:
- get_fpf_index_status {} -> { sourcePath, sourceHash?, builtAt?,
snapshotExists, currentSourceHash, fresh, compilerMode, artifacts,
sessionCache }
(sourceHash and builtAt are OMITTED when no snapshot could be loaded —
exactly the degraded case; parse them as optional or the degraded render
itself breaks)
- search_fpf { query (<=1000 chars), kind?: pattern|route|lexeme|preface,
limit?: 1..100 } -> { hits: [{ id, kind, title, status?, part?, score,
snippet }], total, snapshot }
- browse_fpf_catalog { part?, status?, kind?, limit?: 1..500, offset? }
-> { entries: [{ id, kind, title, status?, part?, description }], total,
nextOffset?, didYouMean?, snapshot }
- ask_fpf { question (<=2000 chars), mode?: compact|verbose|proof }
-> { markdown, ids, citations, constraints, gaps, confidence (nullable),
candidateIds?, status, snapshot }
(candidateIds carries the structured suggestions rendered as chips when
status is "ambiguous" — never scrape them from the markdown. ALWAYS send
mode explicitly, "compact" unless the user toggles otherwise: the
server-side default is deployment-configured and is verbose in
production, so omitting mode gives slow verbose answers)
- read_fpf_doc { selector (<=256 chars), mode?: preview|full, maxChars? }
-> { status: ok|not_found, resolvedAs: id|route|lexeme|not_found,
title?, nodeId?, markdown?, markdownChars?, truncated?, headings?,
preview?, docRef?: { staticPath }, snapshot }
(on status "not_found" the content fields are absent — render the
Reader's not-found recovery state offering Search, never an empty page)
(The endpoint also exposes a sixth public tool, query_fpf_spec; it returns an
agent-facing structured envelope and is intentionally NOT used by this app —
do not call it or generate code for it.)
App shape: a menu-bar icon with a global-shortcut quick-search (top 5
search_fpf hits; Enter opens the reader; typing an exact ID like "A.1.1" or
"route:project-alignment" opens the reader directly), and a main window with
a sidebar: Ask, Search, Catalog, Reader, Status.
Non-negotiable behaviors:
1. Call get_fpf_index_status at launch, on manual refresh, after any
stale_snapshot_prevented answer, and on network reconnect — never on a
timer. Trusted state
requires snapshotExists && fresh && currentSourceHash === sourceHash.
Show a green "Fresh" pill when trusted, amber "Degraded" otherwise, and
label all content "unverified" in degraded state.
2. Every answer and page shows a footer with the response's own
snapshot.sourceHash (first 12 hex chars) and builtAt. If it differs from
the status hash, show a stale banner.
3. ask_fpf answers are grounded summaries. For exact spec wording always
fetch read_fpf_doc and say so in the UI ("Exact text").
4. Handle the answer status enum: ok; not_found (offer search); ambiguous
(render candidateIds as suggestions); unsupported (explain);
stale_snapshot_prevented (stale banner + status re-check).
5. Reader fetches use maxChars 20000 first; when truncated is true show
"Load full page (N chars)" and re-fetch with maxChars = min(N, 200000)
— maxChars must always stay within the schema bounds [100, 200000].
Preview/hover cards use mode "preview".
6. Cache responses keyed by (tool, arguments, sourceHash); drop the cache
when the status hash changes. NEVER cache get_fpf_index_status itself —
it is the invalidation oracle and always goes to the network. Offline:
cached pages stay readable, labeled "offline — freshness unverified"
with their snapshot hash.
7. Enforce input bounds client-side: question <= 2000 characters and never
empty, search query <= 1000 and never empty, selector <= 256 and never
empty, catalog part/status filters <= 64, maxChars within [100, 200000];
omit empty optional fields entirely instead of sending empty strings.
Debounce search by >= 300 ms (or fire on submit); page the catalog with
nextOffset, and when a browse response carries didYouMean.part, offer it
as a one-tap corrected filter; never poll.
8. Freshness means internal consistency of the deployed snapshot only —
never display any claim that upstream FPF is current.
9. The service name is fpf_reference. Never reference "fpf_memory" anywhere:
not in code, settings, copy, or the store listing. Any store or unlisted
listing must name fpf_reference and link https://fpf.sh/.
10. The app is read-only: no notes, no memory, no editing, no workflow
features. Link out to https://fpf.sh/ for the full site, and give
every Reader page an "open on fpf.sh" link built from its
docRef.staticPath.
Visual design: default native macOS look — standard system appearance,
system fonts, light and dark mode both supported. Do not invent a custom
brand palette. The only required visual elements are functional:
- Layout: left sidebar (Ask, Search, Catalog, Reader, Status) + content
pane; freshness pill at the sidebar bottom with the snapshot
short-hash beneath it; window footer with endpoint name, snapshot
short-hash, and freshness state.
- Semantic states: the freshness pill uses the platform's standard
positive color when trusted and warning color when degraded; the stale
banner uses the warning color. Kind badges (pattern / route / lexeme /
preface) are visually distinct from each other using system-appropriate
tints.
- Monospace for every FPF ID, snapshot hash, and payload value.
- Components: search hits as rows (kind badge, title + monospace ID,
part/status, one-line snippet, score); FPF IDs rendered as chips that
open the Reader; every answer ends with a grounding footer
(confidence · status · snapshot short-hash · builtAt), with the
answer's citations, constraints, and gaps listed beneath it; every
Reader page ends with a footer (status · snapshot short-hash ·
builtAt); the Status surface's detail view lists sourcePath,
sourceHash, builtAt, compilerMode, and the artifact booleans.
Evidence (Design-Time Payloads, Production Endpoint, 2026-07-07)
get_fpf_index_status:
{"sourceHash":"sha256:f916341a8b37…","builtAt":"2026-07-03T21:18:14Z",
"snapshotExists":true,"currentSourceHash":"sha256:f916341a8b37…",
"fresh":true,"compilerMode":"local_vectorless"}
(Abbreviated: hashes shortened, and the full payload also carried sourcePath, the eight artifact booleans (all true), and the sessionCache object — the always-present fields the prompt's schema declares.)
search_fpf { query: "bounded context", limit: 3 } → 1689 total; top hits lex:bounded-context (lexeme, score 160.8), A.1.1 "U.BoundedContext Semantic Frame" (pattern, Stable), A.2.5 (pattern, Stable) — same snapshot hash echoed.
browse_fpf_catalog { kind: "route", limit: 3 } → 3 routes total: route:boundary-unpacking, route:project-alignment, route:writing-or-reviewing-patterns, each with a task-oriented description.
ask_fpf (boundary question) → route:boundary-unpacking, IDs A.6, A.6.B, A.6.C (+ conditional A.6.P, C.16.Q, A.6.A), confidence 0.92, status ok.
Non-Goals
- No Glaze anywhere in the build/publish/sync path; the README scope list (no vector DB, no remote indexing, no LLM step in the docs pipeline) stays authoritative and untouched.
- No expert tools; no bundling of the local stdio runtime inside the app.
- No cross-platform promise — the website remains the canonical human surface; the companion is macOS-only by Glaze's own constraint.
- Not an FPF authoring or editing surface, and not a second source of adoption copy: endpoint/setup wording keeps its SSOT in
src/core/public-copy.ts.
Acceptance Checks (App-Level, Mirroring the Contract's)
- With outbound network restricted to
mcp.fpf.sh, all five surfaces function — proving the single-dependency boundary (GLZ-1).
- Simulating
fresh: false degrades every surface and labels all content unverified; simulating a response-vs-status hash mismatch renders the stale banner (GLZ-3, GLZ-7).
- A search of the generated app's source contains zero occurrences of
fpf_memory and zero references to the non-public tools (inspect_*, trace_fpf_path, expand_fpf_citations, refresh_fpf_index) (GLZ-2, GLZ-6).
- Every rendered answer/page shows its snapshot short-hash and
builtAt; in the nominal state the hash equals the status hash (GLZ-7).
- Any store or unlisted listing names
fpf_reference and links https://fpf.sh/; Glaze Store publication additionally ships only after explicit human approval (GLZ-6, GLZ-12).
Decision
Park as an optional exploration: the benefit is real but minor (a nicer native lookup surface plus a small discovery channel), the cost of a clean build is one prompt, and the boundary above keeps it from ever leaking into the core. If nobody wants the desktop experience, dropping this doc is the entire cost of exit.