@medicine-wheel/app 0.17.7 → 0.17.8

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.
@@ -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.7",
3
+ "version": "0.17.8",
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.7",
95
- "@medicine-wheel/ceremony-protocol": "^0.17.7",
96
- "@medicine-wheel/community-identity": "^0.17.7",
97
- "@medicine-wheel/community-review": "^0.17.7",
98
- "@medicine-wheel/consent-lifecycle": "^0.17.7",
99
- "@medicine-wheel/creative-orientation": "^0.17.7",
100
- "@medicine-wheel/data-store": "^0.17.7",
101
- "@medicine-wheel/data-store-postgres": "^0.17.7",
102
- "@medicine-wheel/fire-keeper": "^0.17.7",
103
- "@medicine-wheel/github-ceremony": "^0.17.7",
104
- "@medicine-wheel/graph-viz": "^0.17.7",
105
- "@medicine-wheel/honcho": "^0.17.7",
106
- "@medicine-wheel/importance-unit": "^0.17.7",
107
- "@medicine-wheel/mcp": "^4.17.7",
108
- "@medicine-wheel/memory": "^0.17.7",
109
- "@medicine-wheel/narrative-cluster": "^0.17.7",
110
- "@medicine-wheel/narrative-engine": "^0.17.7",
111
- "@medicine-wheel/ontology-core": "^0.17.7",
112
- "@medicine-wheel/perception-layer": "^0.17.7",
113
- "@medicine-wheel/prompt-decomposition": "^0.17.7",
114
- "@medicine-wheel/relational-index": "^0.17.7",
115
- "@medicine-wheel/relational-query": "^0.17.7",
116
- "@medicine-wheel/session-reader": "^0.17.7",
117
- "@medicine-wheel/storage-provider": "^0.17.7",
118
- "@medicine-wheel/transformation-tracker": "^0.17.7",
119
- "@medicine-wheel/ui-components": "^0.17.7",
94
+ "@medicine-wheel/ceremonial-diary": "^0.17.8",
95
+ "@medicine-wheel/ceremony-protocol": "^0.17.8",
96
+ "@medicine-wheel/community-identity": "^0.17.8",
97
+ "@medicine-wheel/community-review": "^0.17.8",
98
+ "@medicine-wheel/consent-lifecycle": "^0.17.8",
99
+ "@medicine-wheel/creative-orientation": "^0.17.8",
100
+ "@medicine-wheel/data-store": "^0.17.8",
101
+ "@medicine-wheel/data-store-postgres": "^0.17.8",
102
+ "@medicine-wheel/fire-keeper": "^0.17.8",
103
+ "@medicine-wheel/github-ceremony": "^0.17.8",
104
+ "@medicine-wheel/graph-viz": "^0.17.8",
105
+ "@medicine-wheel/honcho": "^0.17.8",
106
+ "@medicine-wheel/importance-unit": "^0.17.8",
107
+ "@medicine-wheel/mcp": "^4.17.8",
108
+ "@medicine-wheel/memory": "^0.17.8",
109
+ "@medicine-wheel/narrative-cluster": "^0.17.8",
110
+ "@medicine-wheel/narrative-engine": "^0.17.8",
111
+ "@medicine-wheel/ontology-core": "^0.17.8",
112
+ "@medicine-wheel/perception-layer": "^0.17.8",
113
+ "@medicine-wheel/prompt-decomposition": "^0.17.8",
114
+ "@medicine-wheel/relational-index": "^0.17.8",
115
+ "@medicine-wheel/relational-query": "^0.17.8",
116
+ "@medicine-wheel/session-reader": "^0.17.8",
117
+ "@medicine-wheel/storage-provider": "^0.17.8",
118
+ "@medicine-wheel/transformation-tracker": "^0.17.8",
119
+ "@medicine-wheel/ui-components": "^0.17.8",
120
120
  "@miadi/episode-vessel": "^0.2.1",
121
121
  "@neondatabase/serverless": "^0.10.0",
122
122
  "@xyflow/react": "^12.3.0",