Skip to content

Commit 6abaec2

Browse files
Jessica Symonsclaude
andcommitted
Initial scaffold: Visioning Lab Systems Development Tool
Next.js 14 + TypeScript + Tailwind app implementing the Define -> Structure -> Insight -> Action workflow with Anthropic Claude for extraction and generation, editable cards throughout, and PDF/JSON export. Includes a static-export build path for GitHub Pages (AI features disabled in that mode) and a Pages deploy workflow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0 parents  commit 6abaec2

32 files changed

Lines changed: 3912 additions & 0 deletions

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ANTHROPIC_API_KEY=sk-ant-...
2+
ANTHROPIC_MODEL=claude-sonnet-4-6

.github/workflows/pages.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- uses: actions/setup-node@v4
24+
with:
25+
node-version: 20
26+
cache: npm
27+
28+
- name: Install
29+
run: npm ci
30+
31+
- name: Strip server-only API routes (incompatible with static export)
32+
run: rm -rf app/api
33+
34+
- name: Build static site
35+
env:
36+
STATIC_EXPORT: "1"
37+
run: npm run build
38+
39+
- name: Add .nojekyll
40+
run: touch out/.nojekyll
41+
42+
- uses: actions/configure-pages@v5
43+
44+
- uses: actions/upload-pages-artifact@v3
45+
with:
46+
path: out
47+
48+
deploy:
49+
needs: build
50+
runs-on: ubuntu-latest
51+
environment:
52+
name: github-pages
53+
url: ${{ steps.deployment.outputs.page_url }}
54+
steps:
55+
- id: deployment
56+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
node_modules
2+
.next
3+
out
4+
dist
5+
.env
6+
.env.local
7+
.DS_Store
8+
*.log
9+
.vercel
10+
next-env.d.ts

