@wildwinter/app-shell 0.53.0 → 0.54.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/README.md CHANGED
@@ -219,10 +219,12 @@ const outcome = await history.applyUndo(); // { ok, label } or { ok: false, er
219
219
 
220
220
  **`@wildwinter/app-shell/value-rows`**: a row for a value of any declared type,
221
221
  for an extension's fields. The seven scalars are the field row; a `reference`
222
- picks one of the host's nodes, a `record` is its fields, a `map` is keyed by a
222
+ picks one of the host's nodes, a `pick` one choice from its sources ("auto"
223
+ first, then its fixed words, the host's nodes of a kind, and the extension's
224
+ project lists, through the adapter's optional `list(name)`), a `record` is its fields, a `map` is keyed by a
223
225
  host's nodes or by text, a `list` adds, moves and removes items (a new item of
224
226
  an id list gets a fresh id). The checks are `@wildwinter/toolkit/extensions`'
225
- (an optional peer, 0.2.0 or later), so the editor and validation agree.
227
+ (an optional peer, 0.2.5 or later), so the editor and validation agree.
226
228
 
227
229
  ```ts
228
230
  import { valueRows } from "@wildwinter/app-shell/value-rows";
@@ -242,6 +244,9 @@ host.append(rows.row(`${nodeId}:${decl.name}`, { decl, value, adapter: { nodes:
242
244
  `valueProblemSentence`, `fieldPhrase`), Patterpad's rules made the family's,
243
245
  with each app's words for its items, kinds and where a value is changed
244
246
  passed in.
247
+ - **v0.54:** the `pick` row, the adapter's `list(name)` for a pick's
248
+ project lists, and the words for a pick's problems (`not-a-choice`,
249
+ `pick-ambiguous`, `pick-clash`). Needs toolkit 0.2.5.
245
250
 
246
251
  ## Roadmap
247
252
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/extension-copy.ts
4
4
  var quoted = (s) => `\u201C${s}\u201D`;
5
+ var curly = (s) => s.replace(/"([^"]*)"/g, (_, x) => quoted(x));
5
6
  var cap = (s) => `${s.charAt(0).toUpperCase()}${s.slice(1)}`;
6
7
  var article = (s) => /^[aeiou]/i.test(s) ? "an" : "a";
7
8
  function fieldPhrase(fields, path) {
@@ -35,6 +36,16 @@ function valueProblemSentence(code, message, w) {
35
36
  case "not-a-value":
36
37
  if (/not among its flags$/.test(message)) return { text: `${on} has a flag that isn't one of its choices.`, next: `Pick from the choices ${place}.` };
37
38
  return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its choices.`, next: `Pick one ${place}.` };
39
+ case "not-a-choice":
40
+ return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its choices.`, next: `Pick one ${place}.` };
41
+ case "pick-ambiguous": {
42
+ const m = / is "([^"]*)", which is (.+)$/.exec(message);
43
+ return m ? { text: `${on} is ${quoted(m[1])}, which is ${curly(m[2])}, so it's ambiguous.`, next: "Rename one of them." } : void 0;
44
+ }
45
+ case "pick-clash": {
46
+ const m = /^"([^"]*)" is (.+), so a value naming it is ambiguous$/.exec(message);
47
+ return m ? { text: `${quoted(m[1])} is ${curly(m[2])}, so ${field} naming it is ambiguous.`, next: "Rename one of them." } : void 0;
48
+ }
38
49
  case "not-a-stage":
