@medicine-wheel/app 0.17.7 → 0.17.9
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/app/episodes/[id]/page.tsx +25 -1
- package/components/episode-lineage-web.tsx +10 -3
- package/package.json +27 -27
|
@@ -30,9 +30,12 @@ import Link from "next/link";
|
|
|
30
30
|
import type { RelationalNode, RelationalEdge } from "@/lib/types";
|
|
31
31
|
import { EpisodeFilesPanel } from "@/components/episode-files-panel";
|
|
32
32
|
import { EpisodeHoldings } from "@/components/episode-holdings";
|
|
33
|
+
import { EpisodeLineageWeb, type LineageEpisode } from "@/components/episode-lineage-web";
|
|
33
34
|
import {
|
|
34
35
|
chronicleKey,
|
|
36
|
+
episodeDateOf,
|
|
35
37
|
episodeFolderOfNode,
|
|
38
|
+
lineageEdges,
|
|
36
39
|
relationsOf,
|
|
37
40
|
type EpisodeRelation,
|
|
38
41
|
type LineageEdge,
|
|
@@ -113,6 +116,8 @@ export default function EpisodePage({ params }: { params: Promise<{ id: string }
|
|
|
113
116
|
const [episode, setEpisode] = useState<EpisodeNode | null>(null);
|
|
114
117
|
const [attention, setAttention] = useState<EpisodeNode[]>([]);
|
|
115
118
|
const [web, setWeb] = useState<{ nodes: EpisodeNode[]; edges: RelationalEdge[] } | null>(null);
|
|
119
|
+
// The whole chronicle's lineage, for the picture: two relations away needs more than this episode's web.
|
|
120
|
+
const [chronicle, setChronicle] = useState<{ episodes: LineageEpisode[]; edges: LineageEdge[] } | null>(null);
|
|
116
121
|
const [before, setBefore] = useState<EpisodeNode | null>(null);
|
|
117
122
|
const [after, setAfter] = useState<EpisodeNode | null>(null);
|
|
118
123
|
const [loading, setLoading] = useState(true);
|
|
@@ -122,10 +127,11 @@ export default function EpisodePage({ params }: { params: Promise<{ id: string }
|
|
|
122
127
|
setLoading(true);
|
|
123
128
|
try {
|
|
124
129
|
const encoded = encodeURIComponent(id);
|
|
125
|
-
const [nodeRes, attentionRes, webRes] = await Promise.all([
|
|
130
|
+
const [nodeRes, attentionRes, webRes, edgesRes] = await Promise.all([
|
|
126
131
|
fetch(`/api/nodes?kind=chronicle_episode&limit=all`),
|
|
127
132
|
fetch(`/api/nodes?kind=attention&parent_id=${encoded}&limit=all`),
|
|
128
133
|
fetch(`/api/nodes/${encoded}/web?depth=1`),
|
|
134
|
+
fetch(`/api/edges?limit=all`).catch(() => null),
|
|
129
135
|
]);
|
|
130
136
|
|
|
131
137
|
if (!nodeRes.ok) throw new Error(`Episodes: ${nodeRes.status}`);
|
|
@@ -138,6 +144,19 @@ export default function EpisodePage({ params }: { params: Promise<{ id: string }
|
|
|
138
144
|
setBefore(at > 0 ? ordered[at - 1] : null);
|
|
139
145
|
setAfter(at >= 0 && at < ordered.length - 1 ? ordered[at + 1] : null);
|
|
140
146
|
|
|
147
|
+
// The picture is a second answer: without the edges, the lists below still stand.
|
|
148
|
+
if (edgesRes?.ok) {
|
|
149
|
+
const body = await edgesRes.json();
|
|
150
|
+
const allEdges: LineageEdge[] = Array.isArray(body) ? body : (body.edges ?? []);
|
|
151
|
+
const ids = new Set(all.map((n) => n.id));
|
|
152
|
+
setChronicle({
|
|
153
|
+
episodes: ordered.map((n) => ({ id: n.id, name: n.name, direction: n.direction, date: episodeDateOf(n) })),
|
|
154
|
+
edges: lineageEdges(allEdges, (other) => ids.has(other)),
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
setChronicle(null);
|
|
158
|
+
}
|
|
159
|
+
|
|
141
160
|
setAttention(attentionRes.ok ? ((await attentionRes.json()).nodes ?? []) : []);
|
|
142
161
|
|
|
143
162
|
// A 404 here means the node is not in the wheel at all — distinct from a
|
|
@@ -257,6 +276,11 @@ export default function EpisodePage({ params }: { params: Promise<{ id: string }
|
|
|
257
276
|
<h2 className="text-sm font-semibold mb-2">
|
|
258
277
|
Lineage {lineage.length > 0 && `(${lineage.length})`}
|
|
259
278
|
</h2>
|
|
279
|
+
{lineage.length > 0 && chronicle && (
|
|
280
|
+
<div className="mb-3">
|
|
281
|
+
<EpisodeLineageWeb episodes={chronicle.episodes} edges={chronicle.edges} around={id} panel={false} />
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
260
284
|
{lineage.length === 0 ? (
|
|
261
285
|
<p className="text-sm text-muted-foreground">
|
|
262
286
|
No relation to another episode has been woven yet.
|
|
@@ -38,6 +38,8 @@ export function EpisodeLineageWeb({
|
|
|
38
38
|
episodes,
|
|
39
39
|
edges,
|
|
40
40
|
matches,
|
|
41
|
+
around,
|
|
42
|
+
panel = true,
|
|
41
43
|
}: {
|
|
42
44
|
/** In chronicle order, oldest first. */
|
|
43
45
|
episodes: LineageEpisode[];
|
|
@@ -45,9 +47,13 @@ export function EpisodeLineageWeb({
|
|
|
45
47
|
edges: LineageEdge[];
|
|
46
48
|
/** When a filter is typed: the episodes it matches. Their relatives stay, as rings. */
|
|
47
49
|
matches?: Set<string> | null;
|
|
50
|
+
/** Open centred on this episode and its relatives (an episode's own page). */
|
|
51
|
+
around?: string;
|
|
52
|
+
/** The selected episode's relations under the web. Off where the page lists them already. */
|
|
53
|
+
panel?: boolean;
|
|
48
54
|
}) {
|
|
49
55
|
const router = useRouter();
|
|
50
|
-
const [selected, setSelected] = useState<string | null>(null);
|
|
56
|
+
const [selected, setSelected] = useState<string | null>(around ?? null);
|
|
51
57
|
|
|
52
58
|
const webEpisodes = useMemo(
|
|
53
59
|
() => episodes.map((e) => ({ id: e.id, label: markOf(e.id), title: e.name, direction: e.direction, date: e.date })),
|
|
@@ -84,11 +90,12 @@ export function EpisodeLineageWeb({
|
|
|
84
90
|
matches={matches}
|
|
85
91
|
layout={LAYOUT}
|
|
86
92
|
bandLabels={BAND_LABELS}
|
|
87
|
-
startAt="end"
|
|
93
|
+
startAt={around ? "start" : "end"}
|
|
94
|
+
defaultView={around ? { scope: "around" } : undefined}
|
|
88
95
|
/>
|
|
89
96
|
</div>
|
|
90
97
|
|
|
91
|
-
{chosen ? (
|
|
98
|
+
{!panel ? null : chosen ? (
|
|
92
99
|
<div className="p-3 border rounded-lg bg-card space-y-2">
|
|
93
100
|
<div className="flex items-baseline justify-between gap-3 flex-wrap">
|
|
94
101
|
<Link href={`/episodes/${encodeURIComponent(chosen.id)}`} className="text-sm font-medium hover:underline">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medicine-wheel/app",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.9",
|
|
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",
|
|
@@ -91,32 +91,32 @@
|
|
|
91
91
|
"release:major": "npm run version:major && npm run publish:all && npm run release:commit"
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
|
-
"@medicine-wheel/ceremonial-diary": "^0.17.
|
|
95
|
-
"@medicine-wheel/ceremony-protocol": "^0.17.
|
|
96
|
-
"@medicine-wheel/community-identity": "^0.17.
|
|
97
|
-
"@medicine-wheel/community-review": "^0.17.
|
|
98
|
-
"@medicine-wheel/consent-lifecycle": "^0.17.
|
|
99
|
-
"@medicine-wheel/creative-orientation": "^0.17.
|
|
100
|
-
"@medicine-wheel/data-store": "^0.17.
|
|
101
|
-
"@medicine-wheel/data-store-postgres": "^0.17.
|
|
102
|
-
"@medicine-wheel/fire-keeper": "^0.17.
|
|
103
|
-
"@medicine-wheel/github-ceremony": "^0.17.
|
|
104
|
-
"@medicine-wheel/graph-viz": "^0.17.
|
|
105
|
-
"@medicine-wheel/honcho": "^0.17.
|
|
106
|
-
"@medicine-wheel/importance-unit": "^0.17.
|
|
107
|
-
"@medicine-wheel/mcp": "^4.17.
|
|
108
|
-
"@medicine-wheel/memory": "^0.17.
|
|
109
|
-
"@medicine-wheel/narrative-cluster": "^0.17.
|
|
110
|
-
"@medicine-wheel/narrative-engine": "^0.17.
|
|
111
|
-
"@medicine-wheel/ontology-core": "^0.17.
|
|
112
|
-
"@medicine-wheel/perception-layer": "^0.17.
|
|
113
|
-
"@medicine-wheel/prompt-decomposition": "^0.17.
|
|
114
|
-
"@medicine-wheel/relational-index": "^0.17.
|
|
115
|
-
"@medicine-wheel/relational-query": "^0.17.
|
|
116
|
-
"@medicine-wheel/session-reader": "^0.17.
|
|
117
|
-
"@medicine-wheel/storage-provider": "^0.17.
|
|
118
|
-
"@medicine-wheel/transformation-tracker": "^0.17.
|
|
119
|
-
"@medicine-wheel/ui-components": "^0.17.
|
|
94
|
+
"@medicine-wheel/ceremonial-diary": "^0.17.9",
|
|
95
|
+
"@medicine-wheel/ceremony-protocol": "^0.17.9",
|
|
96
|
+
"@medicine-wheel/community-identity": "^0.17.9",
|
|
97
|
+
"@medicine-wheel/community-review": "^0.17.9",
|
|
98
|
+
"@medicine-wheel/consent-lifecycle": "^0.17.9",
|
|
99
|
+
"@medicine-wheel/creative-orientation": "^0.17.9",
|
|
100
|
+
"@medicine-wheel/data-store": "^0.17.9",
|
|
101
|
+
"@medicine-wheel/data-store-postgres": "^0.17.9",
|
|
102
|
+
"@medicine-wheel/fire-keeper": "^0.17.9",
|
|
103
|
+
"@medicine-wheel/github-ceremony": "^0.17.9",
|
|
104
|
+
"@medicine-wheel/graph-viz": "^0.17.9",
|
|
105
|
+
"@medicine-wheel/honcho": "^0.17.9",
|
|
106
|
+
"@medicine-wheel/importance-unit": "^0.17.9",
|
|
107
|
+
"@medicine-wheel/mcp": "^4.17.9",
|
|
108
|
+
"@medicine-wheel/memory": "^0.17.9",
|
|
109
|
+
"@medicine-wheel/narrative-cluster": "^0.17.9",
|
|
110
|
+
"@medicine-wheel/narrative-engine": "^0.17.9",
|
|
111
|
+
"@medicine-wheel/ontology-core": "^0.17.9",
|
|
112
|
+
"@medicine-wheel/perception-layer": "^0.17.9",
|
|
113
|
+
"@medicine-wheel/prompt-decomposition": "^0.17.9",
|
|
114
|
+
"@medicine-wheel/relational-index": "^0.17.9",
|
|
115
|
+
"@medicine-wheel/relational-query": "^0.17.9",
|
|
116
|
+
"@medicine-wheel/session-reader": "^0.17.9",
|
|
117
|
+
"@medicine-wheel/storage-provider": "^0.17.9",
|
|
118
|
+
"@medicine-wheel/transformation-tracker": "^0.17.9",
|
|
119
|
+
"@medicine-wheel/ui-components": "^0.17.9",
|
|
120
120
|
"@miadi/episode-vessel": "^0.2.1",
|
|
121
121
|
"@neondatabase/serverless": "^0.10.0",
|
|
122
122
|
"@xyflow/react": "^12.3.0",
|