@bvdm/delano 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.delano/viewer/README.md +3 -2
  2. package/.delano/viewer/public/app.js +13 -1
  3. package/.delano/viewer/public/app.jsx +2312 -0
  4. package/.delano/viewer/public/delano-mark.svg +4 -0
  5. package/.delano/viewer/public/index.html +12 -14
  6. package/.delano/viewer/public/styles.css +1005 -833
  7. package/.delano/viewer/server.js +46 -5
  8. package/README.md +63 -3
  9. package/assets/install-manifest.json +7 -0
  10. package/assets/payload/.agents/adapters/manifest.schema.json +103 -0
  11. package/assets/payload/.agents/adapters/spec-kit/adapter.json +71 -0
  12. package/assets/payload/.agents/hooks/README.md +6 -1
  13. package/assets/payload/.agents/hooks/codex-session-status.js +123 -0
  14. package/assets/payload/.agents/schemas/status-transitions.json +35 -0
  15. package/assets/payload/.agents/scripts/README.md +1 -1
  16. package/assets/payload/.agents/scripts/check-status-transitions.mjs +171 -2
  17. package/assets/payload/.agents/scripts/pm/import-spec-kit.sh +605 -0
  18. package/assets/payload/.agents/scripts/pm/init.sh +31 -2
  19. package/assets/payload/.agents/scripts/pm/research.sh +296 -0
  20. package/assets/payload/.agents/scripts/pm/status.sh +135 -28
  21. package/assets/payload/.agents/scripts/pm/validate.sh +16 -0
  22. package/assets/payload/.codex/hooks.json +17 -0
  23. package/assets/payload/.delano/viewer/README.md +3 -2
  24. package/assets/payload/.delano/viewer/public/app.js +13 -1
  25. package/assets/payload/.delano/viewer/public/index.html +12 -14
  26. package/assets/payload/.delano/viewer/public/styles.css +1005 -833
  27. package/assets/payload/.delano/viewer/server.js +46 -5
  28. package/assets/payload/.project/templates/decisions.md +18 -0
  29. package/assets/payload/.project/templates/plan.md +17 -0
  30. package/assets/payload/.project/templates/spec.md +12 -0
  31. package/assets/payload/.project/templates/task.md +6 -0
  32. package/assets/payload/.project/templates/workstream.md +1 -0
  33. package/package.json +4 -2
  34. package/src/cli/commands/install.js +2 -1
  35. package/src/cli/commands/state.js +689 -0
  36. package/src/cli/commands/viewer.js +2 -1
  37. package/src/cli/commands/wrapper.js +29 -5
  38. package/src/cli/index.js +120 -7
  39. package/src/cli/lib/install.js +179 -2
  40. package/src/cli/lib/project-state.js +918 -0
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'USAGE'
6
+ Usage:
7
+ research.sh <project-slug> <research-slug> [options]
8
+
9
+ Creates a repo-native Delano research intake folder for a project.
10
+
11
+ Required arguments:
12
+ project-slug Existing Delano project slug
13
+ research-slug Research folder slug in kebab-case
14
+
15
+ Options:
16
+ --title <title> Human-readable research title
17
+ --question <question> Primary research question
18
+ --owner <owner> Research owner, defaults to team
19
+ --no-validate Create artifacts without running Delano validation
20
+ --json Print a single machine-readable JSON result
21
+ -h, --help Show this help
22
+
23
+ Agent notes:
24
+ - Use this before mutating spec/plan/tasks when intent is unclear.
25
+ - Update findings.md and progress.md during investigation.
26
+ - Fold durable conclusions forward into spec.md, plan.md, decisions.md, workstreams, tasks, or updates.
27
+ - Research files are supporting discovery state, not executable task truth.
28
+ USAGE
29
+ }
30
+
31
+ resolve_python() {
32
+ if command -v python3 >/dev/null 2>&1 && python3 -c "import sys" >/dev/null 2>&1; then
33
+ PYTHON_CMD=(python3)
34
+ elif command -v py >/dev/null 2>&1 && py -3 -c "import sys" >/dev/null 2>&1; then
35
+ PYTHON_CMD=(py -3)
36
+ elif command -v python >/dev/null 2>&1 && python -c "import sys" >/dev/null 2>&1; then
37
+ PYTHON_CMD=(python)
38
+ else
39
+ echo "Error: Python runtime not found. Install python3, python, or py -3." >&2
40
+ exit 1
41
+ fi
42
+ }
43
+
44
+ resolve_python
45
+
46
+ json_escape() {
47
+ "${PYTHON_CMD[@]}" -c 'import json,sys; print(json.dumps(sys.stdin.read().rstrip("\n")))'
48
+ }
49
+
50
+ if [[ "${1:-}" == "" || "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
51
+ usage
52
+ exit 0
53
+ fi
54
+
55
+ if [[ "${2:-}" == "" ]]; then
56
+ usage
57
+ exit 1
58
+ fi
59
+
60
+ project_slug="$1"
61
+ research_slug="$2"
62
+ shift 2
63
+
64
+ title=""
65
+ question=""
66
+ owner="team"
67
+ validate="true"
68
+ json="false"
69
+
70
+ while [[ $# -gt 0 ]]; do
71
+ case "$1" in
72
+ --title)
73
+ title="${2:-}"
74
+ if [[ -z "$title" ]]; then echo "Error: --title requires a value"; exit 1; fi
75
+ shift 2
76
+ ;;
77
+ --question)
78
+ question="${2:-}"
79
+ if [[ -z "$question" ]]; then echo "Error: --question requires a value"; exit 1; fi
80
+ shift 2
81
+ ;;
82
+ --owner)
83
+ owner="${2:-}"
84
+ if [[ -z "$owner" ]]; then echo "Error: --owner requires a value"; exit 1; fi
85
+ shift 2
86
+ ;;
87
+ --no-validate)
88
+ validate="false"
89
+ shift
90
+ ;;
91
+ --json)
92
+ json="true"
93
+ shift
94
+ ;;
95
+ -h|--help)
96
+ usage
97
+ exit 0
98
+ ;;
99
+ --)
100
+ shift
101
+ ;;
102
+ --*)
103
+ echo "Error: unknown option: $1"
104
+ exit 1
105
+ ;;
106
+ *)
107
+ echo "Error: unexpected positional argument: $1"
108
+ exit 1
109
+ ;;
110
+ esac
111
+ done
112
+
113
+ if [[ ! "$project_slug" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
114
+ echo "Error: project-slug must be kebab-case"
115
+ exit 1
116
+ fi
117
+
118
+ if [[ ! "$research_slug" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
119
+ echo "Error: research-slug must be kebab-case"
120
+ exit 1
121
+ fi
122
+
123
+ root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
124
+ cd "$root"
125
+
126
+ project_dir=".project/projects/$project_slug"
127
+ if [[ ! -d "$project_dir" ]]; then
128
+ echo "Error: Delano project not found: $project_dir"
129
+ exit 1
130
+ fi
131
+
132
+ research_dir="$project_dir/research/$research_slug"
133
+ if [[ -d "$research_dir" ]]; then
134
+ echo "Error: research intake already exists at $research_dir"
135
+ exit 1
136
+ fi
137
+
138
+ now="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
139
+ title="${title:-${research_slug//-/ }}"
140
+ question="${question:-<primary research question>}"
141
+
142
+ mkdir -p "$research_dir"
143
+
144
+ cat > "$research_dir/task_plan.md" <<PLAN
145
+ ---
146
+ type: research_intake
147
+ project: $project_slug
148
+ slug: $research_slug
149
+ owner: $owner
150
+ status: opened
151
+ created: $now
152
+ updated: $now
153
+ ---
154
+
155
+ # Research Plan: $title
156
+
157
+ ## Goal
158
+
159
+ Answer the research question and fold durable conclusions into canonical Delano project artifacts.
160
+
161
+ ## Primary Question
162
+
163
+ $question
164
+
165
+ ## Scope
166
+
167
+ ### In Scope
168
+
169
+ - Gather relevant evidence.
170
+ - Capture findings and decisions.
171
+ - Identify changes needed in \`spec.md\`, \`plan.md\`, \`decisions.md\`, workstreams, tasks, or updates.
172
+
173
+ ### Out of Scope
174
+
175
+ - Marking delivery tasks done from research alone.
176
+ - External sync writes without normal Delano approval semantics.
177
+ - Storing secrets, credentials, or private machine paths.
178
+
179
+ ## Current Phase
180
+
181
+ Opened
182
+
183
+ ## Phases
184
+
185
+ - [x] Open research intake
186
+ - [ ] Investigate sources and options
187
+ - [ ] Summarize findings
188
+ - [ ] Fold forward into canonical project artifacts or explicitly close as no-action
189
+
190
+ ## Decisions Made
191
+
192
+ | Decision | Rationale |
193
+ | --- | --- |
194
+
195
+ ## Blockers
196
+
197
+ | Blocker | Owner | Check-back |
198
+ | --- | --- | --- |
199
+ PLAN
200
+
201
+ cat > "$research_dir/findings.md" <<FINDINGS
202
+ ---
203
+ type: research_findings
204
+ project: $project_slug
205
+ slug: $research_slug
206
+ created: $now
207
+ updated: $now
208
+ ---
209
+
210
+ # Findings: $title
211
+
212
+ ## Source References
213
+
214
+ - <source, file, command, or artifact inspected>
215
+
216
+ ## Observations
217
+
218
+ - <finding>
219
+
220
+ ## Options Considered
221
+
222
+ | Option | Pros | Cons | Decision |
223
+ | --- | --- | --- | --- |
224
+
225
+ ## Fold-Forward Candidates
226
+
227
+ | Finding | Target Artifact | Proposed Change |
228
+ | --- | --- | --- |
229
+
230
+ ## Open Questions
231
+
232
+ - <question>
233
+ FINDINGS
234
+
235
+ cat > "$research_dir/progress.md" <<PROGRESS
236
+ ---
237
+ type: research_progress
238
+ project: $project_slug
239
+ slug: $research_slug
240
+ created: $now
241
+ updated: $now
242
+ ---
243
+
244
+ # Progress: $title
245
+
246
+ ## $now
247
+
248
+ - Opened research intake for project \`$project_slug\`.
249
+ - Primary question: $question
250
+
251
+ ## Validation Evidence
252
+
253
+ - Pending.
254
+
255
+ ## Handoff Summary
256
+
257
+ - Pending.
258
+ PROGRESS
259
+
260
+ validation_status="skipped"
261
+ ok="true"
262
+ error=""
263
+ if [[ "$validate" == "true" ]]; then
264
+ if [[ "$json" == "true" ]]; then
265
+ validation_log="$(mktemp)"
266
+ if "$root/.agents/scripts/pm/validate.sh" >"$validation_log" 2>&1; then
267
+ validation_status="passed"
268
+ else
269
+ validation_status="failed"
270
+ ok="false"
271
+ error="validation_failed"
272
+ fi
273
+ rm -f "$validation_log"
274
+ else
275
+ "$root/.agents/scripts/pm/validate.sh"
276
+ validation_status="passed"
277
+ fi
278
+ fi
279
+
280
+ if [[ "$json" == "true" ]]; then
281
+ project_json="$(printf '%s' "$project_dir" | json_escape)"
282
+ research_json="$(printf '%s' "$research_dir" | json_escape)"
283
+ validation_json="$(printf '%s' "$validation_status" | json_escape)"
284
+ if [[ "$ok" == "true" ]]; then
285
+ printf '{"ok":true,"command":"research","project":%s,"research":%s,"files":["task_plan.md","findings.md","progress.md"],"validation":%s}\n' "$project_json" "$research_json" "$validation_json"
286
+ else
287
+ error_json="$(printf '%s' "$error" | json_escape)"
288
+ printf '{"ok":false,"command":"research","project":%s,"research":%s,"files":["task_plan.md","findings.md","progress.md"],"validation":%s,"error":%s}\n' "$project_json" "$research_json" "$validation_json" "$error_json"
289
+ exit 1
290
+ fi
291
+ else
292
+ echo "Created Delano research intake: $research_dir"
293
+ echo "Files: task_plan.md, findings.md, progress.md"
294
+ echo "Validation: $validation_status"
295
+ echo "Next: update findings.md and progress.md, then fold conclusions into canonical Delano artifacts."
296
+ fi
@@ -4,24 +4,92 @@ set -euo pipefail
4
4
  root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
5
5
  cd "$root"
6
6
 
7
+ open_only=false
8
+ brief=false
9
+
10
+ usage() {
11
+ cat <<'EOF'
12
+ Usage:
13
+ status.sh [--open] [--brief]
14
+
15
+ Options:
16
+ --open Show only projects that are not closed.
17
+ --brief Show one compact line per project.
18
+ -h, --help
19
+ Show this help.
20
+ EOF
21
+ }
22
+
23
+ while [[ $# -gt 0 ]]; do
24
+ case "$1" in
25
+ --open)
26
+ open_only=true
27
+ ;;
28
+ --brief)
29
+ brief=true
30
+ ;;
31
+ -h|--help)
32
+ usage
33
+ exit 0
34
+ ;;
35
+ *)
36
+ echo "Unknown status option: $1" >&2
37
+ usage >&2
38
+ exit 1
39
+ ;;
40
+ esac
41
+ shift
42
+ done
43
+
7
44
  fm_get() {
8
45
  local file="$1"
9
46
  local key="$2"
10
- awk -v key="$key" '
11
- BEGIN {in_fm=0}
12
- /^---[[:space:]]*$/ {if (in_fm==0) {in_fm=1; next} else {exit}}
13
- in_fm==1 && $0 ~ "^" key ":[[:space:]]*" {
14
- sub("^" key ":[[:space:]]*", "")
15
- print
16
- exit
17
- }
18
- ' "$file"
47
+ local line
48
+ local in_fm=0
49
+ while IFS= read -r line; do
50
+ if [[ "$line" =~ ^---[[:space:]]*$ ]]; then
51
+ if [[ $in_fm -eq 0 ]]; then
52
+ in_fm=1
53
+ continue
54
+ fi
55
+ return 1
56
+ fi
57
+
58
+ if [[ $in_fm -eq 1 && "$line" == "$key:"* ]]; then
59
+ local value="${line#"$key:"}"
60
+ value="${value#"${value%%[![:space:]]*}"}"
61
+ printf '%s\n' "$value"
62
+ return 0
63
+ fi
64
+ done < "$file"
65
+ return 1
19
66
  }
20
67
 
21
- echo "Delano portfolio status"
22
- echo "======================="
68
+ is_closed_spec_status() {
69
+ local status="${1:-unknown}"
70
+ [[ "$status" == "complete" || "$status" == "deferred" ]]
71
+ }
72
+
73
+ is_closed_plan_status() {
74
+ local status="${1:-unknown}"
75
+ [[ "$status" == "done" || "$status" == "deferred" ]]
76
+ }
77
+
78
+ is_closed_task_status() {
79
+ local status="${1:-unknown}"
80
+ [[ "$status" == "done" || "$status" == "deferred" || "$status" == "canceled" ]]
81
+ }
82
+
83
+ if [[ "$open_only" == "true" ]]; then
84
+ echo "Delano open project status"
85
+ echo "=========================="
86
+ else
87
+ echo "Delano portfolio status"
88
+ echo "======================="
89
+ fi
23
90
 
24
91
  project_count=0
92
+ printed_count=0
25
93
  for project_dir in .project/projects/*; do
26
94
  [[ -d "$project_dir" ]] || continue
27
95
  [[ "$(basename "$project_dir")" == ".gitkeep" ]] && continue
@@ -31,31 +99,70 @@ for project_dir in .project/projects/*; do
31
99
  spec_status="$(fm_get "$project_dir/spec.md" status 2>/dev/null || true)"
32
100
  plan_status="$(fm_get "$project_dir/plan.md" status 2>/dev/null || true)"
33
101
 
34
- echo ""
35
- echo "Project: $slug"
36
- echo " Spec status: ${spec_status:-unknown}"
37
- echo " Plan status: ${plan_status:-unknown}"
38
-
39
102
  total=0
103
+ open_tasks=0
104
+ backlog_count=0
105
+ ready_count=0
106
+ in_progress_count=0
107
+ review_count=0
108
+ done_count=0
109
+ blocked_count=0
110
+ deferred_count=0
111
+ canceled_count=0
112
+ unknown_count=0
40
113
  for task in "$project_dir"/tasks/*.md; do
41
114
  [[ -f "$task" ]] || continue
115
+ status="$(fm_get "$task" status 2>/dev/null || true)"
42
116
  total=$((total + 1))
117
+ if ! is_closed_task_status "$status"; then
118
+ open_tasks=$((open_tasks + 1))
119
+ fi
120
+ case "$status" in
121
+ backlog) backlog_count=$((backlog_count + 1)) ;;
122
+ ready) ready_count=$((ready_count + 1)) ;;
123
+ in-progress) in_progress_count=$((in_progress_count + 1)) ;;
124
+ review) review_count=$((review_count + 1)) ;;
125
+ done) done_count=$((done_count + 1)) ;;
126
+ blocked) blocked_count=$((blocked_count + 1)) ;;
127
+ deferred) deferred_count=$((deferred_count + 1)) ;;
128
+ canceled) canceled_count=$((canceled_count + 1)) ;;
129
+ *) unknown_count=$((unknown_count + 1)) ;;
130
+ esac
43
131
  done
44
132
 
45
- for st in backlog ready in-progress review done blocked canceled; do
46
- count=0
47
- for task in "$project_dir"/tasks/*.md; do
48
- [[ -f "$task" ]] || continue
49
- status="$(fm_get "$task" status 2>/dev/null || true)"
50
- if [[ "$status" == "$st" ]]; then
51
- count=$((count + 1))
52
- fi
53
- done
54
- [[ $count -gt 0 ]] && echo " $st: $count"
55
- done
56
- echo " total tasks: $total"
133
+ project_open=false
134
+ if ! is_closed_spec_status "$spec_status" || ! is_closed_plan_status "$plan_status" || [[ $open_tasks -gt 0 ]]; then
135
+ project_open=true
136
+ fi
137
+
138
+ if [[ "$open_only" == "true" && "$project_open" != "true" ]]; then
139
+ continue
140
+ fi
141
+
142
+ printed_count=$((printed_count + 1))
143
+
144
+ if [[ "$brief" == "true" ]]; then
145
+ echo "${slug} spec=${spec_status:-unknown} plan=${plan_status:-unknown} open_tasks=${open_tasks} total_tasks=${total}"
146
+ else
147
+ echo ""
148
+ echo "Project: $slug"
149
+ echo " Spec status: ${spec_status:-unknown}"
150
+ echo " Plan status: ${plan_status:-unknown}"
151
+ [[ $backlog_count -gt 0 ]] && echo " backlog: $backlog_count"
152
+ [[ $ready_count -gt 0 ]] && echo " ready: $ready_count"
153
+ [[ $in_progress_count -gt 0 ]] && echo " in-progress: $in_progress_count"
154
+ [[ $review_count -gt 0 ]] && echo " review: $review_count"
155
+ [[ $done_count -gt 0 ]] && echo " done: $done_count"
156
+ [[ $blocked_count -gt 0 ]] && echo " blocked: $blocked_count"
157
+ [[ $deferred_count -gt 0 ]] && echo " deferred: $deferred_count"
158
+ [[ $canceled_count -gt 0 ]] && echo " canceled: $canceled_count"
159
+ [[ $unknown_count -gt 0 ]] && echo " unknown: $unknown_count"
160
+ echo " total tasks: $total"
161
+ fi
57
162
  done
58
163
 
59
164
  if [[ $project_count -eq 0 ]]; then
60
165
  echo "No projects found. Create one with: .agents/scripts/pm/init.sh <slug> <project-name>"
166
+ elif [[ "$open_only" == "true" && $printed_count -eq 0 ]]; then
167
+ echo "No open projects found."
61
168
  fi
@@ -467,6 +467,20 @@ if [[ -n "$artifact_schema_check" ]]; then
467
467
  fi
468
468
  fi
469
469
 
470
+ if [[ -f scripts/check-adapter-manifests.mjs ]]; then
471
+ echo ""
472
+ if command -v node >/dev/null 2>&1; then
473
+ if node scripts/check-adapter-manifests.mjs; then
474
+ true
475
+ else
476
+ errors=$((errors + 1))
477
+ fi
478
+ else
479
+ echo "❌ Node runtime not found for adapter manifest check"
480
+ errors=$((errors + 1))
481
+ fi
482
+ fi
483
+
470
484
  operating_modes_check=""
471
485
  if [[ -f .agents/scripts/check-operating-modes.mjs ]]; then
472
486
  operating_modes_check=".agents/scripts/check-operating-modes.mjs"
@@ -497,6 +511,8 @@ fi
497
511
 
498
512
  if [[ -n "$status_transition_check" ]]; then
499
513
  echo ""
514
+ echo "Project lifecycle and status transition check"
515
+ echo "---------------------------------------------"
500
516
  if command -v node >/dev/null 2>&1; then
501
517
  if node "$status_transition_check"; then
502
518
  true
@@ -0,0 +1,17 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|resume",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node \"$(git rev-parse --show-toplevel)/.agents/hooks/codex-session-status.js\"",
10
+ "timeout": 5,
11
+ "statusMessage": "Loading Delano open projects"
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
@@ -3,8 +3,9 @@
3
3
  Minimal local frontend for browsing `.project` markdown contracts.
4
4
 
5
5
  - Read-only: serves files from `.project` and does not write delivery state.
6
- - Default URL: `http://127.0.0.1:3977`
7
- - Override port: `DELANO_VIEWER_PORT=3987 npm run viewer`
6
+ - Default starting URL: `http://127.0.0.1:3977`
7
+ - Override starting port: `DELANO_VIEWER_PORT=3987 npm run viewer`
8
+ - Multiple viewers can run at once. If the starting port is already in use, the viewer tries the next available port and prints the actual URL.
8
9
 
9
10
  Run from the repository root:
10
11
 
@@ -771,7 +771,19 @@ function render() {
771
771
  });
772
772
  document.querySelectorAll('[data-doc]').forEach((el) => el.onclick = () => loadDoc(el.dataset.doc));
773
773
  document.querySelectorAll('[data-status]').forEach((el) => el.onclick = () => { state.status = el.dataset.status; render(); });
774
- document.querySelectorAll('[data-role]').forEach((el) => el.onclick = () => { state.role = el.dataset.role; state.workstream = null; render(); });
774
+ document.querySelectorAll('[data-role]').forEach((el) => el.onclick = () => {
775
+ state.role = el.dataset.role;
776
+ state.workstream = null;
777
+ if (state.role !== 'all') {
778
+ const firstInRole = currentDocs().find((doc) => doc.role === state.role);
779
+ if (firstInRole) {
780
+ if (state.role === 'workstream') state.workstream = firstInRole.path;
781
+ loadDoc(firstInRole.path);
782
+ return;
783
+ }
784
+ }
785
+ render();
786
+ });
775
787
  document.querySelectorAll('[data-workstream]').forEach((el) => el.onclick = () => { state.workstream = el.dataset.workstream; state.role = 'all'; loadDoc(el.dataset.doc); });
776
788
  document.querySelectorAll('[data-clear-workstream]').forEach((el) => el.onclick = () => { state.workstream = null; render(); });
777
789
  document.querySelectorAll('[data-outline-toggle]').forEach((el) => el.onclick = () => { state.outlineOpen = !state.outlineOpen; render(); });
@@ -1,21 +1,19 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>Delano Viewer</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link
10
- rel="stylesheet"
11
- href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;1,6..72,400&family=Instrument+Serif:ital@0;1&display=swap"
12
- />
13
- <link rel="stylesheet" href="/styles.css" />
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Delano Viewer</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10
+ <link rel="stylesheet" href="/styles.css" />
14
11
  </head>
15
12
  <body>
16
- <div class="ambient" aria-hidden="true"></div>
17
- <div id="app">Loading Delano viewer...</div>
18
- <div id="copy-live" class="visually-hidden" aria-live="polite" aria-atomic="true"></div>
19
- <script src="/app.js"></script>
13
+ <div id="root"></div>
14
+ <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" crossorigin="anonymous"></script>
15
+ <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" crossorigin="anonymous"></script>
16
+ <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" crossorigin="anonymous"></script>
17
+ <script type="text/babel" src="/app.jsx"></script>
20
18
  </body>
21
19
  </html>