39
50
  return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its stages.`, next: `Pick one ${place}.` };
40
51
  case "renamed-stage": {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/extension-copy.ts"],"names":[],"mappings":";;;AAsCO,IAAM,MAAA,GAAS,CAAC,CAAA,KAAsB,CAAA,MAAA,EAAI,CAAC,CAAA,MAAA;AAClD,IAAM,GAAA,GAAM,CAAC,CAAA,KAAsB,CAAA,EAAG,EAAE,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,EAAa,CAAA,EAAG,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AAC5E,IAAM,UAAU,CAAC,CAAA,KAAuB,YAAY,IAAA,CAAK,CAAC,IAAI,IAAA,GAAO,GAAA;AAG9D,SAAS,WAAA,CAAY,QAAmC,IAAA,EAAsB;AACnF,EAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAM,WAAW,EAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,KAAM,EAAE,CAAA;AAC7D,EAAA,MAAM,GAAA,GAAM,OAAO,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,IAAA,KAAS,MAAA,CAAO,CAAC,CAAC,CAAA;AACnD,EAAA,MAAM,QAAA,GAAW,GAAA,EAAK,KAAA,IAAS,MAAA,CAAO,CAAC,CAAA,IAAK,EAAA;AAC5C,EAAA,IAAI,MAAA,CAAO,MAAA,IAAU,CAAA,EAAG,OAAO,QAAA;AAC/B,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,MAAA,CAAO,MAAA,GAAS,CAAC,CAAA;AACrC,EAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,IAAA,CAAK,IAAI,CAAA;AACrC,EAAA,IAAI,KAAA,EAAO,OAAO,CAAA,KAAA,EAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,GAAI,CAAC,CAAA,IAAA,EAAO,QAAQ,CAAA,CAAA;AAC7D,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAI,GAAA,EAAK,MAAA,IAAU,EAAC,EAAI,GAAI,GAAA,EAAK,EAAA,EAAI,MAAA,IAAU,EAAG,CAAA;AACjE,EAAA,OAAO,CAAA,EAAG,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,IAAI,CAAA,EAAG,KAAA,IAAS,IAAI,CAAA,IAAA,EAAO,QAAQ,CAAA,CAAA;AAC5E;AAIO,SAAS,oBAAA,CAAqB,IAAA,EAA0B,OAAA,EAAiB,CAAA,EAAmE;AACjJ,EAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAM,SAAA,EAAW,IAAA,EAAM,OAAM,GAAI,CAAA;AAChD,EAAA,MAAM,EAAA,GAAK,CAAA,EAAG,KAAK,CAAA,IAAA,EAAO,IAAI,CAAA,CAAA;AAC9B,EAAA,MAAM,KAAA,GAAQ,uBAAA,CAAwB,IAAA,CAAK,OAAO,IAAI,CAAC,CAAA;AACvD,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,UAAA;AAAY,MAAA,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,IAAI,CAAA,OAAA,EAAU,QAAQ,KAAK,CAAC,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,CAAA,EAAI;AAAA,IAC5E,KAAK,YAAA;AAAc,MAAA,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,EAAE,CAAA,yBAAA,EAA4B,IAAI,CAAA,SAAA,CAAA,EAAa,IAAA,EAAM,CAAA,aAAA,EAAgB,KAAK,CAAA,CAAA,CAAA,EAAI;AAAA,IACnH,KAAK,WAAA,EAAa;AAAE,MAAA,MAAM,CAAA,GAAI,+CAAA,CAAgD,IAAA,CAAK,OAAO,CAAA;AAAG,MAAA,OAAO,IAAI,EAAE,IAAA,EAAM,CAAA,EAAG,EAAE,OAAO,CAAA,CAAE,CAAC,CAAC,CAAA,MAAA,EAAS,IAAI,CAAA,qBAAA,EAAwB,CAAA,CAAE,CAAC,CAAC,KAAI,GAAI,MAAA;AAAA,IAAW;AAAA,IAC5L,KAAK,WAAA,EAAa;AAAE,MAAA,MAAM,CAAA,GAAI,8CAAA,CAA+C,IAAA,CAAK,OAAO,CAAA;AAAG,MAAA,OAAO,IAAI,EAAE,IAAA,EAAM,CAAA,EAAG,EAAE,OAAO,CAAA,CAAE,CAAC,CAAC,CAAA,MAAA,EAAS,IAAI,CAAA,qBAAA,EAAwB,CAAA,CAAE,CAAC,CAAC,KAAI,GAAI,MAAA;AAAA,IAAW;AAAA,IAC3L,KAAK,aAAA;AACH,MAAA,IAAI,sBAAA,CAAuB,IAAA,CAAK,OAAO,CAAA,EAAG,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,EAAE,CAAA,0CAAA,CAAA,EAA8C,IAAA,EAAM,CAAA,sBAAA,EAAyB,KAAK,CAAA,CAAA,CAAA,EAAI;AACpJ,MAAA,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,EAAE,CAAA,IAAA,EAAO,MAAA,CAAO,KAAA,IAAS,EAAE,CAAC,CAAA,iCAAA,CAAA,EAAqC,IAAA,EAAM,CAAA,SAAA,EAAY,KAAK,CAAA,CAAA,CAAA,EAAI;AAAA,IAChH,KAAK,aAAA;AAAe,MAAA,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,EAAE,CAAA,IAAA,EAAO,MAAA,CAAO,KAAA,IAAS,EAAE,CAAC,CAAA,gCAAA,CAAA,EAAoC,IAAA,EAAM,CAAA,SAAA,EAAY,KAAK,CAAA,CAAA,CAAA,EAAI;AAAA,IACjI,KAAK,eAAA,EAAiB;AACpB,MAAA,MAAM,CAAA,GAAI,mEAAA,CAAoE,IAAA,CAAK,OAAO,CAAA;AAC1F,MAAA,OAAO,CAAA,GAAI,EAAE,IAAA,EAAM,CAAA,EAAG,EAAE,CAAA,IAAA,EAAO,MAAA,CAAO,CAAA,CAAE,CAAC,CAAE,CAAC,CAAA,UAAA,EAAa,IAAI,CAAA,mBAAA,EAAsB,MAAA,CAAO,CAAA,CAAE,CAAC,CAAE,CAAC,CAAA,CAAA,CAAA,EAAK,IAAA,EAAM,CAAA,KAAA,EAAQ,MAAA,CAAO,CAAA,CAAE,CAAC,CAAE,CAAC,CAAA,cAAA,CAAA,EAAiB,GAAI,MAAA;AAAA,IACvJ;AAAA,IACA,KAAK,cAAA,EAAgB;AACnB,MAAA,MAAM,CAAA,GAAI,8DAAA,CAA+D,IAAA,CAAK,OAAO,CAAA;AACrF,MAAA,OAAO,CAAA,GAAI,EAAE,IAAA,EAAM,CAAA,EAAG,EAAE,CAAA,WAAA,EAAc,CAAA,CAAE,SAAS,CAAA,CAAE,CAAC,CAAE,CAAC,CAAA,CAAA,EAAI,OAAO,CAAA,CAAE,CAAC,CAAE,CAAC,CAAA,6BAAA,CAAA,EAAiC,IAAA,EAAM,8BAAA,EAA+B,GAAI,MAAA;AAAA,IACpJ;AAAA,IACA,KAAK,YAAA;AAAc,MAAA,OAAO,EAAE,IAAA,EAAM,CAAA,WAAA,EAAc,KAAK,CAAA,IAAA,EAAO,IAAI,CAAA,WAAA,CAAA,EAAe,IAAA,EAAM,CAAA,YAAA,EAAe,KAAK,CAAA,CAAA,CAAA,EAAI;AAAA,IAC7G,KAAK,cAAA,EAAgB;AAAE,MAAA,MAAM,EAAA,GAAK,2BAAA,CAA4B,IAAA,CAAK,OAAO,IAAI,CAAC,CAAA;AAAG,MAAA,OAAO,EAAE,IAAA,EAAM,CAAA,aAAA,EAAgB,KAAK,CAAA,IAAA,EAAO,IAAI,CAAA,cAAA,EAAiB,MAAA,CAAO,EAAA,IAAM,EAAE,CAAC,CAAA,CAAA,CAAA,EAAK,MAAM,oBAAA,EAAqB;AAAA,IAAG;AAAA,IACrM,KAAK,eAAA;AAAA,IAAiB,KAAK,aAAA;AACzB,MAAA,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,IAAI,CAAA,iBAAA,EAAoB,MAAA,CAAO,KAAK,CAAC,CAAA,QAAA,EAAW,IAAI,CAAA,0BAAA,CAAA,EAA8B,IAAA,EAAM,wCAAA,EAAyC;AAAA,IACrJ;AAAS,MAAA,OAAO,MAAA;AAAA;AAEpB;AAGO,SAAS,aAAA,CAAc,WAAmB,CAAA,EAAoE;AACnH,EAAA,OAAO,IAAI,CAAA,EAAG,SAAS,CAAA,EAAA,EAAK,GAAA,CAAI,EAAE,IAAI,CAAC,CAAA,EAAG,CAAA,CAAE,OAAO,CAAA,CAAA,EAAI,CAAA,CAAE,IAAI,CAAA,CAAA,GAAK,EAAE,CAAA,CAAA,GAAK,MAAA;AAC3E;AAkBO,SAAS,gBAAA,CAAiB,OAAA,EAAiB,IAAA,EAA0B,CAAA,EAA0C;AACpH,EAAA,MAAM,KAAA,GAAQ,2CAAA,CAA4C,IAAA,CAAK,OAAO,CAAA;AACtE,EAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AACnB,EAAA,MAAM,GAAG,IAAA,EAAM,EAAA,EAAI,IAAA,EAAM,IAAI,CAAA,GAAI,KAAA;AACjC,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,OAAO,aAAA,CAAc,EAAE,SAAA,EAAW,EAAE,MAAM,CAAA,qBAAA,EAAwB,OAAA,CAAQ,EAAE,QAAA,CAAS,IAAI,CAAC,CAAC,CAAA,CAAA,EAAI,EAAE,QAAA,CAAS,IAAI,CAAC,CAAA,oCAAA,CAAA,EAAwC,IAAA,EAAM,+CAA+C,CAAA;AAAA,EAC9M;AACA,EAAA,IAAI,IAAA,KAAS,YAAA,EAAc,OAAO,aAAA,CAAc,CAAA,CAAE,SAAA,EAAW,EAAE,IAAA,EAAM,8DAAA,EAAgE,IAAA,EAAM,gCAAA,EAAkC,CAAA;AAC7K,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,CAAA,CAAE,MAAA,CAAO,IAAI,GAAG,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA;AACjE,EAAA,OAAO,aAAA,CAAc,CAAA,CAAE,SAAA,EAAW,oBAAA,CAAqB,MAAM,IAAA,EAAM;AAAA,IACjE,WAAW,CAAA,CAAE,SAAA;AAAA,IAAW,IAAA,EAAM,CAAA,CAAE,IAAA,CAAK,IAAA,EAAM,EAAE,CAAA;AAAA,IAAG,KAAA;AAAA,IAAO,OAAO,CAAA,CAAE,KAAA;AAAA,IAAO,UAAU,CAAA,CAAE;AAAA,GACpF,CAAC,CAAA;AACJ","file":"extension-copy.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// An extension's problems, worded for the writer, in both apps.\n//\n// The command lines word an extension's problems for the file: \"entries.card.c_7k2.cue.sound: \"cue.sound\" is\n// required and not set\". The problems bar says the same for the writer: the extension by its name, the item as\n// the writer knows it, and the field by its label: \"Probe: The bell needs a sound in cue.\" Patterpad wrote these\n// rules first (its problem-copy.ts); they are the family's, here, so the two apps read alike.\n//\n// What is the app's is passed in: how it names the item (a line by its words, a card by its title), the words\n// for its node kinds, and where the writer goes to change a value (\"in the inspector\", \"on its page\"). The rules\n// key on the toolkit's code for the problem (`@wildwinter/toolkit/extensions`' Problem.code), which each app's\n// compiler carries on the issue; a code they do not know stands as the message it came with.\n//\n// No app state, no Electron, no DOM: words only.\n// ---------------------------------------------------------------------------\n\n/** A field declaration as the wording reads it: its name, its label, and a record's or an item's own fields. */\nexport interface FieldWordsDecl {\n name: string;\n label?: string;\n fields?: readonly FieldWordsDecl[];\n of?: { fields?: readonly FieldWordsDecl[] };\n}\n\n/** What the app knows to word one problem about a value. */\nexport interface ValueProblemWords {\n /** The extension's name, which leads the line (\"Probe\"). */\n extension: string;\n /** The item, as the writer knows it (\"“What'll it be, stranger?”\", \"the card “bell”\"). */\n node: string;\n /** The field, by its label (`fieldPhrase`). */\n field: string;\n /** Where a value is changed, finishing \"Pick one ...\" (\"in the inspector\", \"on its page\"). */\n place: string;\n /** A node kind in the app's words (\"game event\", \"hand template\"). */\n kindWord(kind: string): string;\n}\n\nexport const quoted = (s: string): string => `“${s}”`;\nconst cap = (s: string): string => `${s.charAt(0).toUpperCase()}${s.slice(1)}`;\nconst article = (s: string): string => (/^[aeiou]/i.test(s) ? \"an\" : \"a\");\n\n/** A field inside a node's entry, by its label: \"Note\", \"sound in cue\", \"item 2 of words\". */\nexport function fieldPhrase(fields: readonly FieldWordsDecl[], path: string): string {\n const tokens = path.split(/\\.|(?=\\[)/).filter((t) => t !== \"\");\n const top = fields.find((f) => f.name === tokens[0]);\n const topLabel = top?.label ?? tokens[0] ?? \"\";\n if (tokens.length <= 1) return topLabel;\n const last = tokens[tokens.length - 1]!;\n const index = /^\\[(\\d+)\\]$/.exec(last);\n if (index) return `item ${Number(index[1]) + 1} of ${topLabel}`;\n const parts = [...(top?.fields ?? []), ...(top?.of?.fields ?? [])];\n return `${parts.find((f) => f.name === last)?.label ?? last} in ${topLabel}`;\n}\n\n/** The sentence and its next step for one value a check refused, by the toolkit's code; undefined for a code\n * the rules do not know. `message` is the check's own sentence, which some sentences take their values from. */\nexport function valueProblemSentence(code: string | undefined, message: string, w: ValueProblemWords): { text: string; next?: string } | undefined {\n const { field, node, extension: name, place } = w;\n const on = `${field} on ${node}`;\n const value = /^\"[^\"]*\" is \"([^\"]*)\"/.exec(message)?.[1];\n switch (code) {\n case \"required\": return { text: `${node} needs ${article(field)} ${field}.` };\n case \"wrong-type\": return { text: `${on} isn't the kind of value ${name} expects.`, next: `Set it again ${place}.` };\n case \"below-min\": { const m = / is ([^,]+), below the least it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing below ${m[2]}.` } : undefined; }\n case \"above-max\": { const m = / is ([^,]+), above the most it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing above ${m[2]}.` } : undefined; }\n case \"not-a-value\":\n if (/not among its flags$/.test(message)) return { text: `${on} has a flag that isn't one of its choices.`, next: `Pick from the choices ${place}.` };\n return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its choices.`, next: `Pick one ${place}.` };\n case \"not-a-stage\": return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its stages.`, next: `Pick one ${place}.` };\n case \"renamed-stage\": {\n const m = / is \"([^\"]*)\", a stage this extension has since renamed \"([^\"]*)\"/.exec(message);\n return m ? { text: `${on} is ${quoted(m[1]!)}, a stage ${name} has since renamed ${quoted(m[2]!)}.`, next: `Pick ${quoted(m[2]!)} to update it.` } : undefined;\n }\n case \"no-such-node\": {\n const m = / (?:names|is keyed by) (\\w+) \"([^\"]*)\", which does not exist/.exec(message);\n return m ? { text: `${on} names the ${w.kindWord(m[1]!)} ${quoted(m[2]!)}, which isn't in the project.`, next: \"Choose another, or clear it.\" } : undefined;\n }\n case \"missing-id\": return { text: `An item in ${field} on ${node} has no id.`, next: `Give it one ${place}.` };\n case \"duplicate-id\": { const id = / repeats the id \"([^\"]*)\"/.exec(message)?.[1]; return { text: `Two items in ${field} on ${node} share the id ${quoted(id ?? \"\")}.`, next: \"Give each its own.\" }; }\n case \"unknown-field\": case \"unknown-key\":\n return { text: `${node} has a value for ${quoted(field)}, which ${name} doesn't declare any more.`, next: \"It's kept, but your game won't get it.\" };\n default: return undefined;\n }\n}\n\n/** A problem's line: the extension's name, then the sentence and its next step. */\nexport function extensionLine(extension: string, c: { text: string; next?: string } | undefined): string | undefined {\n return c ? `${extension}: ${cap(c.text)}${c.next ? ` ${c.next}` : \"\"}` : undefined;\n}\n\n/** What the app knows to word an entry problem (`entryProblemLine`). */\nexport interface EntryProblemWords {\n extension: string;\n /** The extension's declarations for a node kind. */\n fields(kind: string): readonly FieldWordsDecl[];\n /** A node as the writer knows it, by kind and id. */\n node(kind: string, id: string): string;\n place: string;\n kindWord(kind: string): string;\n}\n\n/**\n * The writer's line for a problem in a node's extension values, read from the command line's\n * \"entries.<kind>.<id>[.<path>]: <what is wrong>\" and the toolkit's code (`orphan` and `unresolved` included);\n * undefined for a message not of that shape, or a code the rules do not know.\n */\nexport function entryProblemLine(message: string, code: string | undefined, w: EntryProblemWords): string | undefined {\n const entry = /^entries\\.(\\w+)\\.([^.:\\s[]+)(\\S*?): (.*)$/.exec(message);\n if (!entry) return undefined;\n const [, kind, id, rest, said] = entry as unknown as [string, string, string, string, string];\n if (code === \"orphan\") {\n return extensionLine(w.extension, { text: `There are values for ${article(w.kindWord(kind))} ${w.kindWord(kind)} that isn't in the project any more.`, next: \"They're kept, but your game won't get them.\" });\n }\n if (code === \"unresolved\") return extensionLine(w.extension, { text: \"A merge left a clash in this data that hasn't been resolved.\", next: \"Resolve it before you publish.\" });\n const field = fieldPhrase(w.fields(kind), rest.replace(/^\\./, \"\"));\n return extensionLine(w.extension, valueProblemSentence(code, said, {\n extension: w.extension, node: w.node(kind, id), field, place: w.place, kindWord: w.kindWord,\n }));\n}\n"]}
