dreamteamer 0.20.0 → 0.21.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dreamteamer",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "A workspace compiler for coding agents — schema-validated records as plain files over git, compiled into every harness",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Gilad Khen <giladkhen@gmail.com>",
@@ -227,6 +227,45 @@ Two gates around it:
227
227
  - ⚠ **An overlay can add fields but cannot remove an inherited one.** If the shape is wrong for
228
228
  the module rather than just for this workspace, fix the base.
229
229
 
230
+ ## `x-choices` — what an enum VALUE looks like
231
+
232
+ An enum value carries a label and nothing else by default: a surface gets `{ text, value }` and
233
+ draws the value. `x-choices` is an OPTIONAL sparse map, keyed by the value, that gives a surface
234
+ more to draw with — a board grouping by the field, a dropdown in a form, anything reading
235
+ `edit_options.choices`.
236
+
237
+ ```yaml
238
+ lane:
239
+ type: string
240
+ enum: [alpha, bravo, charlie]
241
+ x-choices:
242
+ alpha:
243
+ label: Alpha team # what a surface shows; the stored VALUE is still `alpha`
244
+ description: the one that ships
245
+ icon: rocket # a codicon name …
246
+ color: charts.blue # a theme colour id — the accent
247
+ background: charts.blue # … and the fill
248
+ bravo:
249
+ icon: assets/icons/lucide/anchor # … OR a reference to a `codec: file` record
250
+ ```
251
+
252
+ - **Sparse and additive.** Decorate one value, or none. A value with no entry projects exactly as it
253
+ did before this keyword existed, so adding it changes nothing that already works.
254
+ - **`enum` still owns the value set AND its order.** A map key cannot add, remove or reorder a
255
+ value — which matters, because a grouped view takes its band order from the enum.
256
+ - **Five keys, and only five** — `label` · `description` · `icon` · `color` · `background`, each an
257
+ optional string. Anything else in an entry is dropped: the projection copies by name, so a
258
+ descriptor cannot inject keys into a contract every surface reads.
259
+ - **`label` becomes `text`.** So a workspace can relabel a value without touching the value, and no
260
+ stored record moves.
261
+ - **`icon` is a codicon name or a reference to a record of a `codec: file` collection.** A codicon
262
+ name never contains a slash and a record reference always does, so the surface decides which
263
+ without a second keyword.
264
+ - **Colours are theme colour ids, not hex.** A hex is authored against one theme and wrong in the
265
+ other.
266
+ - **Both mistakes warn rather than fail** — a key that is not one of the enum's values, and the
267
+ keyword on a field with no enum. See the message catalog below.
268
+
230
269
  ## the reference contract — `x-reference` across the module graph
231
270
 
232
271
  Every `x-reference` target must be one of: a **core** collection (the entity kinds plus `repos`)
