From 45c46a53cd84f8e79e3bab437931cdeac9a3f895 Mon Sep 17 00:00:00 2001 From: Simon Heimlicher Date: Wed, 25 Feb 2026 16:05:49 +0100 Subject: [PATCH 1/3] fix: show hero text on mobile by overriding scroll-driven opacity On mobile the hero scroll container is shorter than the viewport, so Framer Motion's scrollYProgress reports ~1 and textFade becomes 0, making all supporting text invisible. Override with opacity: 1 via CSS since mobile doesn't use the scroll animation. --- src/app/globals.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/globals.css b/src/app/globals.css index 0127c0a..3c2ffe7 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -700,6 +700,12 @@ pre { padding: 0; } + /* Override Framer Motion inline opacity — on mobile the scroll container is + shorter than the viewport so scrollYProgress≈1 and textFade=0 */ + .hero-text-col > * { + opacity: 1 !important; + } + .hero-ground-line, .hero-scroll-hint, .hero-spec-overlay { From d030f10e2f95ae906756c3d317db81fdab2df6fd Mon Sep 17 00:00:00 2001 From: Simon Heimlicher Date: Thu, 26 Feb 2026 02:56:50 +0100 Subject: [PATCH 2/3] refactor: replace scroll-driven hero with static responsive layout Remove Framer Motion from hero section entirely. The scroll-driven animation was janky on desktop and caused invisible text on mobile. - Rewrite HeroSection as a server component (no client JS) - Remove HeroSpecTree overlay, ground line, scroll hint - Responsive two-column grid at >=768px, stacked on mobile - Tree visible at all breakpoints (was hidden on mobile) - Tighten SVG viewBox to eliminate whitespace around tree - Use clamp() for fluid h1 sizing across viewports --- src/app/globals.css | 199 +++++----------------------- src/components/HeroSection.tsx | 210 +++++++----------------------- src/components/HeroSpecTree.tsx | 115 ---------------- src/components/OutcomeTreeSVG.tsx | 4 +- 4 files changed, 84 insertions(+), 444 deletions(-) delete mode 100644 src/components/HeroSpecTree.tsx diff --git a/src/app/globals.css b/src/app/globals.css index 3c2ffe7..77458d1 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -505,211 +505,78 @@ pre { color: rgba(255, 255, 255, 0.2); } -/* ===== HERO (scroll-driven Variant F) ===== */ +/* ===== HERO ===== */ -.hero-scroll-container { - height: 200vh; +.hero { position: relative; background: linear-gradient(180deg, #0c1008 0%, #111a0e 40%, #0e150a 100%); -} - -.hero-sticky { - position: sticky; - top: 0; - height: 100vh; - overflow: hidden; - width: 100%; + padding: 2rem 1.5rem; } .hero-grid { display: grid; - grid-template-columns: 42% 58%; - grid-template-rows: 1fr; - height: 100%; + grid-template-columns: 1fr; + gap: 1rem; + max-width: 72rem; + margin: 0 auto; } .hero-tree-col { - grid-row: 1; - grid-column: 1; display: flex; align-items: center; justify-content: center; position: relative; - z-index: 2; } -.hero-text-col { - grid-row: 1; - grid-column: 2; - display: flex; - flex-direction: column; - justify-content: center; - padding: 2em 3em; +.hero-tree-col svg { + width: 80%; } -.hero-annotation { - position: absolute; - font-size: 0.75rem; - letter-spacing: 0.12em; - text-transform: uppercase; - font-weight: 500; - white-space: nowrap; -} - -.hero-ann-leaves { - top: 8%; - right: 0; - color: rgba(120, 185, 90, 0.7); -} - -.hero-ann-branches { - top: 40%; - right: -12px; - color: rgba(170, 150, 120, 0.7); -} - -.hero-ann-roots { - bottom: 10%; - left: 24px; - color: rgba(200, 170, 100, 0.75); -} - -.hero-ground-line { - grid-column: 1 / -1; - grid-row: 1; - align-self: center; - position: relative; - height: 2px; - background: linear-gradient( - 90deg, - transparent 3%, - rgba(200, 160, 60, 0.25) 20%, - rgba(200, 160, 60, 0.4) 50%, - rgba(200, 160, 60, 0.25) 80%, - transparent 97% - ); - z-index: 2; - pointer-events: none; -} - -.hero-ground-line span { - position: absolute; - left: 21%; - top: 0; - transform: translate(-50%, -140%); - font-size: 0.75rem; - letter-spacing: 0.14em; - text-transform: uppercase; - color: rgba(215, 180, 105, 0.7); - font-weight: 500; - white-space: nowrap; -} - -.hero-spec-overlay { - grid-column: 1 / -1; - grid-row: 1; - display: grid; - grid-template-columns: 42% 58%; - align-content: center; - z-index: 3; -} - -.hero-spec-inner { - grid-column: 2; - display: flex; - flex-direction: column; - justify-content: flex-start; - padding: 22vh 2em 1.5em 1em; - background: linear-gradient( - 180deg, - transparent 0%, - transparent 16vh, - rgb(14, 16, 12) 22vh, - rgb(14, 16, 12) 100% - ); -} - -.hero-scroll-hint { - position: absolute; - bottom: 2rem; - left: 50%; - transform: translateX(-50%); +.hero-text-col { display: flex; flex-direction: column; - align-items: center; - gap: 0.5rem; - z-index: 10; -} - -.hero-scroll-hint span { - font-size: 0.75rem; - letter-spacing: 0.14em; - text-transform: uppercase; - color: rgba(200, 200, 190, 0.45); - font-weight: 500; -} - -@keyframes bounceDown { - 0%, 100% { - transform: translateY(0); - } - 50% { - transform: translateY(4px); - } -} - -.hero-scroll-hint svg { - animation: bounceDown 2s ease-in-out infinite; - color: rgba(200, 200, 190, 0.25); + justify-content: center; } .hero-h1 { text-align: left; + font-size: clamp(2rem, 5vw, 3.2rem); } +/* Two-column layout */ @media (min-width: 768px) { - .hero-h1 { - text-align: right; - } -} - -/* Mobile: single column, no scroll animation */ -@media (max-width: 767px) { - .hero-scroll-container { - height: auto; - background: linear-gradient(180deg, #0c1008 0%, #0e150a 100%); - } - - .hero-sticky { - position: relative; - height: auto; - padding: 3rem 1.5rem; + .hero { + padding: 0; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; } .hero-grid { - grid-template-columns: 1fr; - height: auto; - gap: 2rem; + grid-template-columns: 42% 58%; + grid-template-rows: 1fr; + gap: 0; + min-height: 100vh; + width: 100%; + max-width: none; } .hero-tree-col { - display: none; + z-index: 2; } - .hero-text-col { - padding: 0; + .hero-tree-col svg { + width: auto; + max-height: 85%; } - /* Override Framer Motion inline opacity — on mobile the scroll container is - shorter than the viewport so scrollYProgress≈1 and textFade=0 */ - .hero-text-col > * { - opacity: 1 !important; + .hero-text-col { + padding: 2em 3em; } - .hero-ground-line, - .hero-scroll-hint, - .hero-spec-overlay { - display: none; + .hero-h1 { + text-align: right; } } diff --git a/src/components/HeroSection.tsx b/src/components/HeroSection.tsx index 1c35a7a..2b6d612 100644 --- a/src/components/HeroSection.tsx +++ b/src/components/HeroSection.tsx @@ -1,171 +1,59 @@ -"use client"; - -import HeroSpecTree from "@/components/HeroSpecTree"; import OutcomeTreeSVG from "@/components/OutcomeTreeSVG"; -import { motion, useScroll, useTransform } from "framer-motion"; -import { useCallback, useEffect, useRef, useState } from "react"; - -function useAnimationTargets() { - const h1Ref = useRef(null); - const treeColRef = useRef(null); - const textColRef = useRef(null); - const [targets, setTargets] = useState({ liftPx: 0, expandPx: 0 }); - - const compute = useCallback(() => { - const h1 = h1Ref.current; - const treeCol = treeColRef.current; - const textCol = textColRef.current; - if (!h1 || !treeCol || !textCol) return; - - const h1Rect = h1.getBoundingClientRect(); - const treeRect = treeCol.getBoundingClientRect(); - const targetY = treeRect.top + treeRect.height * 0.05; - const liftPx = targetY - h1Rect.top; - - const textPadLeft = parseFloat(getComputedStyle(textCol).paddingLeft); - const expandPx = treeRect.width + textPadLeft; - - setTargets({ liftPx, expandPx }); - }, []); - - useEffect(() => { - const raf1 = requestAnimationFrame(() => { - requestAnimationFrame(compute); - }); - - const observer = new ResizeObserver(compute); - if (h1Ref.current) observer.observe(h1Ref.current); - if (treeColRef.current) observer.observe(treeColRef.current); - - return () => { - cancelAnimationFrame(raf1); - observer.disconnect(); - }; - }, [compute]); - - return { h1Ref, treeColRef, textColRef, targets }; -} export default function HeroSection() { - const containerRef = useRef(null); - const { scrollYProgress } = useScroll({ - target: containerRef, - offset: ["start start", "end end"], - }); - const { h1Ref, treeColRef, textColRef, targets } = useAnimationTargets(); - - // Map 0-0.85 scroll range to 0-1 transition progress - const t = useTransform(scrollYProgress, [0, 0.85], [0, 1], { clamp: true }); - - // Animated values - const textFade = useTransform(t, [0, 1], [1, 0]); - const annotationFade = useTransform(t, [0, 1], [1, 0]); - const specTreeY = useTransform(t, (v) => `${(1 - v) * 100}vh`); - const groundFade = useTransform(t, [0, 1], [0, 1]); - const scrollHintFade = useTransform(scrollYProgress, [0, 0.05], [1, 0]); - - // H1 expansion and text panel lift (depend on measured DOM values) - const textLiftY = useTransform(t, (v) => v * targets.liftPx); - const h1MarginLeft = useTransform(t, (v) => v * -targets.expandPx); - const h1Width = useTransform(t, (v) => `calc(100% + ${v * targets.expandPx}px)`); - return ( -
-
-
- {/* Left column: SVG tree + annotations */} -
- - - Thriving Outcomes - - - Outcome Hypotheses - - - Goals & Understanding - -
+
+
+ {/* Tree column: SVG tree + annotations */} +
+ +
- {/* Right column: Narrative text */} - - - Outcome Engineering - - - Rooted in understanding, -
- branching toward -
- outcomes -
- -

- Start with business goals and customer insight at the roots, express outcome hypotheses as testable - specs, and let the structure prove what works. -

-
-
-
- Roots -
-
- Business goals and deep customer understanding form the foundation. -
-
-
-
- Branches -
-
- Outcome hypotheses — structured, testable paths forward. -
-
-
-
- Leaves -
-
- Thriving outcomes that prove the structure works. -
-
+ {/* Text column */} +
+
+ Outcome Engineering +
+

+ Rooted in understanding, +
+ branching toward +
+ outcomes +

+

+ Start with business goals and customer insight at the roots, express outcome hypotheses as testable specs, + and let the structure prove what works. +

+
+
+
Roots
+
+ Business goals and deep customer understanding form the foundation.
- - View on GitHub → - - - - - {/* Ground line — inside grid so z-index is comparable with overlay */} - - Outcome Hypothesis - - - {/* Spec tree overlay: slides up from bottom */} - - - +
+
+
Branches
+
+ Outcome hypotheses — structured, testable paths forward. +
+
+
+
Leaves
+
+ Thriving outcomes that prove the structure works. +
+
+
+ + View on GitHub → +
- - {/* Scroll hint */} - - Scroll - - - -
); diff --git a/src/components/HeroSpecTree.tsx b/src/components/HeroSpecTree.tsx deleted file mode 100644 index 814eafd..0000000 --- a/src/components/HeroSpecTree.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/** Spec tree directory listing for the hero overlay. */ - -interface TreeEntry { - chrome: string; - name: string; - cls?: string; - status?: string; - statusCls?: string; -} - -const entries: TreeEntry[] = [ - { chrome: "", name: "spx/", cls: "st-product" }, - { chrome: "\u251C\u2500\u2500 ", name: "spx-cli.product.md", cls: "st-product" }, - { chrome: "\u251C\u2500\u2500 ", name: "15-cli-framework.adr.md", cls: "st-decision" }, - { - chrome: "\u251C\u2500\u2500 ", - name: "21-test-harness.enabler/", - cls: "st-enabler", - status: "\u25CF valid", - statusCls: "st-s-valid", - }, - { chrome: "\u2502 \u251C\u2500\u2500 ", name: "test-harness.md" }, - { chrome: "\u2502 \u251C\u2500\u2500 ", name: "tests/" }, - { chrome: "\u2502 \u2514\u2500\u2500 ", name: "spx-lock.yaml" }, - { - chrome: "\u251C\u2500\u2500 ", - name: "32-parse-directory-tree.enabler/", - cls: "st-enabler", - status: "\u25CF valid", - statusCls: "st-s-valid", - }, - { - chrome: "\u251C\u2500\u2500 ", - name: "43-node-status.enabler/", - cls: "st-enabler", - status: "\u25CF valid", - statusCls: "st-s-valid", - }, - { - chrome: "\u251C\u2500\u2500 ", - name: "54-spx-tree-interpretation.outcome/", - cls: "st-outcome", - status: "\u25D0 stale", - statusCls: "st-s-stale", - }, - { chrome: "\u2502 \u251C\u2500\u2500 ", name: "spx-tree-interpretation.md" }, - { - chrome: "\u2502 \u251C\u2500\u2500 ", - name: "21-parent-child-links.enabler/", - cls: "st-enabler", - status: "\u25CF valid", - statusCls: "st-s-valid", - }, - { - chrome: "\u2502 \u251C\u2500\u2500 ", - name: "43-status-rollup.outcome/", - cls: "st-outcome", - status: "\u25D0 stale", - statusCls: "st-s-stale", - }, - { - chrome: "\u2502 \u2514\u2500\u2500 ", - name: "54-spx-tree-status.outcome/", - cls: "st-outcome", - status: "\u25CB needs work", - statusCls: "st-s-needs", - }, - { - chrome: "\u251C\u2500\u2500 ", - name: "76-cli-integration.outcome/", - cls: "st-outcome", - status: "\u25CF valid", - statusCls: "st-s-valid", - }, - { - chrome: "\u2514\u2500\u2500 ", - name: "87-e2e-workflow.outcome/", - cls: "st-outcome", - status: "\u25CB needs work", - statusCls: "st-s-needs", - }, -]; - -export default function HeroSpecTree() { - return ( -
-
- The Spec Tree -
-
- {entries.map((e, i) => ( -
- {e.chrome} - {e.name} - {e.status && {e.status}} -
- ))} -
-
-
- - Valid -
-
- - Stale -
-
- - Needs work -
-
-
- ); -} diff --git a/src/components/OutcomeTreeSVG.tsx b/src/components/OutcomeTreeSVG.tsx index ed383aa..8b88ddd 100644 --- a/src/components/OutcomeTreeSVG.tsx +++ b/src/components/OutcomeTreeSVG.tsx @@ -378,11 +378,11 @@ export default function OutcomeTreeSVG({ className, variant = "split" }: Outcome if (variant === "full") { return ( Date: Thu, 26 Feb 2026 02:57:07 +0100 Subject: [PATCH 3/3] refactor: remove spx- prefix from outcome names to save horizontal space Shorten directory and file names in all example spec trees across the landing page, blog, docs, and concept variation assets. --- .../e-dual-visualization.html | 6 ++-- .../f-scroll-driven-hero.html | 14 ++++---- assets/concept-variations/hero-proposals.html | 20 +++++------ content/blog/growing-a-spec-tree.mdx | 36 +++++++++---------- mintlify/guide/deterministic-context.mdx | 8 ++--- mintlify/guide/operational-loop.mdx | 14 ++++---- mintlify/guide/spec-tree.mdx | 6 ++-- mintlify/index.mdx | 4 +-- mintlify/reference/context-injection.mdx | 22 ++++++------ mintlify/reference/filesystem.mdx | 3 +- mintlify/reference/spx-cli.mdx | 8 ++--- src/components/ContextInjectionSection.tsx | 6 ++-- src/components/OperationalLoopSection.tsx | 14 ++++---- src/components/SolutionSection.tsx | 4 +-- src/lib/spec-tree-data.ts | 16 ++++----- src/lib/story-data.tsx | 18 +++++----- 16 files changed, 100 insertions(+), 99 deletions(-) diff --git a/assets/concept-variations/e-dual-visualization.html b/assets/concept-variations/e-dual-visualization.html index e3ba402..a803b73 100644 --- a/assets/concept-variations/e-dual-visualization.html +++ b/assets/concept-variations/e-dual-visualization.html @@ -288,11 +288,11 @@
│ └── spx-lock.yaml
├── 32-parse-directory-tree.enabler/● valid
├── 43-node-status.enabler/● valid
-
├── 54-spx-tree-interpretation.outcome/◐ stale
-
│ ├── spx-tree-interpretation.md
+
├── 54-tree-interpretation.outcome/◐ stale
+
│ ├── tree-interpretation.md
│ ├── 21-parent-child-links.enabler/● valid
│ ├── 43-status-rollup.outcome/◐ stale
-
│ └── 54-spx-tree-status.outcome/○ needs work
+
│ └── 54-tree-status.outcome/○ needs work
├── 76-cli-integration.outcome/● valid
└── 87-e2e-workflow.outcome/○ needs work
diff --git a/assets/concept-variations/f-scroll-driven-hero.html b/assets/concept-variations/f-scroll-driven-hero.html index 9783274..e0b3e24 100644 --- a/assets/concept-variations/f-scroll-driven-hero.html +++ b/assets/concept-variations/f-scroll-driven-hero.html @@ -409,11 +409,11 @@

Rooted in understanding,
branching toward
│ └── spx-lock.yaml
├── 32-parse-directory-tree.enabler/● valid
├── 43-node-status.enabler/● valid
-
├── 54-spx-tree-interpretation.outcome/◐ stale
-
│ ├── spx-tree-interpretation.md
+
├── 54-tree-interpretation.outcome/◐ stale
+
│ ├── tree-interpretation.md
│ ├── 21-parent-child-links.enabler/● valid
│ ├── 43-status-rollup.outcome/◐ stale
-
│ └── 54-spx-tree-status.outcome/○ needs work
+
│ └── 54-tree-status.outcome/○ needs work
├── 76-cli-integration.outcome/● valid
└── 87-e2e-workflow.outcome/○ needs work

@@ -471,7 +471,7 @@

Decision records capture choices upfront

├── 21-test-harness.enabler/ ● valid
├── 32-parse-directory-tree.enabler/ ● valid
├── 43-node-status.enabler/ ● valid
-
├── 54-spx-tree-interpretation.outcome/
+
├── 54-tree-interpretation.outcome/
└── ...
@@ -490,11 +490,11 @@

Outcomes express testable hypotheses

spx/
├── ...
-
├── 54-spx-tree-interpretation.outcome/ ◐ stale
-
│ ├── spx-tree-interpretation.md
+
├── 54-tree-interpretation.outcome/ ◐ stale
+
│ ├── tree-interpretation.md
│ ├── 21-parent-child-links.enabler/ ● valid
│ ├── 43-status-rollup.outcome/ ◐ stale
-
│ └── 54-spx-tree-status.outcome/ ○ needs work
+
│ └── 54-tree-status.outcome/ ○ needs work
└── ...
diff --git a/assets/concept-variations/hero-proposals.html b/assets/concept-variations/hero-proposals.html index 10e2286..d695a1a 100644 --- a/assets/concept-variations/hero-proposals.html +++ b/assets/concept-variations/hero-proposals.html @@ -1383,11 +1383,11 @@

D — Cinematic Emergence

│ └── spx-lock.yaml
├── 32-parse-directory-tree.enabler/● valid
├── 43-node-status.enabler/● valid
-
├── 54-spx-tree-interpretation.outcome/◐ stale
-
│ ├── spx-tree-interpretation.md
+
├── 54-tree-interpretation.outcome/◐ stale
+
│ ├── tree-interpretation.md
│ ├── 21-parent-child-links.enabler/● valid
│ ├── 43-status-rollup.outcome/◐ stale
-
│ └── 54-spx-tree-status.outcome/○ needs work
+
│ └── 54-tree-status.outcome/○ needs work
├── 76-cli-integration.outcome/● valid
└── 87-e2e-workflow.outcome/○ needs work
@@ -1464,11 +1464,11 @@

Rooted in understanding,
branching toward
│ └── spx-lock.yaml
├── 32-parse-directory-tree.enabler/● valid
├── 43-node-status.enabler/● valid
-
├── 54-spx-tree-interpretation.outcome/◐ stale
-
│ ├── spx-tree-interpretation.md
+
├── 54-tree-interpretation.outcome/◐ stale
+
│ ├── tree-interpretation.md
│ ├── 21-parent-child-links.enabler/● valid
│ ├── 43-status-rollup.outcome/◐ stale
-
│ └── 54-spx-tree-status.outcome/○ needs work
+
│ └── 54-tree-status.outcome/○ needs work
├── 76-cli-integration.outcome/● valid
└── 87-e2e-workflow.outcome/○ needs work
@@ -1526,7 +1526,7 @@

Decision records capture choices upfront

├── 21-test-harness.enabler/ ● valid
├── 32-parse-directory-tree.enabler/ ● valid
├── 43-node-status.enabler/ ● valid
-
├── 54-spx-tree-interpretation.outcome/
+
├── 54-tree-interpretation.outcome/
└── ...
@@ -1545,11 +1545,11 @@

Outcomes express testable hypotheses

spx/
├── ...
-
├── 54-spx-tree-interpretation.outcome/ ◐ stale
-
│ ├── spx-tree-interpretation.md
+
├── 54-tree-interpretation.outcome/ ◐ stale
+
│ ├── tree-interpretation.md
│ ├── 21-parent-child-links.enabler/ ● valid
│ ├── 43-status-rollup.outcome/ ◐ stale
-
│ └── 54-spx-tree-status.outcome/ ○ needs work
+
│ └── 54-tree-status.outcome/ ○ needs work
└── ...
diff --git a/content/blog/growing-a-spec-tree.mdx b/content/blog/growing-a-spec-tree.mdx index 1db40d7..727b970 100644 --- a/content/blog/growing-a-spec-tree.mdx +++ b/content/blog/growing-a-spec-tree.mdx @@ -136,8 +136,8 @@ spx/ │ │ └── node-state-machine.unit.test.ts │ └── tests/ │ └── node-status.unit.test.ts -├── 54-spx-tree-interpretation.outcome/ -│ ├── spx-tree-interpretation.md +├── 54-tree-interpretation.outcome/ +│ ├── tree-interpretation.md │ ├── 21-parent-child-links.enabler/ │ │ ├── parent-child-links.md │ │ └── tests/ @@ -146,12 +146,12 @@ spx/ │ │ ├── status-rollup.md │ │ └── tests/ │ │ └── status.unit.test.ts -│ ├── 54-spx-tree-status.outcome/ +│ ├── 54-tree-status.outcome/ │ │ ├── spx-tree-status.md │ │ └── tests/ │ │ └── spx-tree-status.unit.test.ts │ └── tests/ -│ └── spx-tree-interpretation.unit.test.ts +│ └── tree-interpretation.unit.test.ts ├── 76-cli-integration.outcome/ │ ├── cli-integration.md │ └── tests/ @@ -169,7 +169,7 @@ spx/ └── AGENTS.md ``` -**Enabler nodes** (`.enabler` suffix) exist to serve other nodes — infrastructure that would be removed if all its dependents were retired. **Outcome nodes** (`.outcome` suffix) each express one hypothesis and the testable assertions that define its output. Position in the tree implies scope: `21-test-harness.enabler/` at the root level serves all nodes in the product; `21-parent-child-links.enabler/` nested inside `54-spx-tree-interpretation.outcome/` serves only its sibling nodes and their descendants. The spec file inside each node is `{slug}.md` — no type suffix, no numeric prefix. Enabler specs start with `## Enables`; outcome specs with `## Outcome`. +**Enabler nodes** (`.enabler` suffix) exist to serve other nodes — infrastructure that would be removed if all its dependents were retired. **Outcome nodes** (`.outcome` suffix) each express one hypothesis and the testable assertions that define its output. Position in the tree implies scope: `21-test-harness.enabler/` at the root level serves all nodes in the product; `21-parent-child-links.enabler/` nested inside `54-tree-interpretation.outcome/` serves only its sibling nodes and their descendants. The spec file inside each node is `{slug}.md` — no type suffix, no numeric prefix. Enabler specs start with `## Enables`; outcome specs with `## Outcome`. When a behavior spans multiple nodes, the assertion lives in the lowest common ancestor. The ancestor's spec captures cross-cutting behaviors; child nodes handle their local concerns. If an ancestor accumulates too many cross-cutting assertions, that is a signal to extract a shared enabler at a lower index. @@ -190,7 +190,7 @@ No sibling at the root level is affected. Growth is bounded: a new node touches ## What a node looks like -The spec at `54-spx-tree-interpretation.outcome/43-status-rollup.outcome/status-rollup.md`: +The spec at `54-tree-interpretation.outcome/43-status-rollup.outcome/status-rollup.md`: ```markdown ## Outcome @@ -208,7 +208,7 @@ Every assertion links to the test file meant to prove it — the `spx` CLI parse Numeric prefixes encode dependency scope. Within each directory, a lower-index item provides context to every sibling at a higher index — and to that sibling's descendants. Decisions constrain; enablers provide infrastructure; all are part of the deterministic context for higher-index nodes. -The lock file at `54-spx-tree-interpretation.outcome/43-status-rollup.outcome/spx-lock.yaml`: +The lock file at `54-tree-interpretation.outcome/43-status-rollup.outcome/spx-lock.yaml`: ```yaml schema: spx-lock/v1 @@ -230,7 +230,7 @@ Each node's lock file tracks only its own spec and tests. Subtree validity is ch In most agentic workflows, context is assembled heuristically — search, embeddings, tool-use defaults. The selection is hard to review and unstable as the repo grows. The Spec Tree enables deterministic context injection: the `spx` CLI walks the tree from the product level down to the target node and applies one structural rule: at each directory along the path, inject all lower-index siblings' specs. Ancestor specs along the path are always included. Test files are excluded. -If an agent is assigned `54-spx-tree-interpretation.outcome/43-status-rollup.outcome`, deterministic context injection provides: +If an agent is assigned `54-tree-interpretation.outcome/43-status-rollup.outcome`, deterministic context injection provides: ```text spx/ @@ -244,14 +244,14 @@ spx/ parse-directory-tree.md ← index 32 < 54 43-node-status.enabler/ node-status.md ← index 43 < 54 - 54-spx-tree-interpretation.outcome/ - spx-tree-interpretation.md ← ancestor spec + 54-tree-interpretation.outcome/ + tree-interpretation.md ← ancestor spec 21-parent-child-links.enabler/ parent-child-links.md ← index 21 < 43 43-status-rollup.outcome/ [TARGET] status-rollup.md ← target spec tests/ — ignored - 54-spx-tree-status.outcome/ — index 54 ≥ 43 + 54-tree-status.outcome/ — index 54 ≥ 43 76-cli-integration.outcome/ — index 76 ≥ 54 87-e2e-workflow.outcome/ — index 87 ≥ 54 ``` @@ -269,23 +269,23 @@ The tree is designed for tree filesystem browsers — VS Code, Neovim (neo-tree, Three commands form the core loop: ```text -$ spx status --tree 54-spx-tree-interpretation.outcome -54-spx-tree-interpretation.outcome/ needs work (no lock file) +$ spx status --tree 54-tree-interpretation.outcome +54-tree-interpretation.outcome/ needs work (no lock file) 21-parent-child-links.enabler/ valid 43-status-rollup.outcome/ stale status-rollup.md changed (was a3b7c12, now 5e9f1d8) - 54-spx-tree-status.outcome/ needs work (no lock file) + 54-tree-status.outcome/ needs work (no lock file) -$ spx lock 54-spx-tree-interpretation.outcome/43-status-rollup.outcome +$ spx lock 54-tree-interpretation.outcome/43-status-rollup.outcome Running tests... tests/status.unit.test.ts 3 passed Lock regenerated: 43-status-rollup.outcome/spx-lock.yaml -$ spx verify --tree 54-spx-tree-interpretation.outcome -54-spx-tree-interpretation.outcome/ needs work +$ spx verify --tree 54-tree-interpretation.outcome +54-tree-interpretation.outcome/ needs work 21-parent-child-links.enabler/ valid 43-status-rollup.outcome/ valid - 54-spx-tree-status.outcome/ needs work + 54-tree-status.outcome/ needs work ``` - `spx status` reads the tree and shows node states without running tests. diff --git a/mintlify/guide/deterministic-context.mdx b/mintlify/guide/deterministic-context.mdx index 54cd386..88fa6b3 100644 --- a/mintlify/guide/deterministic-context.mdx +++ b/mintlify/guide/deterministic-context.mdx @@ -11,7 +11,7 @@ The `spx` CLI walks the tree from the product level down to the target node and ## Worked example -If an agent is assigned `54-spx-tree-interpretation.outcome/43-status-rollup.outcome`: +If an agent is assigned `54-tree-interpretation.outcome/43-status-rollup.outcome`: ```text spx/ @@ -25,14 +25,14 @@ spx/ parse-directory-tree.md ← index 32 < 54 43-node-status.enabler/ node-status.md ← index 43 < 54 - 54-spx-tree-interpretation.outcome/ - spx-tree-interpretation.md ← ancestor spec + 54-tree-interpretation.outcome/ + tree-interpretation.md ← ancestor spec 21-parent-child-links.enabler/ parent-child-links.md ← index 21 < 43 43-status-rollup.outcome/ [TARGET] status-rollup.md ← target spec tests/ — ignored - 54-spx-tree-status.outcome/ — index 54 ≥ 43 + 54-tree-status.outcome/ — index 54 ≥ 43 76-cli-integration.outcome/ — index 76 ≥ 54 87-e2e-workflow.outcome/ — index 87 ≥ 54 ``` diff --git a/mintlify/guide/operational-loop.mdx b/mintlify/guide/operational-loop.mdx index 53cabb4..29877f4 100644 --- a/mintlify/guide/operational-loop.mdx +++ b/mintlify/guide/operational-loop.mdx @@ -6,23 +6,23 @@ description: "Three commands form the core loop: spx status, spx lock, spx verif ## The commands ```text -$ spx status --tree 54-spx-tree-interpretation.outcome -54-spx-tree-interpretation.outcome/ needs work (no lock file) +$ spx status --tree 54-tree-interpretation.outcome +54-tree-interpretation.outcome/ needs work (no lock file) 21-parent-child-links.enabler/ valid 43-status-rollup.outcome/ stale status-rollup.md changed (was a3b7c12, now 5e9f1d8) - 54-spx-tree-status.outcome/ needs work (no lock file) + 54-tree-status.outcome/ needs work (no lock file) -$ spx lock 54-spx-tree-interpretation.outcome/43-status-rollup.outcome +$ spx lock 54-tree-interpretation.outcome/43-status-rollup.outcome Running tests... tests/status.unit.test.ts 3 passed Lock regenerated: 43-status-rollup.outcome/spx-lock.yaml -$ spx verify --tree 54-spx-tree-interpretation.outcome -54-spx-tree-interpretation.outcome/ needs work +$ spx verify --tree 54-tree-interpretation.outcome +54-tree-interpretation.outcome/ needs work 21-parent-child-links.enabler/ valid 43-status-rollup.outcome/ valid - 54-spx-tree-status.outcome/ needs work + 54-tree-status.outcome/ needs work ``` ## What each command does diff --git a/mintlify/guide/spec-tree.mdx b/mintlify/guide/spec-tree.mdx index c18d053..81147e5 100644 --- a/mintlify/guide/spec-tree.mdx +++ b/mintlify/guide/spec-tree.mdx @@ -30,11 +30,11 @@ spx/ │ ├── test-harness.md │ └── tests/ │ └── test-harness.unit.test.ts -├── 54-spx-tree-interpretation.outcome/ -│ ├── spx-tree-interpretation.md +├── 54-tree-interpretation.outcome/ +│ ├── tree-interpretation.md │ ├── 21-parent-child-links.enabler/ │ ├── 43-status-rollup.outcome/ -│ └── 54-spx-tree-status.outcome/ +│ └── 54-tree-status.outcome/ ├── 76-cli-integration.outcome/ │ └── cli-integration.md └── 87-e2e-workflow.outcome/ diff --git a/mintlify/index.mdx b/mintlify/index.mdx index fc228ad..33daa4a 100644 --- a/mintlify/index.mdx +++ b/mintlify/index.mdx @@ -27,8 +27,8 @@ spx/ │ ├── test-harness.md │ └── tests/ │ └── test-harness.unit.test.ts -├── 54-spx-tree-interpretation.outcome/ -│ ├── spx-tree-interpretation.md +├── 54-tree-interpretation.outcome/ +│ ├── tree-interpretation.md │ ├── 21-parent-child-links.enabler/ │ └── 43-status-rollup.outcome/ └── 76-cli-integration.outcome/ diff --git a/mintlify/reference/context-injection.mdx b/mintlify/reference/context-injection.mdx index 1bd1583..b302376 100644 --- a/mintlify/reference/context-injection.mdx +++ b/mintlify/reference/context-injection.mdx @@ -14,18 +14,18 @@ The `spx` CLI walks the tree from the product level down to the target node. At ## What gets included -| Item | Included? | -| --- | --- | -| Product file (`*.product.md`) | Always | -| Ancestor specs along the path | Always | -| Lower-index sibling specs | Yes | -| Lower-index sibling decision records | Yes | -| Test files | Never | -| Higher-index sibling specs | Never | +| Item | Included? | +| ------------------------------------ | --------- | +| Product file (`*.product.md`) | Always | +| Ancestor specs along the path | Always | +| Lower-index sibling specs | Yes | +| Lower-index sibling decision records | Yes | +| Test files | Never | +| Higher-index sibling specs | Never | ## Example -For target `54-spx-tree-interpretation.outcome/43-status-rollup.outcome`: +For target `54-tree-interpretation.outcome/43-status-rollup.outcome`: ```text spx/ @@ -38,8 +38,8 @@ spx/ parse-directory-tree.md ← index 32 < 54 43-node-status.enabler/ node-status.md ← index 43 < 54 - 54-spx-tree-interpretation.outcome/ - spx-tree-interpretation.md ← ancestor + 54-tree-interpretation.outcome/ + tree-interpretation.md ← ancestor 21-parent-child-links.enabler/ parent-child-links.md ← index 21 < 43 43-status-rollup.outcome/ [TARGET] diff --git a/mintlify/reference/filesystem.mdx b/mintlify/reference/filesystem.mdx index 85966c6..505484d 100644 --- a/mintlify/reference/filesystem.mdx +++ b/mintlify/reference/filesystem.mdx @@ -14,9 +14,10 @@ description: "Directory naming, spec file naming, test naming, and path derivati - **type**: `enabler` or `outcome` Examples: + ```text 21-test-harness.enabler/ -54-spx-tree-interpretation.outcome/ +54-tree-interpretation.outcome/ ``` ## Spec file naming diff --git a/mintlify/reference/spx-cli.mdx b/mintlify/reference/spx-cli.mdx index 29a8cb4..23e200d 100644 --- a/mintlify/reference/spx-cli.mdx +++ b/mintlify/reference/spx-cli.mdx @@ -14,12 +14,12 @@ spx status --tree Compares blob hashes in lock files against current file content. Reports each node as valid, stale, or needs work. ```text -$ spx status --tree 54-spx-tree-interpretation.outcome -54-spx-tree-interpretation.outcome/ needs work (no lock file) +$ spx status --tree 54-tree-interpretation.outcome +54-tree-interpretation.outcome/ needs work (no lock file) 21-parent-child-links.enabler/ valid 43-status-rollup.outcome/ stale status-rollup.md changed (was a3b7c12, now 5e9f1d8) - 54-spx-tree-status.outcome/ needs work (no lock file) + 54-tree-status.outcome/ needs work (no lock file) ``` ## spx lock @@ -33,7 +33,7 @@ spx lock Runs all tests for the node. If all pass, writes `spx-lock.yaml` with current blob hashes. If any test fails, exits with an error and leaves the existing lock file unchanged. ```text -$ spx lock 54-spx-tree-interpretation.outcome/43-status-rollup.outcome +$ spx lock 54-tree-interpretation.outcome/43-status-rollup.outcome Running tests... tests/status.unit.test.ts 3 passed Lock regenerated: 43-status-rollup.outcome/spx-lock.yaml diff --git a/src/components/ContextInjectionSection.tsx b/src/components/ContextInjectionSection.tsx index 00334f0..4ccaa77 100644 --- a/src/components/ContextInjectionSection.tsx +++ b/src/components/ContextInjectionSection.tsx @@ -12,14 +12,14 @@ const contextTree = `spx/ parse-directory-tree.md <-- included 43-node-status.enabler/ node-status.md <-- included - 54-spx-tree-interpretation.outcome/ - spx-tree-interpretation.md <-- included (ancestor) + 54-tree-interpretation.outcome/ + tree-interpretation.md <-- included (ancestor) 21-parent-child-links.enabler/ parent-child-links.md <-- included 43-status-rollup.outcome/ [TARGET] status-rollup.md <-- included tests/ -- not included - 54-spx-tree-status.outcome/ -- not included + 54-tree-status.outcome/ -- not included 76-cli-integration.outcome/ -- not included 87-e2e-workflow.outcome/ -- not included`; diff --git a/src/components/OperationalLoopSection.tsx b/src/components/OperationalLoopSection.tsx index 2ad9aad..d471b7b 100644 --- a/src/components/OperationalLoopSection.tsx +++ b/src/components/OperationalLoopSection.tsx @@ -1,23 +1,23 @@ import CodeExample from "@/components/CodeExample"; import Section from "@/components/Section"; -const statusOutput = `$ spx status --tree 54-spx-tree-interpretation.outcome -54-spx-tree-interpretation.outcome/ needs work (no lock file) +const statusOutput = `$ spx status --tree 54-tree-interpretation.outcome +54-tree-interpretation.outcome/ needs work (no lock file) 21-parent-child-links.enabler/ valid 43-status-rollup.outcome/ stale status-rollup.md changed (was a3b7c12, now 5e9f1d8) - 54-spx-tree-status.outcome/ needs work (no lock file)`; + 54-tree-status.outcome/ needs work (no lock file)`; -const lockOutput = `$ spx lock 54-spx-tree-interpretation.outcome/43-status-rollup.outcome +const lockOutput = `$ spx lock 54-tree-interpretation.outcome/43-status-rollup.outcome Running tests... tests/status.unit.test.ts 3 passed Lock regenerated: 43-status-rollup.outcome/spx-lock.yaml`; -const verifyOutput = `$ spx verify --tree 54-spx-tree-interpretation.outcome -54-spx-tree-interpretation.outcome/ needs work +const verifyOutput = `$ spx verify --tree 54-tree-interpretation.outcome +54-tree-interpretation.outcome/ needs work 21-parent-child-links.enabler/ valid 43-status-rollup.outcome/ valid - 54-spx-tree-status.outcome/ needs work`; + 54-tree-status.outcome/ needs work`; const commands = [ { diff --git a/src/components/SolutionSection.tsx b/src/components/SolutionSection.tsx index 09e8a5e..3102ecf 100644 --- a/src/components/SolutionSection.tsx +++ b/src/components/SolutionSection.tsx @@ -31,8 +31,8 @@ const treeStructureCode = `spx/ \u251C\u2500\u2500 43-node-status.enabler/ \u2502 \u251C\u2500\u2500 node-status.md \u2502 \u2514\u2500\u2500 tests/ -\u251C\u2500\u2500 54-spx-tree-interpretation.outcome/ -\u2502 \u251C\u2500\u2500 spx-tree-interpretation.md +\u251C\u2500\u2500 54-tree-interpretation.outcome/ +\u2502 \u251C\u2500\u2500 tree-interpretation.md \u2502 \u251C\u2500\u2500 43-status-rollup.outcome/ \u2502 \u2514\u2500\u2500 tests/ \u2514\u2500\u2500 76-cli-integration.outcome/ diff --git a/src/lib/spec-tree-data.ts b/src/lib/spec-tree-data.ts index 9a8fbc2..f57acc1 100644 --- a/src/lib/spec-tree-data.ts +++ b/src/lib/spec-tree-data.ts @@ -164,9 +164,9 @@ export const specTreeData: SpecNode = node( }, ), node( - "spx-tree-interpretation", - "54-spx-tree-interpretation.outcome/", - "spx-tree-interpretation", + "tree-interpretation", + "54-tree-interpretation.outcome/", + "tree-interpretation", "outcome", "needs-work", 54, @@ -200,7 +200,7 @@ export const specTreeData: SpecNode = node( ), node( "spx-tree-status", - "54-spx-tree-status.outcome/", + "54-tree-status.outcome/", "spx-tree-status", "outcome", "needs-work", @@ -380,9 +380,9 @@ export const specTreeDataSimplified: SpecNode = node( }, ), node( - "spx-tree-interpretation", - "54-spx-tree-interpretation.outcome/", - "spx-tree-interpretation", + "tree-interpretation", + "54-tree-interpretation.outcome/", + "tree-interpretation", "outcome", "needs-work", 54, @@ -406,7 +406,7 @@ export const specTreeDataSimplified: SpecNode = node( ), node( "spx-tree-status", - "54-spx-tree-status.outcome/", + "54-tree-status.outcome/", "spx-tree-status", "outcome", "needs-work", diff --git a/src/lib/story-data.tsx b/src/lib/story-data.tsx index 0c37640..d1fbe27 100644 --- a/src/lib/story-data.tsx +++ b/src/lib/story-data.tsx @@ -41,15 +41,15 @@ export const principleSteps: StoryStepData[] = [ }, { chrome: "\u2514\u2500\u2500 ", - name: "54-spx-tree-interpretation.outcome/", + name: "54-tree-interpretation.outcome/", nameClass: "st-outcome", status: needs, }, - { chrome: " \u251C\u2500\u2500 ", name: "spx-tree-interpretation.md" }, + { chrome: " \u251C\u2500\u2500 ", name: "tree-interpretation.md" }, { chrome: " \u251C\u2500\u2500 ", name: "43-status-rollup.outcome/", nameClass: "st-outcome", status: stale }, { chrome: " \u2514\u2500\u2500 ", - name: "54-spx-tree-status.outcome/", + name: "54-tree-status.outcome/", nameClass: "st-outcome", status: needs, }, @@ -76,7 +76,7 @@ export const principleSteps: StoryStepData[] = [ }, { chrome: "\u2514\u2500\u2500 ", - name: "54-spx-tree-interpretation.outcome/", + name: "54-tree-interpretation.outcome/", nameClass: "st-outcome", dim: true, }, @@ -97,7 +97,7 @@ export const principleSteps: StoryStepData[] = [ { chrome: "\u2502 ", name: "\u2191 human decides: why this product exists", dim: true }, { chrome: "\u251C\u2500\u2500 ", name: "15-cli-framework.adr.md", nameClass: "st-decision" }, { chrome: "\u2502 ", name: "\u2191 human decides: architecture constraint", dim: true }, - { chrome: "\u2514\u2500\u2500 ", name: "54-spx-tree-interpretation.outcome/", nameClass: "st-outcome" }, + { chrome: "\u2514\u2500\u2500 ", name: "54-tree-interpretation.outcome/", nameClass: "st-outcome" }, { chrome: " ", name: "\u2193 agents propagate through dependent nodes", dim: true }, ]), }, @@ -158,7 +158,7 @@ export const specTreeSteps: StoryStepData[] = [ { chrome: "\u251C\u2500\u2500 ", name: "43-node-status.enabler/", nameClass: "st-enabler", status: valid }, { chrome: "\u251C\u2500\u2500 ", - name: "54-spx-tree-interpretation.outcome/", + name: "54-tree-interpretation.outcome/", nameClass: "st-outcome", dim: true, }, @@ -177,11 +177,11 @@ export const specTreeSteps: StoryStepData[] = [ { chrome: "\u251C\u2500\u2500 ", name: "...", dim: true }, { chrome: "\u251C\u2500\u2500 ", - name: "54-spx-tree-interpretation.outcome/", + name: "54-tree-interpretation.outcome/", nameClass: "st-outcome", status: stale, }, - { chrome: "\u2502 \u251C\u2500\u2500 ", name: "spx-tree-interpretation.md" }, + { chrome: "\u2502 \u251C\u2500\u2500 ", name: "tree-interpretation.md" }, { chrome: "\u2502 \u251C\u2500\u2500 ", name: "21-parent-child-links.enabler/", @@ -196,7 +196,7 @@ export const specTreeSteps: StoryStepData[] = [ }, { chrome: "\u2502 \u2514\u2500\u2500 ", - name: "54-spx-tree-status.outcome/", + name: "54-tree-status.outcome/", nameClass: "st-outcome", status: needs, },