1
+ {"version":3,"sources":["../src/extension-copy.ts"],"names":[],"mappings":"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","file":"extension-copy.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// An extension's problems, worded for the writer, in both apps.\n//\n// The command lines word an extension's problems for the file: \"entries.card.c_7k2.cue.sound: \"cue.sound\" is\n// required and not set\". The problems bar says the same for the writer: the extension by its name, the item as\n// the writer knows it, and the field by its label: \"Probe: The bell needs a sound in cue.\" Patterpad wrote these\n// rules first (its problem-copy.ts); they are the family's, here, so the two apps read alike.\n//\n// What is the app's is passed in: how it names the item (a line by its words, a card by its title), the words\n// for its node kinds, and where the writer goes to change a value (\"in the inspector\", \"on its page\"). The rules\n// key on the toolkit's code for the problem (`@wildwinter/toolkit/extensions`' Problem.code), which each app's\n// compiler carries on the issue; a code they do not know stands as the message it came with.\n//\n// No app state, no Electron, no DOM: words only.\n// ---------------------------------------------------------------------------\n\n/** A field declaration as the wording reads it: its name, its label, and a record's or an item's own fields. */\nexport interface FieldWordsDecl {\n name: string;\n label?: string;\n fields?: readonly FieldWordsDecl[];\n of?: { fields?: readonly FieldWordsDecl[] };\n}\n\n/** What the app knows to word one problem about a value. */\nexport interface ValueProblemWords {\n /** The extension's name, which leads the line (\"Probe\"). */\n extension: string;\n /** The item, as the writer knows it (\"“What'll it be, stranger?”\", \"the card “bell”\"). */\n node: string;\n /** The field, by its label (`fieldPhrase`). */\n field: string;\n /** Where a value is changed, finishing \"Pick one ...\" (\"in the inspector\", \"on its page\"). */\n place: string;\n /** A node kind in the app's words (\"game event\", \"hand template\"). */\n kindWord(kind: string): string;\n}\n\nexport const quoted = (s: string): string => `“${s}”`;\n/** A check's own quoted names in the family's quotes. */\nconst curly = (s: string): string => s.replace(/\"([^\"]*)\"/g, (_, x: string) => quoted(x));\nconst cap = (s: string): string => `${s.charAt(0).toUpperCase()}${s.slice(1)}`;\nconst article = (s: string): string => (/^[aeiou]/i.test(s) ? \"an\" : \"a\");\n\n/** A field inside a node's entry, by its label: \"Note\", \"sound in cue\", \"item 2 of words\". */\nexport function fieldPhrase(fields: readonly FieldWordsDecl[], path: string): string {\n const tokens = path.split(/\\.|(?=\\[)/).filter((t) => t !== \"\");\n const top = fields.find((f) => f.name === tokens[0]);\n const topLabel = top?.label ?? tokens[0] ?? \"\";\n if (tokens.length <= 1) return topLabel;\n const last = tokens[tokens.length - 1]!;\n const index = /^\\[(\\d+)\\]$/.exec(last);\n if (index) return `item ${Number(index[1]) + 1} of ${topLabel}`;\n const parts = [...(top?.fields ?? []), ...(top?.of?.fields ?? [])];\n return `${parts.find((f) => f.name === last)?.label ?? last} in ${topLabel}`;\n}\n\n/** The sentence and its next step for one value a check refused, by the toolkit's code; undefined for a code\n * the rules do not know. `message` is the check's own sentence, which some sentences take their values from. */\nexport function valueProblemSentence(code: string | undefined, message: string, w: ValueProblemWords): { text: string; next?: string } | undefined {\n const { field, node, extension: name, place } = w;\n const on = `${field} on ${node}`;\n const value = /^\"[^\"]*\" is \"([^\"]*)\"/.exec(message)?.[1];\n switch (code) {\n case \"required\": return { text: `${node} needs ${article(field)} ${field}.` };\n case \"wrong-type\": return { text: `${on} isn't the kind of value ${name} expects.`, next: `Set it again ${place}.` };\n case \"below-min\": { const m = / is ([^,]+), below the least it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing below ${m[2]}.` } : undefined; }\n case \"above-max\": { const m = / is ([^,]+), above the most it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing above ${m[2]}.` } : undefined; }\n case \"not-a-value\":\n if (/not among its flags$/.test(message)) return { text: `${on} has a flag that isn't one of its choices.`, next: `Pick from the choices ${place}.` };\n return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its choices.`, next: `Pick one ${place}.` };\n case \"not-a-choice\": return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its choices.`, next: `Pick one ${place}.` };\n case \"pick-ambiguous\": {\n const m = / is \"([^\"]*)\", which is (.+)$/.exec(message);\n return m ? { text: `${on} is ${quoted(m[1]!)}, which is ${curly(m[2]!)}, so it's ambiguous.`, next: \"Rename one of them.\" } : undefined;\n }\n case \"pick-clash\": {\n const m = /^\"([^\"]*)\" is (.+), so a value naming it is ambiguous$/.exec(message);\n return m ? { text: `${quoted(m[1]!)} is ${curly(m[2]!)}, so ${field} naming it is ambiguous.`, next: \"Rename one of them.\" } : undefined;\n }\n case \"not-a-stage\": return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its stages.`, next: `Pick one ${place}.` };\n case \"renamed-stage\": {\n const m = / is \"([^\"]*)\", a stage this extension has since renamed \"([^\"]*)\"/.exec(message);\n return m ? { text: `${on} is ${quoted(m[1]!)}, a stage ${name} has since renamed ${quoted(m[2]!)}.`, next: `Pick ${quoted(m[2]!)} to update it.` } : undefined;\n }\n case \"no-such-node\": {\n const m = / (?:names|is keyed by) (\\w+) \"([^\"]*)\", which does not exist/.exec(message);\n return m ? { text: `${on} names the ${w.kindWord(m[1]!)} ${quoted(m[2]!)}, which isn't in the project.`, next: \"Choose another, or clear it.\" } : undefined;\n }\n case \"missing-id\": return { text: `An item in ${field} on ${node} has no id.`, next: `Give it one ${place}.` };\n case \"duplicate-id\": { const id = / repeats the id \"([^\"]*)\"/.exec(message)?.[1]; return { text: `Two items in ${field} on ${node} share the id ${quoted(id ?? \"\")}.`, next: \"Give each its own.\" }; }\n case \"unknown-field\": case \"unknown-key\":\n return { text: `${node} has a value for ${quoted(field)}, which ${name} doesn't declare any more.`, next: \"It's kept, but your game won't get it.\" };\n default: return undefined;\n }\n}\n\n/** A problem's line: the extension's name, then the sentence and its next step. */\nexport function extensionLine(extension: string, c: { text: string; next?: string } | undefined): string | undefined {\n return c ? `${extension}: ${cap(c.text)}${c.next ? ` ${c.next}` : \"\"}` : undefined;\n}\n\n/** What the app knows to word an entry problem (`entryProblemLine`). */\nexport interface EntryProblemWords {\n extension: string;\n /** The extension's declarations for a node kind. */\n fields(kind: string): readonly FieldWordsDecl[];\n /** A node as the writer knows it, by kind and id. */\n node(kind: string, id: string): string;\n place: string;\n kindWord(kind: string): string;\n}\n\n/**\n * The writer's line for a problem in a node's extension values, read from the command line's\n * \"entries.<kind>.<id>[.<path>]: <what is wrong>\" and the toolkit's code (`orphan` and `unresolved` included);\n * undefined for a message not of that shape, or a code the rules do not know.\n */\nexport function entryProblemLine(message: string, code: string | undefined, w: EntryProblemWords): string | undefined {\n const entry = /^entries\\.(\\w+)\\.([^.:\\s[]+)(\\S*?): (.*)$/.exec(message);\n if (!entry) return undefined;\n const [, kind, id, rest, said] = entry as unknown as [string, string, string, string, string];\n if (code === \"orphan\") {\n return extensionLine(w.extension, { text: `There are values for ${article(w.kindWord(kind))} ${w.kindWord(kind)} that isn't in the project any more.`, next: \"They're kept, but your game won't get them.\" });\n }\n if (code === \"unresolved\") return extensionLine(w.extension, { text: \"A merge left a clash in this data that hasn't been resolved.\", next: \"Resolve it before you publish.\" });\n const field = fieldPhrase(w.fields(kind), rest.replace(/^\\./, \"\"));\n return extensionLine(w.extension, valueProblemSentence(code, said, {\n extension: w.extension, node: w.node(kind, id), field, place: w.place, kindWord: w.kindWord,\n }));\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  // src/extension-copy.ts
2
2
  var quoted = (s) => `\u201C${s}\u201D`;
3
+ var curly = (s) => s.replace(/"([^"]*)"/g, (_, x) => quoted(x));
3
4
  var cap = (s) => `${s.charAt(0).toUpperCase()}${s.slice(1)}`;