README.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Systems Development Framework
2+
3+
Visioning Lab has developed a Systems Development framework to help organisations work through complex problems in a structured, transparent way.
4+
5+
The approach moves from defining inputs through to action, enabling clearer thinking, better decisions and ongoing system insight.
6+
7+
## Framework Overview
8+
Define → Structure → Insight → Action
9+
10+
## Define
11+
12+
Capture and clarify what matters across multiple sources and perspectives.
13+
14+
- Documents, meetings, data, stakeholder inputs
15+
- Structured into clear points
16+
- Identification of assumptions and implications
17+
18+
## Structure
19+
20+
Organise relationships, options and decision logic.
21+
22+
- Linking points into models
23+
- Defining options and trade-offs
24+
- Making reasoning explicit and comparable
25+
26+
## Insight
27+
28+
Identify patterns, signals and system-level implications.
29+
30+
- Recurring issues across inputs
31+
- Hidden assumptions
32+
- Emerging risks and opportunities
33+
34+
## Action
35+
36+
Translate insight into interventions and next steps.
37+
38+
- Decisions and action plans
39+
- Policy or operational changes
40+
- Feedback into future cycles
41+
42+
## What this enables
43+
- Structured understanding of complex systems
44+
- Transparent and explainable decision-making
45+
- Comparability across cases and organisations
46+
- Continuous learning over time
47+
48+
## Interactive Tool
49+
50+
An interactive Next.js implementation of the framework lives in this repo.
51+
It walks the user through Define → Structure → Insight → Action, with
52+
LLM-assisted extraction at each step and editable outputs throughout.
53+
54+
### Setup
55+
56+
```bash
57+
npm install
58+
cp .env.example .env.local # add your ANTHROPIC_API_KEY
59+
npm run dev
60+
```
61+
62+
Open [http://localhost:3000](http://localhost:3000).
63+
64+
### Stack
65+
66+
- Next.js 14 (App Router) + TypeScript + Tailwind
67+
- Anthropic Claude (`claude-sonnet-4-6` by default) for extraction & generation
68+
- `zod` schemas for the Point / Option / Insight / Action data model
69+
- `jspdf` for the PDF export
70+
- Session state autosaved to `localStorage` (no server storage)
71+
72+
### Project layout
73+
74+
- [app/page.tsx](app/page.tsx) — landing
75+
- [app/tool/](app/tool/) — the four-step flow + export page
76+
- [app/api/](app/api/) — LLM endpoints (one per step)
77+
- [lib/schema.ts](lib/schema.ts) — data model
78+
- [lib/prompts.ts](lib/prompts.ts) — system prompts per step
79+
- [lib/anthropic.ts](lib/anthropic.ts) — Claude client + JSON helper
80+
- [lib/session.tsx](lib/session.tsx) — React context + localStorage autosave
81+
- [lib/pdf.ts](lib/pdf.ts) — PDF + JSON export
82+
83+
## Applications
84+
- Infrastructure and environmental systems (e.g. water, energy)
85+
- Policy and regulatory decision-making
86+
- Research and interdisciplinary projects
87+
- Complex programme design
88+
89+
## Relationship to Existing Work
90+
91+
This project builds on Visioning Lab’s previous work, including:
92+
93+
- Ontology Maker (structured terminology and relationships)
94+
- Water Decisions (decision support for infrastructure systems)
95+
96+
It brings these together into a single, integrated framework.

app/api/extract-points/route.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { NextResponse } from "next/server";
2+
import { completeJSON, LLMError } from "@/lib/anthropic";
3+
import { EXTRACT_POINTS_SYSTEM } from "@/lib/prompts";
4+
5+
export const runtime = "nodejs";
6+
7+
export async function POST(req: Request) {
8+
try {
9+
const { input } = (await req.json()) as { input?: string };
10+
if (!input || !input.trim()) {
11+
return NextResponse.json({ error: "Input is required" }, { status: 400 });
12+
}
13+
const data = await completeJSON<{ points: unknown[] }>({
14+
system: EXTRACT_POINTS_SYSTEM,
15+
user: input,
16+
maxTokens: 2048,
17+
});
18+
return NextResponse.json(data);
19+
} catch (e) {
20+
const msg = e instanceof Error ? e.message : String(e);
21+
const status = e instanceof LLMError ? 502 : 500;
22+
return NextResponse.json({ error: msg }, { status });
23+
}
24+
}

app/api/generate-actions/route.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { NextResponse } from "next/server";
2+
import { completeJSON, LLMError } from "@/lib/anthropic";
3+
import { GENERATE_ACTIONS_SYSTEM } from "@/lib/prompts";
4+
5+
export const runtime = "nodejs";
6+
7+
export async function POST(req: Request) {
8+
try {
9+
const body = await req.json();
10+
const data = await completeJSON<{ actions: unknown[] }>({
11+
system: GENERATE_ACTIONS_SYSTEM,
12+
user: JSON.stringify(body, null, 2),
13+
maxTokens: 2048,
14+
});
15+
return NextResponse.json(data);
16+
} catch (e) {
17+
const msg = e instanceof Error ? e.message : String(e);
18+
const status = e instanceof LLMError ? 502 : 500;
19+
return NextResponse.json({ error: msg }, { status });
20+
}
21+
}

app/api/generate-insights/route.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { NextResponse } from "next/server";
2+
import { completeJSON, LLMError } from "@/lib/anthropic";
3+
import { GENERATE_INSIGHTS_SYSTEM } from "@/lib/prompts";
4+
5+
export const runtime = "nodejs";
6+
7+
export async function POST(req: Request) {
8+
try {
9+
const body = await req.json();
10+
const data = await completeJSON<{ insights: unknown[] }>({
11+
system: GENERATE_INSIGHTS_SYSTEM,
12+
user: JSON.stringify(body, null, 2),
13+
maxTokens: 2048,
14+
});
15+
return NextResponse.json(data);
16+
} catch (e) {
17+
const msg = e instanceof Error ? e.message : String(e);
18+
const status = e instanceof LLMError ? 502 : 500;
19+
return NextResponse.json({ error: msg }, { status });
20+
}
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { NextResponse } from "next/server";
2+
import { completeJSON, LLMError } from "@/lib/anthropic";
3+
import { GENERATE_STRUCTURE_SYSTEM } from "@/lib/prompts";
4+
5+
export const runtime = "nodejs";
6+
7+
export async function POST(req: Request) {
8+
try {
9+
const { points } = (await req.json()) as { points: unknown[] };
10+
if (!Array.isArray(points) || points.length === 0) {
11+
return NextResponse.json({ error: "Points are required" }, { status: 400 });
12+
}
13+
const data = await completeJSON<{ options: unknown[]; criteria: unknown[] }>({
14+
system: GENERATE_STRUCTURE_SYSTEM,
15+
user: JSON.stringify({ points }, null, 2),
16+
maxTokens: 2048,
17+
});
18+
return NextResponse.json(data);
19+
} catch (e) {
20+
const msg = e instanceof Error ? e.message : String(e);
21+
const status = e instanceof LLMError ? 502 : 500;
22+
return NextResponse.json({ error: msg }, { status });
23+
}
24+
}

app/globals.css

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
html, body { background: #fafaf7; color: #1a1a1a; }
6+
7+
@layer components {
8+
.btn {
9+
@apply inline-flex items-center justify-center rounded-md border border-line bg-white px-4 py-2 text-sm font-medium text-ink shadow-sm transition hover:border-ink/40 hover:shadow disabled:cursor-not-allowed disabled:opacity-50;
10+
}
11+
.btn-primary {
12+
@apply inline-flex items-center justify-center rounded-md bg-accent px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-accent/90 disabled:cursor-not-allowed disabled:opacity-50;
13+
}
14+
.card {
15+
@apply rounded-lg border border-line bg-white p-4 shadow-sm;
16+
}
17+
.label {
18+
@apply mb-1 block text-xs font-medium uppercase tracking-wide text-ink/60;
19+
}
20+
.input, .textarea {
21+
@apply w-full rounded-md border border-line bg-white px-3 py-2 text-sm text-ink placeholder:text-ink/40 focus:border-accent focus:outline-none focus:ring-1 focus:ring-accent;
22+
}
23+
}
24+
25+
@media print {
26+
.no-print { display: none !important; }
27+
body { background: white; }
28+
}

app/layout.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import "./globals.css";
2+
import type { Metadata } from "next";
3+
import type { ReactNode } from "react";
4+
5+
export const metadata: Metadata = {
6+
title: "Visioning Lab — Systems Development Tool",
7+
description:
8+
"Work through complex problems using the Visioning Lab Systems Development framework: Define → Structure → Insight → Action.",
9+
};
10+
11+
export default function RootLayout({ children }: { children: ReactNode }) {
12+
return (
13+
<html lang="en">
14+
<body className="min-h-screen bg-paper text-ink antialiased">{children}</body>
15+
</html>
16+
);
17+
}

0 commit comments

Comments
 (0)