11---
22import {
3+ companionRows ,
34 loadInstances ,
45 kindRows ,
56 layerDisagreements ,
67 sharedRequiredFields ,
8+ shapeDifferences ,
79} from " ../lib/instances" ;
10+ import type { ManifestKind } from " ../lib/instances" ;
811
912const instances = loadInstances ();
1013const rows = kindRows (instances );
1114const shared = rows .filter ((r ) => r .shared );
1215const local = rows .filter ((r ) => ! r .shared );
1316const disagreements = layerDisagreements (rows );
17+ const differingShapes = shapeDifferences (rows );
1418const base = import .meta .env .BASE_URL .replace (/ \/ $ / , " " );
19+
20+ // A directory kind that enumerates nothing is not the same as one that declares undeclared
21+ // files legitimate. Both would render as an absent companion table, so `allow` is said out loud.
22+ const openSet = (kind : ManifestKind ) => kind .additionalCompanions === " allow" ;
1523---
1624<div class =" not-prose mt-8" >
1725 <div class =" rounded-lg border p-4 text-sm" style =" border-color: var(--rule); background: var(--graphite)" >
1826 <strong >Generated</strong > from each instance's own schemas. Every required-metadata table
1927 below is derived from that instance's zod definitions, not transcribed — so a kind that
20- changes upstream changes here, and cannot quietly disagree.
28+ changes upstream changes here, and cannot quietly disagree. A kind also declares its
29+ <strong >shape</strong > and its <strong >companions</strong >: each companion carries a
30+ requirement level (<code >required</code > / <code >recommended</code > / <code >optional</code >)
31+ and a disposition saying where it may travel — <code >foundry-only</code > never leaves the
32+ Foundry, <code >cast-input</code > is read while casting but not shipped,
33+ <code >bundled</code > is carried into the artifact.
2134 <ul class =" mt-2 space-y-0.5" style =" color: var(--muted)" >
2235 { instances .map ((i ) => (
2336 <li >
@@ -37,6 +50,17 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
3750 </p >
3851 )}
3952
53+ { differingShapes .length > 0 && (
54+ <p class = " mt-4 rounded-lg border p-4 text-sm" style = " border-color: var(--rule); color: var(--muted)" >
55+ <strong style = " color: var(--ink)" >Same kind, different shape:</strong >{ " " }
56+ { differingShapes .join (" , " )} — a flat file in one instance, a directory in the other. Not
57+ drift, and not smoothed over: the two are the same kind carrying the same required
58+ metadata, laid out differently because one domain needed files beside the note and the
59+ other did not. Before kinds declared a shape this difference lived inside each instance's
60+ collection glob and reached no manifest, so this page could not have reported it.
61+ </p >
62+ )}
63+
4064 <h2 class =" mt-10 text-2xl font-bold" style =" font-family: var(--font-display)" >
4165 Substrate kinds
4266 </h2 >
@@ -54,7 +78,25 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
5478 <dl class = " mt-3 space-y-3 text-sm" >
5579 { row .present .map ((i ) => (
5680 <div >
57- <dt class = " font-semibold" >{ i .title } </dt >
81+ <dt class = " font-semibold" >
82+ { i .title }
83+ { row .by [i .slug ]! .shape && (
84+ <span
85+ class = " ml-2 rounded px-1.5 py-0.5 text-xs font-normal"
86+ style = " background: var(--graphite); color: var(--muted)"
87+ >
88+ { row .by [i .slug ]! .shape }
89+ </span >
90+ )}
91+ { openSet (row .by [i .slug ]! ) && (
92+ <span
93+ class = " ml-1 rounded px-1.5 py-0.5 text-xs font-normal"
94+ style = " background: var(--graphite); color: var(--glow)"
95+ >
96+ open companion set
97+ </span >
98+ )}
99+ </dt >
58100 <dd class = " mt-0.5" style = " color: var(--muted)" >{ row .by [i .slug ]! .summary } </dd >
59101 <dd class = " mt-1" >
60102 <span style = " color: var(--muted)" >requires</span >{ " " }
@@ -71,6 +113,54 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
71113 { sharedRequiredFields (row ).map ((f ) => <code class = " mr-1" >{ f } </code >)}
72114 </p >
73115 )}
116+ { companionRows (row ).length > 0 && (
117+ <div class = " mt-3 border-t pt-3" style = " border-color: var(--rule)" >
118+ <p class = " text-sm" ><strong >Companions</strong ></p >
119+ <div class = " mt-2 overflow-x-auto" >
120+ <table class = " w-full text-left text-sm" >
121+ <thead >
122+ <tr style = " color: var(--muted)" >
123+ <th class = " pb-1 pr-4 font-normal" >file</th >
124+ { row .present .map ((i ) => (
125+ <th class = " pb-1 pr-4 font-normal" >{ i .title } </th >
126+ ))}
127+ </tr >
128+ </thead >
129+ <tbody >
130+ { companionRows (row ).map ((companion ) => (
131+ <tr class = " border-t align-top" style = " border-color: var(--rule)" >
132+ <td class = " py-1.5 pr-4" >
133+ <code style = { companion .identical ? " color: var(--glow)" : undefined } >
134+ { companion .file }
135+ </code >
136+ </td >
137+ { row .present .map ((i ) => (
138+ <td class = " py-1.5 pr-4" >
139+ { companion .by [i .slug ] ? (
140+ <span >
141+ { companion .by [i .slug ]! .requirement }
142+ <span style = " color: var(--muted)" >
143+ { " · " } { companion .by [i .slug ]! .disposition }
144+ </span >
145+ </span >
146+ ) : (
147+ <span style = " color: var(--muted)" >—</span >
148+ )}
149+ </td >
150+ ))}
151+ </tr >
152+ ))}
153+ </tbody >
154+ </table >
155+ </div >
156+ { companionRows (row ).some ((c ) => c .identical ) && (
157+ <p class = " mt-2 text-sm" style = " color: var(--muted)" >
158+ Highlighted files are declared by both instances at the same requirement and the
159+ same disposition — the layout itself transferring, not just the kind's name.
160+ </p >
161+ )}
162+ </div >
163+ )}
74164 </section >
75165 ))}
76166
@@ -93,6 +183,22 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
93183 .map ((row ) => (
94184 <div class = " mt-3 rounded-lg border p-4" style = " border-color: var(--rule)" >
95185 <code style = " color: var(--ember)" >{ row .kind } </code >
186+ { row .by [instance .slug ]! .shape && (
187+ <span
188+ class = " ml-2 rounded px-1.5 py-0.5 text-xs"
189+ style = " background: var(--graphite); color: var(--muted)"
190+ >
191+ { row .by [instance .slug ]! .shape }
192+ </span >
193+ )}
194+ { openSet (row .by [instance .slug ]! ) && (
195+ <span
196+ class = " ml-1 rounded px-1.5 py-0.5 text-xs"
197+ style = " background: var(--graphite); color: var(--glow)"
198+ >
199+ open companion set
200+ </span >
201+ )}
96202 <p class = " mt-1 text-sm" style = " color: var(--muted)" >
97203 { row .by [instance .slug ]! .summary }
98204 </p >
@@ -102,6 +208,18 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, "");
102208 .filter ((f ) => f .required )
103209 .map ((f ) => <code class = " mr-1" >{ f .name } </code >)}
104210 </p >
211+ { (row .by [instance .slug ]! .companions ?? []).length > 0 && (
212+ <ul class = " mt-2 space-y-0.5 text-sm" >
213+ { row .by [instance .slug ]! .companions ! .map ((companion ) => (
214+ <li >
215+ <code >{ companion .file } </code >{ " " }
216+ <span style = " color: var(--muted)" >
217+ { companion .requirement } · { companion .disposition }
218+ </span >
219+ </li >
220+ ))}
221+ </ul >
222+ )}
105223 </div >
106224 ))}
107225 </div >
0 commit comments