@medicine-wheel/app 0.4.5 → 0.4.7
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.
- package/Dockerfile +0 -1
- package/app/narrative/cycles/page.tsx +6 -2
- package/app/narrative/page.tsx +2 -1
- package/lib/cycle-response.ts +66 -0
- package/lib/store.ts +3 -2
- package/package.json +18 -18
package/Dockerfile
CHANGED
|
@@ -28,7 +28,6 @@ RUN npm ci --legacy-peer-deps --ignore-scripts
|
|
|
28
28
|
# Copy Next.js application source
|
|
29
29
|
COPY app/ ./app/
|
|
30
30
|
COPY components/ ./components/
|
|
31
|
-
COPY hooks/ ./hooks/
|
|
32
31
|
COPY lib/ ./lib/
|
|
33
32
|
COPY public/ ./public/
|
|
34
33
|
COPY next.config.mjs postcss.config.mjs tsconfig.json next-env.d.ts ./
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import type { FormEvent } from "react";
|
|
5
5
|
import { type MedicineWheelCycle, DIRECTION_COLORS, type DirectionName } from "@/lib/types";
|
|
6
|
+
import { extractCycles } from "@/lib/cycle-response";
|
|
6
7
|
import { toast } from "sonner";
|
|
7
8
|
|
|
8
9
|
export default function CyclesPage() {
|
|
@@ -11,7 +12,10 @@ export default function CyclesPage() {
|
|
|
11
12
|
const [expandedId, setExpandedId] = useState<string | null>(null);
|
|
12
13
|
|
|
13
14
|
useEffect(() => {
|
|
14
|
-
fetch("/api/narrative/cycles")
|
|
15
|
+
fetch("/api/narrative/cycles")
|
|
16
|
+
.then((r) => r.json())
|
|
17
|
+
.then((d) => setCycles(extractCycles(d)))
|
|
18
|
+
.catch(() => setCycles([]));
|
|
15
19
|
}, []);
|
|
16
20
|
|
|
17
21
|
async function createCycle(e: FormEvent<HTMLFormElement>) {
|
|
@@ -23,7 +27,7 @@ export default function CyclesPage() {
|
|
|
23
27
|
toast.success("Cycle created");
|
|
24
28
|
setShowForm(false);
|
|
25
29
|
const data = await fetch("/api/narrative/cycles").then((r) => r.json());
|
|
26
|
-
setCycles(
|
|
30
|
+
setCycles(extractCycles(data));
|
|
27
31
|
} else { toast.error("Failed"); }
|
|
28
32
|
}
|
|
29
33
|
|
package/app/narrative/page.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { type NarrativeBeat, type MedicineWheelCycle, DIRECTION_COLORS, type DirectionName } from "@/lib/types";
|
|
5
|
+
import { extractCycles } from "@/lib/cycle-response";
|
|
5
6
|
|
|
6
7
|
export default function NarrativePage() {
|
|
7
8
|
const [beats, setBeats] = useState<NarrativeBeat[]>([]);
|
|
@@ -10,7 +11,7 @@ export default function NarrativePage() {
|
|
|
10
11
|
|
|
11
12
|
useEffect(() => {
|
|
12
13
|
Promise.all([fetch("/api/narrative/beats").then((r) => r.json()), fetch("/api/narrative/cycles").then((r) => r.json())])
|
|
13
|
-
.then(([b, c]) => { setBeats(Array.isArray(b) ? b : []); setCycles(
|
|
14
|
+
.then(([b, c]) => { setBeats(Array.isArray(b) ? b : []); setCycles(extractCycles(c)); })
|
|
14
15
|
.catch(() => {});
|
|
15
16
|
}, []);
|
|
16
17
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { DirectionName, MedicineWheelCycle } from "./types";
|
|
2
|
+
|
|
3
|
+
interface CycleListResponse {
|
|
4
|
+
cycles?: unknown;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type ApiMedicineWheelCycle = MedicineWheelCycle & {
|
|
8
|
+
archived?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const DIRECTION_NAMES = new Set<DirectionName>(["east", "south", "west", "north"]);
|
|
12
|
+
|
|
13
|
+
function asNonNegativeInteger(value: unknown): number {
|
|
14
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
15
|
+
? Math.trunc(value)
|
|
16
|
+
: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function asUnitInterval(value: unknown): number {
|
|
20
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
21
|
+
? Math.max(0, Math.min(1, value))
|
|
22
|
+
: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function normalizeMedicineWheelCycle(value: unknown): ApiMedicineWheelCycle | null {
|
|
26
|
+
if (!value || typeof value !== "object") return null;
|
|
27
|
+
|
|
28
|
+
const cycle = value as Record<string, unknown>;
|
|
29
|
+
if (typeof cycle.id !== "string" || typeof cycle.research_question !== "string") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
id: cycle.id,
|
|
35
|
+
research_question: cycle.research_question,
|
|
36
|
+
start_date:
|
|
37
|
+
typeof cycle.start_date === "string"
|
|
38
|
+
? cycle.start_date
|
|
39
|
+
: new Date(0).toISOString(),
|
|
40
|
+
current_direction:
|
|
41
|
+
typeof cycle.current_direction === "string" &&
|
|
42
|
+
DIRECTION_NAMES.has(cycle.current_direction as DirectionName)
|
|
43
|
+
? (cycle.current_direction as DirectionName)
|
|
44
|
+
: "east",
|
|
45
|
+
beats: Array.isArray(cycle.beats)
|
|
46
|
+
? cycle.beats.filter((beatId): beatId is string => typeof beatId === "string")
|
|
47
|
+
: [],
|
|
48
|
+
ceremonies_conducted: asNonNegativeInteger(cycle.ceremonies_conducted),
|
|
49
|
+
relations_mapped: asNonNegativeInteger(cycle.relations_mapped),
|
|
50
|
+
wilson_alignment: asUnitInterval(cycle.wilson_alignment),
|
|
51
|
+
ocap_compliant: cycle.ocap_compliant === true,
|
|
52
|
+
...(cycle.archived === true ? { archived: true } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function extractCycles(response: unknown): ApiMedicineWheelCycle[] {
|
|
57
|
+
const candidate = Array.isArray(response)
|
|
58
|
+
? response
|
|
59
|
+
: response && typeof response === "object" && Array.isArray((response as CycleListResponse).cycles)
|
|
60
|
+
? ((response as CycleListResponse).cycles as unknown[])
|
|
61
|
+
: [];
|
|
62
|
+
|
|
63
|
+
return candidate
|
|
64
|
+
.map(normalizeMedicineWheelCycle)
|
|
65
|
+
.filter((cycle): cycle is ApiMedicineWheelCycle => cycle !== null);
|
|
66
|
+
}
|
package/lib/store.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
MedicineWheelCycle,
|
|
23
23
|
} from '@/lib/types';
|
|
24
24
|
|
|
25
|
+
import { extractCycles, normalizeMedicineWheelCycle } from './cycle-response';
|
|
25
26
|
import { getJsonlStore } from './jsonl-store';
|
|
26
27
|
|
|
27
28
|
// JSONL store for sync operations (used by seeding and legacy code paths)
|
|
@@ -151,7 +152,7 @@ export function createBeat(data: Omit<NarrativeBeat, 'id' | 'timestamp'> & { id?
|
|
|
151
152
|
|
|
152
153
|
export function getAllCycles(): MedicineWheelCycle[] {
|
|
153
154
|
const { active, archived } = store.getAllCycles();
|
|
154
|
-
return [...active, ...archived]
|
|
155
|
+
return extractCycles([...active, ...archived]);
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
export function createCycle(data: { research_question: string; current_direction?: string }): MedicineWheelCycle {
|
|
@@ -168,7 +169,7 @@ export function createCycle(data: { research_question: string; current_direction
|
|
|
168
169
|
ocap_compliant: false,
|
|
169
170
|
};
|
|
170
171
|
store.createCycle(cycle as any);
|
|
171
|
-
return cycle;
|
|
172
|
+
return normalizeMedicineWheelCycle(cycle) ?? cycle;
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
// ── Seed Data ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medicine-wheel/app",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Medicine Wheel — Interactive visual layer for Indigenous relational research with Four Directions, ceremonies, and narrative arcs",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mw": "dist/cli/mw.js",
|
|
@@ -74,24 +74,24 @@
|
|
|
74
74
|
"release:major": "npm run version:major && npm run publish:all && npm run release:commit"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@medicine-wheel/ceremony-protocol": "^0.4.
|
|
78
|
-
"@medicine-wheel/community-review": "^0.4.
|
|
79
|
-
"@medicine-wheel/consent-lifecycle": "^0.4.
|
|
80
|
-
"@medicine-wheel/data-store": "^0.4.
|
|
81
|
-
"@medicine-wheel/data-store-postgres": "^0.4.
|
|
82
|
-
"@medicine-wheel/fire-keeper": "^0.4.
|
|
83
|
-
"@medicine-wheel/graph-viz": "^0.4.
|
|
84
|
-
"@medicine-wheel/importance-unit": "^0.4.
|
|
77
|
+
"@medicine-wheel/ceremony-protocol": "^0.4.7",
|
|
78
|
+
"@medicine-wheel/community-review": "^0.4.7",
|
|
79
|
+
"@medicine-wheel/consent-lifecycle": "^0.4.7",
|
|
80
|
+
"@medicine-wheel/data-store": "^0.4.7",
|
|
81
|
+
"@medicine-wheel/data-store-postgres": "^0.4.7",
|
|
82
|
+
"@medicine-wheel/fire-keeper": "^0.4.7",
|
|
83
|
+
"@medicine-wheel/graph-viz": "^0.4.7",
|
|
84
|
+
"@medicine-wheel/importance-unit": "^0.4.7",
|
|
85
85
|
"@medicine-wheel/mcp": "^4.4.5",
|
|
86
|
-
"@medicine-wheel/narrative-engine": "^0.4.
|
|
87
|
-
"@medicine-wheel/ontology-core": "^0.4.
|
|
88
|
-
"@medicine-wheel/prompt-decomposition": "^0.4.
|
|
89
|
-
"@medicine-wheel/relational-index": "^0.4.
|
|
90
|
-
"@medicine-wheel/relational-query": "^0.4.
|
|
91
|
-
"@medicine-wheel/session-reader": "^0.4.
|
|
92
|
-
"@medicine-wheel/storage-provider": "^0.4.
|
|
93
|
-
"@medicine-wheel/transformation-tracker": "^0.4.
|
|
94
|
-
"@medicine-wheel/ui-components": "^0.4.
|
|
86
|
+
"@medicine-wheel/narrative-engine": "^0.4.7",
|
|
87
|
+
"@medicine-wheel/ontology-core": "^0.4.7",
|
|
88
|
+
"@medicine-wheel/prompt-decomposition": "^0.4.7",
|
|
89
|
+
"@medicine-wheel/relational-index": "^0.4.7",
|
|
90
|
+
"@medicine-wheel/relational-query": "^0.4.7",
|
|
91
|
+
"@medicine-wheel/session-reader": "^0.4.7",
|
|
92
|
+
"@medicine-wheel/storage-provider": "^0.4.7",
|
|
93
|
+
"@medicine-wheel/transformation-tracker": "^0.4.7",
|
|
94
|
+
"@medicine-wheel/ui-components": "^0.4.7",
|
|
95
95
|
"@neondatabase/serverless": "^0.10.0",
|
|
96
96
|
"clsx": "^2.1.1",
|
|
97
97
|
"lucide-react": "^0.475.0",
|