4
5
  var article = (s) => /^[aeiou]/i.test(s) ? "an" : "a";
5
6
  function fieldPhrase(fields, path) {
@@ -33,6 +34,16 @@ function valueProblemSentence(code, message, w) {
33
34
  case "not-a-value":
34
35
  if (/not among its flags$/.test(message)) return { text: `${on} has a flag that isn't one of its choices.`, next: `Pick from the choices ${place}.` };
35
36
  return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its choices.`, next: `Pick one ${place}.` };
37
+ case "not-a-choice":
38
+ return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its choices.`, next: `Pick one ${place}.` };
39
+ case "pick-ambiguous": {
40
+ const m = / is "([^"]*)", which is (.+)$/.exec(message);
41
+ return m ? { text: `${on} is ${quoted(m[1])}, which is ${curly(m[2])}, so it's ambiguous.`, next: "Rename one of them." } : void 0;
42
+ }
43
+ case "pick-clash": {
44
+ const m = /^"([^"]*)" is (.+), so a value naming it is ambiguous$/.exec(message);
45
+ return m ? { text: `${quoted(m[1])} is ${curly(m[2])}, so ${field} naming it is ambiguous.`, next: "Rename one of them." } : void 0;
46
+ }
36
47
  case "not-a-stage":
37
48
  return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its stages.`, next: `Pick one ${place}.` };
38
49
  case "renamed-stage": {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/extension-copy.ts"],"names":[],"mappings":"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","file":"extension-copy.js","sourcesContent":["// ---------------------------------------------------------------------------\n// An extension's problems, worded for the writer, in both apps.\n//\n// The command lines word an extension's problems for the file: \"entries.card.c_7k2.cue.sound: \"cue.sound\" is\n// required and not set\". The problems bar says the same for the writer: the extension by its name, the item as\n// the writer knows it, and the field by its label: \"Probe: The bell needs a sound in cue.\" Patterpad wrote these\n// rules first (its problem-copy.ts); they are the family's, here, so the two apps read alike.\n//\n// What is the app's is passed in: how it names the item (a line by its words, a card by its title), the words\n// for its node kinds, and where the writer goes to change a value (\"in the inspector\", \"on its page\"). The rules\n// key on the toolkit's code for the problem (`@wildwinter/toolkit/extensions`' Problem.code), which each app's\n// compiler carries on the issue; a code they do not know stands as the message it came with.\n//\n// No app state, no Electron, no DOM: words only.\n// ---------------------------------------------------------------------------\n\n/** A field declaration as the wording reads it: its name, its label, and a record's or an item's own fields. */\nexport interface FieldWordsDecl {\n name: string;\n label?: string;\n fields?: readonly FieldWordsDecl[];\n of?: { fields?: readonly FieldWordsDecl[] };\n}\n\n/** What the app knows to word one problem about a value. */\nexport interface ValueProblemWords {\n /** The extension's name, which leads the line (\"Probe\"). */\n extension: string;\n /** The item, as the writer knows it (\"“What'll it be, stranger?”\", \"the card “bell”\"). */\n node: string;\n /** The field, by its label (`fieldPhrase`). */\n field: string;\n /** Where a value is changed, finishing \"Pick one ...\" (\"in the inspector\", \"on its page\"). */\n place: string;\n /** A node kind in the app's words (\"game event\", \"hand template\"). */\n kindWord(kind: string): string;\n}\n\nexport const quoted = (s: string): string => `“${s}”`;\nconst cap = (s: string): string => `${s.charAt(0).toUpperCase()}${s.slice(1)}`;\nconst article = (s: string): string => (/^[aeiou]/i.test(s) ? \"an\" : \"a\");\n\n/** A field inside a node's entry, by its label: \"Note\", \"sound in cue\", \"item 2 of words\". */\nexport function fieldPhrase(fields: readonly FieldWordsDecl[], path: string): string {\n const tokens = path.split(/\\.|(?=\\[)/).filter((t) => t !== \"\");\n const top = fields.find((f) => f.name === tokens[0]);\n const topLabel = top?.label ?? tokens[0] ?? \"\";\n if (tokens.length <= 1) return topLabel;\n const last = tokens[tokens.length - 1]!;\n const index = /^\\[(\\d+)\\]$/.exec(last);\n if (index) return `item ${Number(index[1]) + 1} of ${topLabel}`;\n const parts = [...(top?.fields ?? []), ...(top?.of?.fields ?? [])];\n return `${parts.find((f) => f.name === last)?.label ?? last} in ${topLabel}`;\n}\n\n/** The sentence and its next step for one value a check refused, by the toolkit's code; undefined for a code\n * the rules do not know. `message` is the check's own sentence, which some sentences take their values from. */\nexport function valueProblemSentence(code: string | undefined, message: string, w: ValueProblemWords): { text: string; next?: string } | undefined {\n const { field, node, extension: name, place } = w;\n const on = `${field} on ${node}`;\n const value = /^\"[^\"]*\" is \"([^\"]*)\"/.exec(message)?.[1];\n switch (code) {\n case \"required\": return { text: `${node} needs ${article(field)} ${field}.` };\n case \"wrong-type\": return { text: `${on} isn't the kind of value ${name} expects.`, next: `Set it again ${place}.` };\n case \"below-min\": { const m = / is ([^,]+), below the least it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing below ${m[2]}.` } : undefined; }\n case \"above-max\": { const m = / is ([^,]+), above the most it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing above ${m[2]}.` } : undefined; }\n case \"not-a-value\":\n if (/not among its flags$/.test(message)) return { text: `${on} has a flag that isn't one of its choices.`, next: `Pick from the choices ${place}.` };\n return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its choices.`, next: `Pick one ${place}.` };\n case \"not-a-stage\": return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its stages.`, next: `Pick one ${place}.` };\n case \"renamed-stage\": {\n const m = / is \"([^\"]*)\", a stage this extension has since renamed \"([^\"]*)\"/.exec(message);\n return m ? { text: `${on} is ${quoted(m[1]!)}, a stage ${name} has since renamed ${quoted(m[2]!)}.`, next: `Pick ${quoted(m[2]!)} to update it.` } : undefined;\n }\n case \"no-such-node\": {\n const m = / (?:names|is keyed by) (\\w+) \"([^\"]*)\", which does not exist/.exec(message);\n return m ? { text: `${on} names the ${w.kindWord(m[1]!)} ${quoted(m[2]!)}, which isn't in the project.`, next: \"Choose another, or clear it.\" } : undefined;\n }\n case \"missing-id\": return { text: `An item in ${field} on ${node} has no id.`, next: `Give it one ${place}.` };\n case \"duplicate-id\": { const id = / repeats the id \"([^\"]*)\"/.exec(message)?.[1]; return { text: `Two items in ${field} on ${node} share the id ${quoted(id ?? \"\")}.`, next: \"Give each its own.\" }; }\n case \"unknown-field\": case \"unknown-key\":\n return { text: `${node} has a value for ${quoted(field)}, which ${name} doesn't declare any more.`, next: \"It's kept, but your game won't get it.\" };\n default: return undefined;\n }\n}\n\n/** A problem's line: the extension's name, then the sentence and its next step. */\nexport function extensionLine(extension: string, c: { text: string; next?: string } | undefined): string | undefined {\n return c ? `${extension}: ${cap(c.text)}${c.next ? ` ${c.next}` : \"\"}` : undefined;\n}\n\n/** What the app knows to word an entry problem (`entryProblemLine`). */\nexport interface EntryProblemWords {\n extension: string;\n /** The extension's declarations for a node kind. */\n fields(kind: string): readonly FieldWordsDecl[];\n /** A node as the writer knows it, by kind and id. */\n node(kind: string, id: string): string;\n place: string;\n kindWord(kind: string): string;\n}\n\n/**\n * The writer's line for a problem in a node's extension values, read from the command line's\n * \"entries.<kind>.<id>[.<path>]: <what is wrong>\" and the toolkit's code (`orphan` and `unresolved` included);\n * undefined for a message not of that shape, or a code the rules do not know.\n */\nexport function entryProblemLine(message: string, code: string | undefined, w: EntryProblemWords): string | undefined {\n const entry = /^entries\\.(\\w+)\\.([^.:\\s[]+)(\\S*?): (.*)$/.exec(message);\n if (!entry) return undefined;\n const [, kind, id, rest, said] = entry as unknown as [string, string, string, string, string];\n if (code === \"orphan\") {\n return extensionLine(w.extension, { text: `There are values for ${article(w.kindWord(kind))} ${w.kindWord(kind)} that isn't in the project any more.`, next: \"They're kept, but your game won't get them.\" });\n }\n if (code === \"unresolved\") return extensionLine(w.extension, { text: \"A merge left a clash in this data that hasn't been resolved.\", next: \"Resolve it before you publish.\" });\n const field = fieldPhrase(w.fields(kind), rest.replace(/^\\./, \"\"));\n return extensionLine(w.extension, valueProblemSentence(code, said, {\n extension: w.extension, node: w.node(kind, id), field, place: w.place, kindWord: w.kindWord,\n }));\n}\n"]}