@@ -291,6 +330,8 @@ collection author actually meets. (⚠ = warning: it compiled, and you should st
291
330
  | `cyclic module dependencies: a → b → a` | concept-level links declared as module deps | the collection belongs in `peerDependencies` |
292
331
  | relation refusals (`stamps a mirror onto…`, `declared on both sides…`) | the relation rules | `data-modeling.md` Part VI |
293
332
  | ⚠ `x-unique on "f" is inert` | a relation keyword with no relation — nothing enforces it | declare the inverse, or drop it |
333
+ | ⚠ `x-choices on "f" has an entry for "k"` | it decorates enum VALUES and `k` is not one — a typo, or a value since removed | fix the spelling, or drop the entry |
334
+ | ⚠ `x-choices on "f" is inert` | the keyword on a field that declares no enum — nothing reads it | give the field an enum, or drop the keyword |
294
335
  | ⚠ `collection … has no description` | it renders as a bare name in the orientation block every session loads | write the sentence (`data-modeling.md` §18) |
295
336
  | ⚠ `module "…" contributed no recognised sources` | its folders match no kind and it ships no UI bundle | usually a layout or naming mistake |
296
337
  | ⚠ `module X: <channel> copy shadows <channel> copy` | the same module delivered twice — the more local wins (npm-link semantics) | intended for dev; otherwise remove one |
package/src/compile.js CHANGED
@@ -1315,6 +1315,25 @@ export function compile({ root, pkg }) {
1315
1315
  if (h['x-unique'] === true && h['x-inverse'] === undefined && h['x-inverse-of'] === undefined) {
1316
1316
  console.warn(`⚠ collection ${name}: x-unique on "${fieldName}" is inert — it is a RELATION keyword, enforced only while the store maintains a mirror, and this field declares no x-inverse. Nothing constrains the value. Declare the relation (dreamteamer update-field ${name} --name ${fieldName} --inverse) or drop x-unique.`);
1317
1317
  }
1318
+ // `x-choices` decorates ENUM VALUES (presentation.js#choiceRow, 0.21.0), and both ways of
1319
+ // getting it wrong are SILENT: a key that is not a value decorates nothing, and the keyword
1320
+ // on a non-enum field is read by no one at all. Either way the author sees no error and no
1321
+ // decoration — on a surface they are probably not looking at while editing the descriptor.
1322
+ // ⚠ WARNINGS, not failures, for the same reason as x-unique directly above: neither breaks
1323
+ // anything today, and a descriptor mid-edit must stay compilable.
1324
+ const choices = h['x-choices'];
1325
+ if (choices && typeof choices === 'object' && !Array.isArray(choices)) {
1326
+ const values = Array.isArray(h.enum) ? h.enum.map(String) : null;
1327
+ if (!values) {
1328
+ console.warn(`⚠ collection ${name}: x-choices on "${fieldName}" is inert — it decorates the values of an enum, and this field declares no enum. Nothing reads it.`);
1329
+ } else {
1330
+ // Named per offending key rather than "some keys are wrong", and the legal values are
1331
+ // quoted so the fix needs no second lookup — the same shape as every other warning here.
1332
+ for (const k of Object.keys(choices)) {
1333
+ if (!values.includes(k)) console.warn(`⚠ collection ${name}: x-choices on "${fieldName}" has an entry for "${k}", which is not one of its enum values (${values.join(', ')}) — it decorates nothing. Fix the spelling, or drop the entry.`);
1334
+ }
1335
+ }
1336
+ }
1318
1337
  }
1319
1338
  const rt = path.join('collections', `${name}.collection.yaml`);
1320
1339
  entries.set(rt, { sources: descriptorSources, bytes: Buffer.from(dump(merged)) });
@@ -152,6 +152,33 @@ function titleTemplateOf(prop, descriptors) {
152
152
  return typeof first === 'string' && first.length > 0 && inherited.every((v) => v === first) ? first : undefined;
153
153
  }
154
154
 
155
+ // The keys a descriptor may attach to an enum VALUE through `x-choices` (0.21.0), beyond the label.
156
+ // Copied BY NAME rather than spread: a descriptor is authored data, and spreading whatever it
157
+ // happens to carry would let a workspace inject arbitrary keys into a contract every surface reads.
158
+ // One line is the whole vocabulary, which is also what makes it readable from the consuming side.
159
+ //
160
+ // `icon` is a codicon name OR a reference to a record of a `codec: file` collection — the surface
161
+ // decides which by whether it resolves as a reference, so no second keyword and no set of known
162
+ // collection names. `color`/`background` are theme colour ids for the same reason a hex is not one:
163
+ // a hex is authored against one theme and wrong in the other.
164
+ const CHOICE_KEYS = ['description', 'icon', 'color', 'background'];
165
+
166
+ /**
167
+ * One `choices` row: `{ text, value }` as it has always been, plus whatever `x-choices` declared.
168
+ *
169
+ * ⚠ PURELY ADDITIVE. A value with no entry projects byte-identically to what it projected before
170
+ * this existed, which is the only reason not one of the descriptors already in the wild had to
171
+ * change. `label` arrives as `text` — the key every surface already reads — so a workspace can
172
+ * relabel a value without touching the value, and a stored record never moves.
173
+ */
174
+ function choiceRow(v, entry) {
175
+ const row = { text: String(entry?.label ?? v), value: v };
176
+ if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
177
+ for (const k of CHOICE_KEYS) if (typeof entry[k] === 'string' && entry[k].length > 0) row[k] = entry[k];
178
+ }
179
+ return row;
180
+ }
181
+
155
182
  function fieldRow(d, name, prop, isRequired, descriptors) {
156
183
  const meta = { collection: d.name, field: name };
157
184
  if (isRequired) meta.required = true;
@@ -233,7 +260,7 @@ function fieldRow(d, name, prop, isRequired, descriptors) {
233
260
  }
234
261
 
235
262
  if (Array.isArray(prop.enum) && prop.enum.length > 0) {
236
- meta.edit_options = { choices: prop.enum.map((v) => ({ text: String(v), value: v })) };
263
+ meta.edit_options = { choices: prop.enum.map((v) => choiceRow(v, prop['x-choices']?.[v])) };
237
264
  }
238
265
  const tpl = titleTemplateOf(prop, descriptors);
239
266
  if (typeof tpl === 'string' && tpl.length > 0) meta.view_options = { ...meta.view_options, template: tpl };