1
+ {"version":3,"sources":["../src/extension-copy.ts"],"names":[],"mappings":"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","file":"extension-copy.js","sourcesContent":["// ---------------------------------------------------------------------------\n// An extension's problems, worded for the writer, in both apps.\n//\n// The command lines word an extension's problems for the file: \"entries.card.c_7k2.cue.sound: \"cue.sound\" is\n// required and not set\". The problems bar says the same for the writer: the extension by its name, the item as\n// the writer knows it, and the field by its label: \"Probe: The bell needs a sound in cue.\" Patterpad wrote these\n// rules first (its problem-copy.ts); they are the family's, here, so the two apps read alike.\n//\n// What is the app's is passed in: how it names the item (a line by its words, a card by its title), the words\n// for its node kinds, and where the writer goes to change a value (\"in the inspector\", \"on its page\"). The rules\n// key on the toolkit's code for the problem (`@wildwinter/toolkit/extensions`' Problem.code), which each app's\n// compiler carries on the issue; a code they do not know stands as the message it came with.\n//\n// No app state, no Electron, no DOM: words only.\n// ---------------------------------------------------------------------------\n\n/** A field declaration as the wording reads it: its name, its label, and a record's or an item's own fields. */\nexport interface FieldWordsDecl {\n name: string;\n label?: string;\n fields?: readonly FieldWordsDecl[];\n of?: { fields?: readonly FieldWordsDecl[] };\n}\n\n/** What the app knows to word one problem about a value. */\nexport interface ValueProblemWords {\n /** The extension's name, which leads the line (\"Probe\"). */\n extension: string;\n /** The item, as the writer knows it (\"“What'll it be, stranger?”\", \"the card “bell”\"). */\n node: string;\n /** The field, by its label (`fieldPhrase`). */\n field: string;\n /** Where a value is changed, finishing \"Pick one ...\" (\"in the inspector\", \"on its page\"). */\n place: string;\n /** A node kind in the app's words (\"game event\", \"hand template\"). */\n kindWord(kind: string): string;\n}\n\nexport const quoted = (s: string): string => `“${s}”`;\n/** A check's own quoted names in the family's quotes. */\nconst curly = (s: string): string => s.replace(/\"([^\"]*)\"/g, (_, x: string) => quoted(x));\nconst cap = (s: string): string => `${s.charAt(0).toUpperCase()}${s.slice(1)}`;\nconst article = (s: string): string => (/^[aeiou]/i.test(s) ? \"an\" : \"a\");\n\n/** A field inside a node's entry, by its label: \"Note\", \"sound in cue\", \"item 2 of words\". */\nexport function fieldPhrase(fields: readonly FieldWordsDecl[], path: string): string {\n const tokens = path.split(/\\.|(?=\\[)/).filter((t) => t !== \"\");\n const top = fields.find((f) => f.name === tokens[0]);\n const topLabel = top?.label ?? tokens[0] ?? \"\";\n if (tokens.length <= 1) return topLabel;\n const last = tokens[tokens.length - 1]!;\n const index = /^\\[(\\d+)\\]$/.exec(last);\n if (index) return `item ${Number(index[1]) + 1} of ${topLabel}`;\n const parts = [...(top?.fields ?? []), ...(top?.of?.fields ?? [])];\n return `${parts.find((f) => f.name === last)?.label ?? last} in ${topLabel}`;\n}\n\n/** The sentence and its next step for one value a check refused, by the toolkit's code; undefined for a code\n * the rules do not know. `message` is the check's own sentence, which some sentences take their values from. */\nexport function valueProblemSentence(code: string | undefined, message: string, w: ValueProblemWords): { text: string; next?: string } | undefined {\n const { field, node, extension: name, place } = w;\n const on = `${field} on ${node}`;\n const value = /^\"[^\"]*\" is \"([^\"]*)\"/.exec(message)?.[1];\n switch (code) {\n case \"required\": return { text: `${node} needs ${article(field)} ${field}.` };\n case \"wrong-type\": return { text: `${on} isn't the kind of value ${name} expects.`, next: `Set it again ${place}.` };\n case \"below-min\": { const m = / is ([^,]+), below the least it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing below ${m[2]}.` } : undefined; }\n case \"above-max\": { const m = / is ([^,]+), above the most it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing above ${m[2]}.` } : undefined; }\n case \"not-a-value\":\n if (/not among its flags$/.test(message)) return { text: `${on} has a flag that isn't one of its choices.`, next: `Pick from the choices ${place}.` };\n return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its choices.`, next: `Pick one ${place}.` };\n case \"not-a-choice\": return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its choices.`, next: `Pick one ${place}.` };\n case \"pick-ambiguous\": {\n const m = / is \"([^\"]*)\", which is (.+)$/.exec(message);\n return m ? { text: `${on} is ${quoted(m[1]!)}, which is ${curly(m[2]!)}, so it's ambiguous.`, next: \"Rename one of them.\" } : undefined;\n }\n case \"pick-clash\": {\n const m = /^\"([^\"]*)\" is (.+), so a value naming it is ambiguous$/.exec(message);\n return m ? { text: `${quoted(m[1]!)} is ${curly(m[2]!)}, so ${field} naming it is ambiguous.`, next: \"Rename one of them.\" } : undefined;\n }\n case \"not-a-stage\": return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its stages.`, next: `Pick one ${place}.` };\n case \"renamed-stage\": {\n const m = / is \"([^\"]*)\", a stage this extension has since renamed \"([^\"]*)\"/.exec(message);\n return m ? { text: `${on} is ${quoted(m[1]!)}, a stage ${name} has since renamed ${quoted(m[2]!)}.`, next: `Pick ${quoted(m[2]!)} to update it.` } : undefined;\n }\n case \"no-such-node\": {\n const m = / (?:names|is keyed by) (\\w+) \"([^\"]*)\", which does not exist/.exec(message);\n return m ? { text: `${on} names the ${w.kindWord(m[1]!)} ${quoted(m[2]!)}, which isn't in the project.`, next: \"Choose another, or clear it.\" } : undefined;\n }\n case \"missing-id\": return { text: `An item in ${field} on ${node} has no id.`, next: `Give it one ${place}.` };\n case \"duplicate-id\": { const id = / repeats the id \"([^\"]*)\"/.exec(message)?.[1]; return { text: `Two items in ${field} on ${node} share the id ${quoted(id ?? \"\")}.`, next: \"Give each its own.\" }; }\n case \"unknown-field\": case \"unknown-key\":\n return { text: `${node} has a value for ${quoted(field)}, which ${name} doesn't declare any more.`, next: \"It's kept, but your game won't get it.\" };\n default: return undefined;\n }\n}\n\n/** A problem's line: the extension's name, then the sentence and its next step. */\nexport function extensionLine(extension: string, c: { text: string; next?: string } | undefined): string | undefined {\n return c ? `${extension}: ${cap(c.text)}${c.next ? ` ${c.next}` : \"\"}` : undefined;\n}\n\n/** What the app knows to word an entry problem (`entryProblemLine`). */\nexport interface EntryProblemWords {\n extension: string;\n /** The extension's declarations for a node kind. */\n fields(kind: string): readonly FieldWordsDecl[];\n /** A node as the writer knows it, by kind and id. */\n node(kind: string, id: string): string;\n place: string;\n kindWord(kind: string): string;\n}\n\n/**\n * The writer's line for a problem in a node's extension values, read from the command line's\n * \"entries.<kind>.<id>[.<path>]: <what is wrong>\" and the toolkit's code (`orphan` and `unresolved` included);\n * undefined for a message not of that shape, or a code the rules do not know.\n */\nexport function entryProblemLine(message: string, code: string | undefined, w: EntryProblemWords): string | undefined {\n const entry = /^entries\\.(\\w+)\\.([^.:\\s[]+)(\\S*?): (.*)$/.exec(message);\n if (!entry) return undefined;\n const [, kind, id, rest, said] = entry as unknown as [string, string, string, string, string];\n if (code === \"orphan\") {\n return extensionLine(w.extension, { text: `There are values for ${article(w.kindWord(kind))} ${w.kindWord(kind)} that isn't in the project any more.`, next: \"They're kept, but your game won't get them.\" });\n }\n if (code === \"unresolved\") return extensionLine(w.extension, { text: \"A merge left a clash in this data that hasn't been resolved.\", next: \"Resolve it before you publish.\" });\n const field = fieldPhrase(w.fields(kind), rest.replace(/^\\./, \"\"));\n return extensionLine(w.extension, valueProblemSentence(code, said, {\n extension: w.extension, node: w.node(kind, id), field, place: w.place, kindWord: w.kindWord,\n }));\n}\n"]}
@@ -837,13 +837,20 @@ var nameOf = (decl) => decl.label ?? decl.name;
837
837
  var asDecl = (d, name) => ({ ...d, name: d.name ?? name });
838
838
  function ownProblems(decl, value, adapter) {
839
839
  if (value === void 0) return "";
840
- const exists = adapter ? (kind, id) => nodeLabel(adapter, kind, id) !== void 0 : void 0;
841
- return extensions.valueProblems(decl, value, exists ? { exists } : {}).filter((p) => p.path === "" && p.severity === "error" && p.code !== "not-a-value" && p.code !== "not-a-stage").map((p) => `${p.message.replace(/^the value/, "The value")}.`).join(" ");
840
+ return extensions.valueProblems(decl, value, contextOf(adapter)).filter((p) => p.path === "" && (p.severity === "error" || p.code === "pick-ambiguous") && p.code !== "not-a-value" && p.code !== "not-a-stage").map((p) => `${p.message.replace(/^the value/, "The value")}.`).join(" ");
841
+ }
842
+ function contextOf(adapter) {
843
+ if (!adapter) return {};
844
+ return {
845
+ exists: (kind, id) => nodeLabel(adapter, kind, id) !== void 0,
846
+ ...adapter.list ? { list: (name) => adapter.list(name) } : {}
847
+ };
842
848
  }
843
849
  function valueRow(initial) {
844
850
  const decl = initial.decl;
845
851
  if (SCALARS.has(decl.type)) return scalarRow(initial);
846
852
  if (decl.type === "reference") return referenceRow(initial);
853
+ if (decl.type === "pick") return pickRow(initial);
847
854
  return structuredRow(initial);
848
855
  }
849
856
  function scalarRow(initial) {
@@ -923,6 +930,86 @@ function referenceRow(initial) {
923
930
  draw(initial);
924
931
  return { el: row, update: draw };
925
932
  }
933
+ function pickRow(initial) {
934
+ let opts = initial;
935
+ if (!opts.adapter && (opts.decl.from ?? []).some((s) => !("words" in s))) {
936
+ const inner = scalarRow({ ...opts, decl: { ...opts.decl, type: "text" } });
937
+ return { el: inner.el, update: (o) => {
938
+ opts = o;
939
+ inner.update({ ...o, decl: { ...o.decl, type: "text" } });
940
+ } };
941
+ }
942
+ const row = el("div", "shell-fieldrow shell-valuerow-pick");
943
+ const caption = el("span", "shell-fieldrow-label");
944
+ const body = el("div", "shell-fieldrow-body");
945
+ const sel = el("select", "field shell-fieldrow-select");
946
+ const help = el("p", "shell-fieldrow-purpose");
947
+ const problem = el("p", "shell-fieldrow-problem");
948
+ problem.setAttribute("role", "status");
949
+ body.append(sel, help, problem);
950
+ row.append(caption, body);
951
+ sel.addEventListener("change", () => {
952
+ const v = sel.value === "" ? void 0 : sel.value;
953
+ opts = { ...opts, value: v };
954
+ draw(opts);
955
+ opts.onCommit?.(v);
956
+ });
957
+ const groupName = (s) => "to" in s ? s.to === "cast" ? "Cast" : s.to.charAt(0).toUpperCase() + s.to.slice(1) : "list" in s ? s.list : void 0;
958
+ const draw = (o) => {
959
+ opts = o;
960
+ row.className = `shell-fieldrow shell-valuerow-pick${o.className ? ` ${o.className}` : ""}`;
961
+ const label = o.label ?? nameOf(o.decl);
962
+ if (typeof label === "string") caption.textContent = label;
963
+ else caption.replaceChildren(label);
964
+ if (o.controlClassName) sel.className = `shell-fieldrow-select ${o.controlClassName}`;
965
+ const adapter = o.adapter;
966
+ const choices = extensions.pickChoices(o.decl, {
967
+ ...adapter ? { nodes: (kind) => adapter.nodes(kind) } : {},
968
+ ...adapter?.list ? { list: (name) => adapter.list(name) } : {}
969
+ });
970
+ const held = typeof o.value === "string" ? o.value : "";
971
+ sel.replaceChildren();
972
+ const option = (v, text) => {
973
+ const opt = el("option", void 0, text);
974
+ opt.value = v;
975
+ return opt;
976
+ };
977
+ sel.append(option("", "auto"));
978
+ const offered = /* @__PURE__ */ new Set();
979
+ let group;
980
+ for (const c of choices) {
981
+ if (offered.has(c.value)) continue;
982
+ offered.add(c.value);
983
+ const name = groupName(c.source);
984
+ if (name === void 0) {
985
+ group = void 0;
986
+ sel.append(option(c.value, c.label));
987
+ continue;
988
+ }
989
+ if (group?.source !== c.source) {
990
+ const og = el("optgroup");
991
+ og.setAttribute("label", name);
992
+ sel.append(og);
993
+ group = { source: c.source, el: og };
994
+ }
995
+ group.el.append(option(c.value, c.label));
996
+ }
997
+ if (held !== "" && !offered.has(held)) {
998
+ const named = (o.decl.from ?? []).map((s) => "to" in s && adapter ? adapter.label?.(s.to, held) : void 0).find((n) => n !== void 0);
999
+ sel.append(option(held, named ?? `${held} (missing)`));
1000
+ }
1001
+ sel.value = held;
1002
+ sel.disabled = o.onCommit === void 0 || o.disabled === true;
1003
+ help.textContent = o.decl.help ?? o.decl.purpose ?? "";
1004
+ help.hidden = help.textContent === "";
1005
+ const words = o.replaceProblem ?? [ownProblems(o.decl, o.value, o.adapter), o.problem ?? ""].filter((w) => w !== "").join(" ");
1006
+ problem.textContent = words;
1007
+ problem.hidden = words === "";
1008
+ row.classList.toggle("has-problem", words !== "");
1009
+ };
1010
+ draw(initial);
1011
+ return { el: row, update: draw };
1012
+ }
926
1013
  function structuredRow(initial) {
927
1014
  let opts = initial;
928
1015
  const row = el("div", "shell-fieldrow shell-valuerow");