@mulmoclaude/core 4.4.1 → 4.5.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/assets/helps/bug-report-faq.md +13 -0
- package/assets/helps/business.md +4 -0
- package/assets/helps/custom-view.md +69 -1
- package/assets/helps/error-recovery.md +71 -0
- package/assets/helps/gemini.md +1 -1
- package/assets/helps/mulmoscript.md +71 -1
- package/assets/helps/presentation-deck.md +4 -1
- package/assets/helps/storyteller.md +7 -0
- package/dist/collection/core/accentColor.d.ts +23 -0
- package/dist/collection/core/iconGlyph.d.ts +26 -0
- package/dist/collection/core/schema.d.ts +9 -0
- package/dist/collection/core/schemaZ.d.ts +18 -0
- package/dist/collection/core/shortcutInfo.d.ts +14 -0
- package/dist/collection/core/uiTypes.d.ts +6 -0
- package/dist/collection/index.cjs +22 -2
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.d.ts +3 -0
- package/dist/collection/index.js +17 -3
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/server/index.cjs +3 -3
- package/dist/collection/registry/server/index.js +3 -3
- package/dist/collection/server/index.cjs +5 -4
- package/dist/collection/server/index.js +5 -5
- package/dist/collection/server/paths.d.ts +10 -0
- package/dist/collection-watchers/index.cjs +5 -5
- package/dist/collection-watchers/index.js +5 -5
- package/dist/{discovery-Dl9nRa-m.cjs → discovery-2TvulIz8.cjs} +25 -5
- package/dist/discovery-2TvulIz8.cjs.map +1 -0
- package/dist/{discovery-P64fgtuD.js → discovery-BY-nMCuh.js} +20 -6
- package/dist/discovery-BY-nMCuh.js.map +1 -0
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/index.cjs +5 -5
- package/dist/feeds/server/index.js +5 -5
- package/dist/files/atomic.d.ts +4 -0
- package/dist/files/index.cjs +1 -1
- package/dist/files/index.js +1 -1
- package/dist/google/index.cjs +3 -3
- package/dist/google/index.js +3 -3
- package/dist/iconGlyph-D8JeNg7R.js +59 -0
- package/dist/iconGlyph-D8JeNg7R.js.map +1 -0
- package/dist/iconGlyph-fSBp6k4J.cjs +70 -0
- package/dist/iconGlyph-fSBp6k4J.cjs.map +1 -0
- package/dist/{ingestTypes-CaX7_s-K.cjs → ingestTypes-DsVH9BYy.cjs} +2 -2
- package/dist/{ingestTypes-CaX7_s-K.cjs.map → ingestTypes-DsVH9BYy.cjs.map} +1 -1
- package/dist/{ingestTypes-CIy1lHjf.js → ingestTypes-GkG7bJdB.js} +2 -2
- package/dist/{ingestTypes-CIy1lHjf.js.map → ingestTypes-GkG7bJdB.js.map} +1 -1
- package/dist/{itemId-BY2YPBkT.js → itemId-4P9S_ehz.js} +48 -2
- package/dist/itemId-4P9S_ehz.js.map +1 -0
- package/dist/{itemId-CEQdK01z.cjs → itemId-CGT2J7YK.cjs} +65 -1
- package/dist/itemId-CGT2J7YK.cjs.map +1 -0
- package/dist/plugin-vue/IconGlyph.d.ts +27 -0
- package/dist/plugin-vue/index.cjs +51 -0
- package/dist/plugin-vue/index.cjs.map +1 -1
- package/dist/plugin-vue/index.d.ts +1 -0
- package/dist/plugin-vue/index.js +52 -2
- package/dist/plugin-vue/index.js.map +1 -1
- package/dist/{promptSafety-Bp780ezk.js → promptSafety-Bte62OvU.js} +2 -2
- package/dist/{promptSafety-Bp780ezk.js.map → promptSafety-Bte62OvU.js.map} +1 -1
- package/dist/{promptSafety-C0BWDsfT.cjs → promptSafety-Cv-SoTd4.cjs} +2 -2
- package/dist/{promptSafety-C0BWDsfT.cjs.map → promptSafety-Cv-SoTd4.cjs.map} +1 -1
- package/dist/{root-BMroU_mB.js → root-BcS4-Kge.js} +12 -2
- package/dist/root-BcS4-Kge.js.map +1 -0
- package/dist/{root-rPH6FGDT.cjs → root-BvwBUcol.cjs} +12 -2
- package/dist/root-BvwBUcol.cjs.map +1 -0
- package/dist/{server-BvXO49Vj.js → server-BENtFplH.js} +47 -18
- package/dist/{server-BvXO49Vj.js.map → server-BENtFplH.js.map} +1 -1
- package/dist/{server-CF1qllGV.cjs → server-Wz4_ALLl.cjs} +47 -18
- package/dist/{server-CF1qllGV.cjs.map → server-Wz4_ALLl.cjs.map} +1 -1
- package/package.json +10 -10
- package/dist/discovery-Dl9nRa-m.cjs.map +0 -1
- package/dist/discovery-P64fgtuD.js.map +0 -1
- package/dist/itemId-BY2YPBkT.js.map +0 -1
- package/dist/itemId-CEQdK01z.cjs.map +0 -1
- package/dist/root-BMroU_mB.js.map +0 -1
- package/dist/root-rPH6FGDT.cjs.map +0 -1
|
@@ -128,3 +128,16 @@ Desktop and phone views have **incompatible runtime contracts** — a desktop vi
|
|
|
128
128
|
records with an injected token, while a `target: "mobile"` view gets them over a postMessage bridge
|
|
129
129
|
and cannot `fetch` at all. A view authored against the wrong contract renders empty with no error.
|
|
130
130
|
Check which target it is registered as before treating it as a rendering fault.
|
|
131
|
+
|
|
132
|
+
## The narration is not the voice I asked for, or a small tweak re-recorded every beat
|
|
133
|
+
|
|
134
|
+
help: mulmoscript.md
|
|
135
|
+
help: error-recovery.md
|
|
136
|
+
|
|
137
|
+
Speech settings resolve through fallbacks that raise no error, so read the script's own
|
|
138
|
+
`speechParams` block against the speaker reference in `mulmoscript.md` (§ speechParams) before
|
|
139
|
+
treating it as a bug. Three mechanisms explain nearly every report: a `lang` entry replaces the
|
|
140
|
+
whole speaker rather than extending it, each delivery option reaches some providers and is dropped
|
|
141
|
+
by others, and the audio cache key of each beat is built from the speaker's own fields — so editing
|
|
142
|
+
one of them re-records every beat that speaker narrates. Which field is missing decides which
|
|
143
|
+
symptom you are looking at.
|
package/assets/helps/business.md
CHANGED
|
@@ -18,6 +18,10 @@ Reach for `presentMulmoScript` when the user asks for a presentation, slideshow,
|
|
|
18
18
|
- `markdown` — rich text, tables, mixed content
|
|
19
19
|
- Do NOT use `imagePrompt` or `moviePrompt` in business presentations
|
|
20
20
|
- Write concise, professional narration text for each beat (becomes the voiceover)
|
|
21
|
+
- Leave the speaker's `model` out unless the user asks for higher-quality narration
|
|
22
|
+
(`"model": "gemini-2.5-pro-preview-tts"`), and set it before the first render — voice / model /
|
|
23
|
+
`speechOptions` form the audio cache key, so a later change re-records every beat
|
|
24
|
+
(full speaker reference: `config/helps/mulmoscript.md` → speechParams)
|
|
21
25
|
- Put a 1–2 sentence summary of the whole presentation in the top-level `description` field
|
|
22
26
|
|
|
23
27
|
## Template
|
|
@@ -55,7 +55,10 @@ collection's view-mode selector automatically.
|
|
|
55
55
|
|
|
56
56
|
## The runtime contract — `window.__MC_VIEW`
|
|
57
57
|
|
|
58
|
-
The host injects a bootstrap
|
|
58
|
+
The host injects a bootstrap at the very start of your `<head>`, so it runs
|
|
59
|
+
**before any script in your `<head>` or `<body>`** — write your view normally
|
|
60
|
+
and `window.__MC_VIEW` is always there. (A `<script>` placed _before_ `<head>`
|
|
61
|
+
would run ahead of it and see no `__MC_VIEW`; there is no reason to write one.)
|
|
59
62
|
|
|
60
63
|
```js
|
|
61
64
|
window.__MC_VIEW = {
|
|
@@ -63,6 +66,8 @@ window.__MC_VIEW = {
|
|
|
63
66
|
token: "<scoped capability token>", // Authorization bearer
|
|
64
67
|
dataUrl: "http://localhost:3001/api/collections/annual-plan/view-data",
|
|
65
68
|
onChange: (cb) => unsubscribe, // live refresh — see "Staying live" below
|
|
69
|
+
searchQuery: "", // live text in the app's own search box — see "One search box"
|
|
70
|
+
onSearchQueryChange: (cb) => unsubscribe, // fires when the user types there
|
|
66
71
|
openItem: (id, mode) => void, // open a record in the host's panel — see "Opening a record"
|
|
67
72
|
startChat: (prompt, role) => void, // draft a new chat for the user — see "Starting a chat"
|
|
68
73
|
};
|
|
@@ -299,6 +304,69 @@ What you need to know:
|
|
|
299
304
|
- It returns an **unsubscribe** function; you rarely need it (the view is torn
|
|
300
305
|
down with the iframe), but it's there for fine-grained control.
|
|
301
306
|
|
|
307
|
+
### One search box — `searchQuery` / `onSearchQueryChange`
|
|
308
|
+
|
|
309
|
+
The app's **standard search box stays on screen while your view renders**, so
|
|
310
|
+
the user expects it to drive your view too. It does: the host relays what they
|
|
311
|
+
type into the sandboxed frame.
|
|
312
|
+
|
|
313
|
+
**Do not build a second search box.** Read the host's, and you get the
|
|
314
|
+
behaviour the user already assumes.
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
const v = window.__MC_VIEW;
|
|
318
|
+
|
|
319
|
+
function render() {
|
|
320
|
+
const q = v.searchQuery.trim().toLowerCase();
|
|
321
|
+
const hits = q ? records.filter((r) => matches(r, q)) : records;
|
|
322
|
+
// …draw hits, highlight where each one matched…
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
v.onSearchQueryChange(render); // the user typed in the app's search box
|
|
326
|
+
v.onChange(reload); // the data changed (a separate wire — see above)
|
|
327
|
+
render();
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
What you need to know:
|
|
331
|
+
|
|
332
|
+
- **`searchQuery` is always current.** It updates the instant the user types, so
|
|
333
|
+
reading it inside your own render (as above) is never a keystroke behind.
|
|
334
|
+
- **The callback is debounced** — it runs once typing pauses (~150 ms), not
|
|
335
|
+
once per keystroke — and receives the query as its argument
|
|
336
|
+
(`onSearchQueryChange((q) => …)`), so you can use either source.
|
|
337
|
+
- **An empty string means the box is empty** — show everything, don't show
|
|
338
|
+
nothing. It fires on clearing too.
|
|
339
|
+
- **It is scoped to this collection and this session.** Switching collections
|
|
340
|
+
clears the box; nothing is persisted.
|
|
341
|
+
- **Navigating your frame away ends it.** The query arrives on a private
|
|
342
|
+
channel bound to the document the host built, so a page your view navigates
|
|
343
|
+
to gets no further queries — deliberately, since the text is the user's own.
|
|
344
|
+
If that page asks the host for the channel, the host reinstalls your view
|
|
345
|
+
over it rather than handing it over. Open outbound links with
|
|
346
|
+
`target="_blank"` (see the sandbox rules) and your view keeps running.
|
|
347
|
+
- **Reloading your own view is fine.** `location.reload()` makes the host
|
|
348
|
+
reinstall the view and hand it a fresh channel, seeded with whatever is in
|
|
349
|
+
the search box — so the query you see after a reload is the current one, not
|
|
350
|
+
an empty string.
|
|
351
|
+
- **Replacing the document is allowed a few times, not endlessly.** Each
|
|
352
|
+
reinstall — after a reload or after another page claims the channel — costs
|
|
353
|
+
the host a rebuild, so it grants **at most 3** between one user-initiated
|
|
354
|
+
view change and the next (switching view or collection resets the budget).
|
|
355
|
+
Past that your view still renders and still reads its data; only the search
|
|
356
|
+
channel stays disconnected, and `searchQuery` stops updating. Use `onChange`
|
|
357
|
+
to refresh rather than reloading the frame, and you will never reach it.
|
|
358
|
+
- **One direction only.** You read the user's query; you cannot write the app's
|
|
359
|
+
search box from view code. If your view needs a filter the box can't express
|
|
360
|
+
(a date range, a facet), add that control yourself and combine it with
|
|
361
|
+
`searchQuery`.
|
|
362
|
+
- **No extra capability needed**, and it is **not** a data reload: it never
|
|
363
|
+
fires `onChange`, and `onChange` never fires this.
|
|
364
|
+
|
|
365
|
+
This is what makes a "search" view worth writing — the built-in box only
|
|
366
|
+
filters table rows, so a view that shows **which field matched**, with a
|
|
367
|
+
highlighted snippet, is a real upgrade the user drives from the box they were
|
|
368
|
+
already using.
|
|
369
|
+
|
|
302
370
|
### Opening a record — `openItem`
|
|
303
371
|
|
|
304
372
|
Your view owns the _layout_ (a grid, a chart, a board); it doesn't have to
|
|
@@ -225,6 +225,41 @@ shipped registry repo's README. Common rejections:
|
|
|
225
225
|
- `name` reuses the reserved value `official`.
|
|
226
226
|
- `name` doesn't match `[A-Za-z0-9][A-Za-z0-9_-]{0,31}`.
|
|
227
227
|
|
|
228
|
+
## A collection's icon shows up as letters, or smears over its neighbours
|
|
229
|
+
|
|
230
|
+
### Symptoms
|
|
231
|
+
|
|
232
|
+
- A collection / feed / pinned shortcut draws its icon NAME as text
|
|
233
|
+
(`podcasts`, or a fragment like `_A__C`) instead of a glyph.
|
|
234
|
+
- The text spills out of its square and overlaps the buttons next to it
|
|
235
|
+
in the launcher bar.
|
|
236
|
+
|
|
237
|
+
### Cause + fix
|
|
238
|
+
|
|
239
|
+
`schema.icon` was set to something the Material Symbols font has no
|
|
240
|
+
ligature for. The font matches icons by LIGATURE, so an unknown value
|
|
241
|
+
is simply laid out as ordinary text.
|
|
242
|
+
|
|
243
|
+
Valid names are lowercase letters, digits and underscores only —
|
|
244
|
+
`podcasts`, `rss_feed`, `menu_book`, `3d_rotation`. Capitals, spaces
|
|
245
|
+
and hyphens never match (`Podcasts` and `menu-book` are the usual
|
|
246
|
+
typos). Look the name up in the Material Symbols set before writing it;
|
|
247
|
+
do not invent one.
|
|
248
|
+
|
|
249
|
+
An **emoji is a valid alternative** and is often the better choice: it
|
|
250
|
+
is in colour and stands out among the monochrome glyphs, which is the
|
|
251
|
+
point when several collections would otherwise share a look-alike icon
|
|
252
|
+
(`podcasts` / `rss_feed` / `menu_book`). Set `"icon": "🎙️"` the same way you
|
|
253
|
+
would set a name. Only the first GRAPHEME is drawn — one emoji, whatever
|
|
254
|
+
number of code points it is built from (a variation selector, a skin tone, a
|
|
255
|
+
ZWJ family) — so write exactly one glyph and nothing after it.
|
|
256
|
+
|
|
257
|
+
If the smearing persists after the value is corrected, the surface
|
|
258
|
+
drawing it is bypassing `IconGlyph`
|
|
259
|
+
(`@mulmoclaude/core/plugin-vue`) — every schema-authored icon must go
|
|
260
|
+
through it rather than into a bare
|
|
261
|
+
`<span class="material-symbols-outlined">`.
|
|
262
|
+
|
|
228
263
|
## A hand-placed custom role never appears in the list
|
|
229
264
|
|
|
230
265
|
### Symptoms
|
|
@@ -365,6 +400,42 @@ missing key to `.env` (restart the server) or rewrite the script's
|
|
|
365
400
|
keys configured. Don't retry the render unchanged — the same provider
|
|
366
401
|
will fail the same way.
|
|
367
402
|
|
|
403
|
+
## MulmoScript narration — wrong voice, ignored direction, or every beat re-recorded
|
|
404
|
+
|
|
405
|
+
### Symptoms
|
|
406
|
+
|
|
407
|
+
- A non-default language (`speechParams.speakers.<name>.lang.<code>`) renders
|
|
408
|
+
in a different voice, or fails on a missing `OPENAI_API_KEY` even though the
|
|
409
|
+
script names `gemini` everywhere.
|
|
410
|
+
- `speechOptions.instruction` or `speed` has no audible effect on the narration.
|
|
411
|
+
- A small edit to a speaker re-generates audio for **every** beat, including the
|
|
412
|
+
ones whose text never changed.
|
|
413
|
+
|
|
414
|
+
### Cause
|
|
415
|
+
|
|
416
|
+
Speech settings resolve through silent fallbacks — the schema does not reject a
|
|
417
|
+
missing field, it fills one in:
|
|
418
|
+
|
|
419
|
+
- A `lang` entry **replaces** the whole speaker; it does not extend it. Whatever
|
|
420
|
+
the entry omits falls back to the schema and provider defaults, so one holding
|
|
421
|
+
only `voiceId` loses `provider` (which then resolves to `openai`), `model`, and
|
|
422
|
+
`speechOptions`.
|
|
423
|
+
- `instruction` is dropped by the `google` provider unless the speaker also sets
|
|
424
|
+
a `model`; `speed` is ignored by `gemini` altogether.
|
|
425
|
+
- Each beat's audio file is cached under a hash of that beat's `text` plus the
|
|
426
|
+
speaker's `voiceId` + `provider` + `model` + `speechOptions`. Editing the
|
|
427
|
+
speaker is a new cache key for every beat that speaks through it — the whole
|
|
428
|
+
script when there is one speaker — so it is re-recorded and re-billed.
|
|
429
|
+
|
|
430
|
+
### Fix
|
|
431
|
+
|
|
432
|
+
Repeat every field the parent speaker sets — `provider` above all — inside each
|
|
433
|
+
`lang` entry. Express pacing for Gemini through `instruction` rather than
|
|
434
|
+
`speed`. Settle voice, model, and delivery **before** the first render — and when
|
|
435
|
+
a change is genuinely wanted, say up front that every beat of that speaker
|
|
436
|
+
re-records, rather than letting the bill surprise the user. Field-by-field
|
|
437
|
+
reference: `config/helps/mulmoscript.md` → speechParams.
|
|
438
|
+
|
|
368
439
|
## Build / yarn workspace ordering
|
|
369
440
|
|
|
370
441
|
### Symptoms
|
package/assets/helps/gemini.md
CHANGED
|
@@ -15,7 +15,7 @@ A single key unlocks all three capabilities (images, TTS audio, video) — you d
|
|
|
15
15
|
|
|
16
16
|
### Audio
|
|
17
17
|
|
|
18
|
-
- **MulmoScript speech** — `presentMulmoScript` synthesizes speaker voices via Gemini TTS
|
|
18
|
+
- **MulmoScript speech** — `presentMulmoScript` synthesizes speaker voices via Gemini TTS. This is what turns a storyboard into spoken narration, so the **Storyteller** and **Storyteller Plus** roles become near-complete multimedia pieces. Speakers record with whatever the provider's current default is (today `gemini-2.5-flash-preview-tts`) unless the script names a model itself — a speaker can set `"model": "gemini-2.5-pro-preview-tts"` for higher-quality narration at about twice the token price (`config/helps/mulmoscript.md` → speechParams).
|
|
19
19
|
|
|
20
20
|
### Video
|
|
21
21
|
|
|
@@ -197,7 +197,77 @@ Configure TTS voices per speaker.
|
|
|
197
197
|
}
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
| Field | Required | Description |
|
|
201
|
+
|---|---|---|
|
|
202
|
+
| `voiceId` | Yes | Provider voice name. Gemini voices are names like `"Kore"`, `"Schedar"`, `"Aoede"` — `config/helps/storyteller.md` lists them by tone |
|
|
203
|
+
| `provider` | No | **Falls back to `openai`, not Gemini** — always write `"provider": "gemini"` |
|
|
204
|
+
| `model` | No | TTS model. Omitted → the provider's default (Gemini: `gemini-2.5-flash-preview-tts`) |
|
|
205
|
+
| `isDefault` | No | Beats with no `speaker` field use this speaker |
|
|
206
|
+
| `displayName` | No | Per-language label for the speaker |
|
|
207
|
+
| `speechOptions` | No | How the line is delivered — see below |
|
|
208
|
+
| `lang` | No | Per-language speaker **replacement** — see below |
|
|
209
|
+
|
|
210
|
+
Nothing here is validated against the provider: `model` is a free-form string,
|
|
211
|
+
and a missing `provider` / `model` is not rejected when the script is parsed — it
|
|
212
|
+
resolves to a default, which may then be wrong at render time or merely audible.
|
|
213
|
+
Set them deliberately rather than relying on the fallback.
|
|
214
|
+
|
|
215
|
+
### Choosing a model
|
|
216
|
+
|
|
217
|
+
Gemini TTS accepts two models:
|
|
218
|
+
|
|
219
|
+
| Model | Cost | Use when |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `gemini-2.5-flash-preview-tts` (default) | 1× | Everything, by default — leave `model` out |
|
|
222
|
+
| `gemini-2.5-pro-preview-tts` | ~2× the token price | The user asks for higher-quality or more expressive narration |
|
|
223
|
+
|
|
224
|
+
**Decide before the first render.** Each beat's audio file is cached under a hash
|
|
225
|
+
of that beat's `text` plus the speaker's `voiceId` + `provider` + `model` +
|
|
226
|
+
`speechOptions`. Rewriting one beat's text therefore re-records that beat alone,
|
|
227
|
+
but touching the speaker changes the key of **every beat that speaks through it**
|
|
228
|
+
— in a single-speaker script, the whole thing, billed again. Leave `model` out to
|
|
229
|
+
track the provider default; add it only when you mean to pin a different model.
|
|
230
|
+
|
|
231
|
+
### `speechOptions`
|
|
232
|
+
|
|
233
|
+
`beats[].speechOptions` merges over the speaker's for that one beat, so a single
|
|
234
|
+
line can be delivered differently without touching the speaker.
|
|
235
|
+
|
|
236
|
+
| Option | Honored by | Notes |
|
|
237
|
+
|---|---|---|
|
|
238
|
+
| `instruction` | `gemini`, `openai` | Direction for delivery, sent as DIRECTOR'S NOTES ahead of the text. Under `google` it is **dropped unless `model` is set** |
|
|
239
|
+
| `speed` | `openai`, `google`, `elevenlabs` | **Silently ignored by `gemini`** — ask for the pace in `instruction` instead |
|
|
240
|
+
|
|
241
|
+
(`stability` / `similarity_boost` belong to `elevenlabs` and `decoration` to
|
|
242
|
+
`kotodama`; neither provider is configured in this app.)
|
|
243
|
+
|
|
244
|
+
### `lang` — a replacement, not an override
|
|
245
|
+
|
|
246
|
+
A `lang` entry **replaces the whole speaker** for that language: whatever it
|
|
247
|
+
omits falls back to the schema and provider defaults, never to the parent's
|
|
248
|
+
value. Repeat every field the parent sets — `provider` above all, since an entry
|
|
249
|
+
holding only `voiceId` resolves to the `openai` provider and then fails on a
|
|
250
|
+
missing `OPENAI_API_KEY` (or rejects the Gemini voice name) even though the
|
|
251
|
+
script names Gemini everywhere.
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"speakers": {
|
|
256
|
+
"Presenter": {
|
|
257
|
+
"provider": "gemini",
|
|
258
|
+
"voiceId": "Kore",
|
|
259
|
+
"speechOptions": { "instruction": "Warm and unhurried." },
|
|
260
|
+
"lang": {
|
|
261
|
+
"ja": {
|
|
262
|
+
"provider": "gemini",
|
|
263
|
+
"voiceId": "Kore",
|
|
264
|
+
"speechOptions": { "instruction": "Warm and unhurried." }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
201
271
|
|
|
202
272
|
## audioParams
|
|
203
273
|
|
|
@@ -26,7 +26,10 @@ override the generic guidance above:
|
|
|
26
26
|
- **Tool**: hand the finished JSON to the `presentMulmoScript` tool — that is how a deck is rendered here.
|
|
27
27
|
- **Narration / TTS**: declare a speaker once in `speechParams.speakers` with `isDefault: true` and
|
|
28
28
|
the **Google** provider (`"provider": "gemini", "voiceId": "Kore"`). Beats then inherit it without
|
|
29
|
-
repeating `speaker`. Both samples below include this block — keep it.
|
|
29
|
+
repeating `speaker`. Both samples below include this block — keep it. Leave `model` out unless the
|
|
30
|
+
user asks for higher-quality narration (`"model": "gemini-2.5-pro-preview-tts"`), and set it before
|
|
31
|
+
the first render — voice / model / `speechOptions` are part of the audio cache key, so a later change
|
|
32
|
+
re-records every beat. Full speaker reference: `config/helps/mulmoscript.md` → speechParams.
|
|
30
33
|
- **Providers are Google-only.** This app configures only Google providers; never emit `openai`,
|
|
31
34
|
`elevenlabs`, etc. Neither approach generates AI images or video, so `imageParams` / `movieParams`
|
|
32
35
|
are not needed (the `slide` type renders static layouts; `html_tailwind` + `animation` renders from
|
|
@@ -31,6 +31,13 @@ Set `speechOptions.instruction` on the Narrator speaker to match the story's ton
|
|
|
31
31
|
| Deep / authoritative | Alnilam, Iapetus, Algieba |
|
|
32
32
|
| Soft / gentle | Aoede, Umbriel, Laomedeia, Achernar, Rasalgethi, Pulcherrima, Vindemiatrix, Sadachbia, Sadaltager, Zubenelgenubi |
|
|
33
33
|
|
|
34
|
+
Leave `model` out — the speaker then records with Gemini's default TTS model. Add
|
|
35
|
+
`"model": "gemini-2.5-pro-preview-tts"` only when the user asks for higher-quality
|
|
36
|
+
narration, and add it **before the first render**: voice, model, and
|
|
37
|
+
`speechOptions` are part of the audio cache key, so changing one later re-records
|
|
38
|
+
every beat. Full speaker reference (including the `lang` map, which *replaces* the
|
|
39
|
+
speaker rather than extending it): `config/helps/mulmoscript.md` → speechParams.
|
|
40
|
+
|
|
34
41
|
## Other Rules
|
|
35
42
|
|
|
36
43
|
- Always use Google providers (`gemini` for TTS, `google` for image generation).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The colours a schema may name, ordered around the hue circle.
|
|
2
|
+
*
|
|
3
|
+
* Deliberately EXCLUDES the warm band (red / orange / amber): those are the
|
|
4
|
+
* notification severities (`ENUM_ALERT` red, `ENUM_NUDGE` amber in
|
|
5
|
+
* `enumColors.ts`) and the launcher's own session badges, so an accent can
|
|
6
|
+
* never make a shortcut read as "something needs attention".
|
|
7
|
+
*
|
|
8
|
+
* Seven rather than eight: rendering all eight candidates on one identical
|
|
9
|
+
* glyph put `cyan` squarely between `sky` and `teal`, so a pill could hold
|
|
10
|
+
* three shortcuts in the same blue-green band. A colour that cannot be told
|
|
11
|
+
* from its neighbour identifies nothing, which is the entire job here — the
|
|
12
|
+
* set is sized for separation, not for count. */
|
|
13
|
+
export declare const ACCENT_COLORS: readonly ["violet", "indigo", "sky", "teal", "emerald", "lime", "fuchsia"];
|
|
14
|
+
export type AccentColor = (typeof ACCENT_COLORS)[number];
|
|
15
|
+
/** Narrowing guard: is `value` one of the colours a schema may name? */
|
|
16
|
+
export declare function isAccentColor(value: unknown): value is AccentColor;
|
|
17
|
+
/** The chip classes for `color`, or `null` when there is no accent to draw —
|
|
18
|
+
* absent, blank, or a name this palette does not carry.
|
|
19
|
+
*
|
|
20
|
+
* Unknown names fail soft rather than throwing, matching how `icon` is
|
|
21
|
+
* handled: a schema is user/LLM-authored, and a typo should cost the colour,
|
|
22
|
+
* not the surface. The caller falls back to its own unstyled treatment. */
|
|
23
|
+
export declare function accentChipClasses(color: string | undefined): string | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** A resolved icon value: either a ligature name for the icon font, or a
|
|
2
|
+
* literal glyph (emoji, a single letter) drawn as plain text. */
|
|
3
|
+
export type IconGlyph = {
|
|
4
|
+
kind: "symbol";
|
|
5
|
+
name: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "glyph";
|
|
8
|
+
text: string;
|
|
9
|
+
};
|
|
10
|
+
/** Drawn when neither the value nor the surface's own fallback says anything.
|
|
11
|
+
* Without it a blank `fallback` resolves to an EMPTY glyph and the surface
|
|
12
|
+
* renders a hole — this function promises a drawable result for every input,
|
|
13
|
+
* so the promise needs a floor. */
|
|
14
|
+
export declare const DEFAULT_ICON = "dataset";
|
|
15
|
+
/** Classify a schema-authored icon value.
|
|
16
|
+
*
|
|
17
|
+
* `raw` empty / blank / absent falls back to `fallback` (the per-surface
|
|
18
|
+
* default such as `"dataset"`), and a blank `fallback` in turn to
|
|
19
|
+
* {@link DEFAULT_ICON}. Both are classified by the same rule — so a caller
|
|
20
|
+
* can never smuggle an unrenderable default past the guard, and the result
|
|
21
|
+
* is always something drawable.
|
|
22
|
+
*
|
|
23
|
+
* A non-name value is cut to ONE grapheme. That bounds the character count,
|
|
24
|
+
* not the rendered width — see the containment note in
|
|
25
|
+
* `plugin-vue/IconGlyph.ts` for the other half. */
|
|
26
|
+
export declare function resolveIconGlyph(raw: string | undefined, fallback: string): IconGlyph;
|
|
@@ -186,6 +186,15 @@ export interface CollectionSummary {
|
|
|
186
186
|
slug: string;
|
|
187
187
|
title: string;
|
|
188
188
|
icon: string;
|
|
189
|
+
/** Optional accent colour name (see `ACCENT_COLORS`), drawn as a pale chip
|
|
190
|
+
* behind the launcher glyph. Absent when the schema names none.
|
|
191
|
+
*
|
|
192
|
+
* Declared NARROW (no `| undefined`), like its `appId` / `readonly`
|
|
193
|
+
* siblings: the remote-host handlers pass these shapes through `Jsonify`,
|
|
194
|
+
* which drops `undefined` from a member union, so a widened optional stops
|
|
195
|
+
* being assignable there. Producers therefore ADD the key conditionally
|
|
196
|
+
* rather than assigning `undefined` to it. */
|
|
197
|
+
color?: string;
|
|
189
198
|
source: CollectionSource;
|
|
190
199
|
/** Present (true) when the collection is backed by an external
|
|
191
200
|
* `dataSource` and therefore read-only in every UI/tool write path.
|
|
@@ -693,6 +693,15 @@ export declare const StorageZ: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
693
693
|
* this parses to, and nothing narrower. */
|
|
694
694
|
declare const CollectionObjectZ: z.ZodObject<{
|
|
695
695
|
title: z.ZodString;
|
|
696
|
+
color: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
|
|
697
|
+
violet: "violet";
|
|
698
|
+
indigo: "indigo";
|
|
699
|
+
sky: "sky";
|
|
700
|
+
teal: "teal";
|
|
701
|
+
emerald: "emerald";
|
|
702
|
+
lime: "lime";
|
|
703
|
+
fuchsia: "fuchsia";
|
|
704
|
+
}>>>;
|
|
696
705
|
icon: z.ZodString;
|
|
697
706
|
dataPath: z.ZodOptional<z.ZodString>;
|
|
698
707
|
dataSource: z.ZodOptional<z.ZodObject<{
|
|
@@ -1175,6 +1184,15 @@ declare const CollectionObjectZ: z.ZodObject<{
|
|
|
1175
1184
|
export type CollectionSchemaInput = z.infer<typeof CollectionObjectZ>;
|
|
1176
1185
|
export declare const CollectionSchemaZ: z.ZodPreprocess<z.ZodObject<{
|
|
1177
1186
|
title: z.ZodString;
|
|
1187
|
+
color: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
|
|
1188
|
+
violet: "violet";
|
|
1189
|
+
indigo: "indigo";
|
|
1190
|
+
sky: "sky";
|
|
1191
|
+
teal: "teal";
|
|
1192
|
+
emerald: "emerald";
|
|
1193
|
+
lime: "lime";
|
|
1194
|
+
fuchsia: "fuchsia";
|
|
1195
|
+
}>>>;
|
|
1178
1196
|
icon: z.ZodString;
|
|
1179
1197
|
dataPath: z.ZodOptional<z.ZodString>;
|
|
1180
1198
|
dataSource: z.ZodOptional<z.ZodObject<{
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CollectionShortcutInfo } from './uiTypes';
|
|
2
|
+
/** The fields an index row supplies. Both `CollectionSummary` and
|
|
3
|
+
* `FeedSummary` satisfy it; `icon` is optional because a feed row may carry
|
|
4
|
+
* an empty one and rely on the surface's default. */
|
|
5
|
+
export interface ShortcutInfoSource {
|
|
6
|
+
slug: string;
|
|
7
|
+
title: string;
|
|
8
|
+
icon?: string | undefined;
|
|
9
|
+
color?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/** An index row as shortcut-reconcile input. `fallbackIcon` stands in when the
|
|
12
|
+
* row names no icon (feeds do this); `color` is omitted entirely rather than
|
|
13
|
+
* set to `undefined` when the row has none. */
|
|
14
|
+
export declare function toShortcutInfo(row: ShortcutInfoSource, fallbackIcon: string): CollectionShortcutInfo;
|
|
@@ -122,6 +122,9 @@ export interface FeedSummary {
|
|
|
122
122
|
slug: string;
|
|
123
123
|
title: string;
|
|
124
124
|
icon: string;
|
|
125
|
+
/** Optional accent colour name (see `ACCENT_COLORS`), drawn as a pale chip
|
|
126
|
+
* behind the launcher glyph. Absent when the schema names none. */
|
|
127
|
+
color?: string;
|
|
125
128
|
kind: string;
|
|
126
129
|
schedule: string;
|
|
127
130
|
lastFetchedAt: string | null;
|
|
@@ -136,4 +139,7 @@ export interface CollectionShortcutInfo {
|
|
|
136
139
|
slug: string;
|
|
137
140
|
title: string;
|
|
138
141
|
icon: string;
|
|
142
|
+
/** Optional accent colour name (see `ACCENT_COLORS`), drawn as a pale chip
|
|
143
|
+
* behind the launcher glyph. Absent when the schema names none. */
|
|
144
|
+
color?: string;
|
|
139
145
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_dist = require("../dist-CsgSfWwR.cjs");
|
|
3
|
-
const require_itemId = require("../itemId-
|
|
4
|
-
const require_promptSafety = require("../promptSafety-
|
|
3
|
+
const require_itemId = require("../itemId-CGT2J7YK.cjs");
|
|
4
|
+
const require_promptSafety = require("../promptSafety-Cv-SoTd4.cjs");
|
|
5
5
|
const require_project = require("../project-C1ep9pvo.cjs");
|
|
6
|
+
const require_iconGlyph = require("../iconGlyph-fSBp6k4J.cjs");
|
|
6
7
|
//#region src/collection/core/presentCollection.ts
|
|
7
8
|
var TOOL_NAME = "presentCollection";
|
|
8
9
|
/** Stamp a host's opaque project scope onto a card payload. The ONLY way a
|
|
@@ -365,6 +366,19 @@ function skillCommandSeed(slug, message, itemId) {
|
|
|
365
366
|
return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;
|
|
366
367
|
}
|
|
367
368
|
//#endregion
|
|
369
|
+
//#region src/collection/core/shortcutInfo.ts
|
|
370
|
+
/** An index row as shortcut-reconcile input. `fallbackIcon` stands in when the
|
|
371
|
+
* row names no icon (feeds do this); `color` is omitted entirely rather than
|
|
372
|
+
* set to `undefined` when the row has none. */
|
|
373
|
+
function toShortcutInfo(row, fallbackIcon) {
|
|
374
|
+
return {
|
|
375
|
+
slug: row.slug,
|
|
376
|
+
title: row.title,
|
|
377
|
+
icon: row.icon && row.icon.length > 0 ? row.icon : fallbackIcon,
|
|
378
|
+
...row.color === void 0 ? {} : { color: row.color }
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
//#endregion
|
|
368
382
|
//#region src/collection/core/sortItems.ts
|
|
369
383
|
var EMPTY = { empty: true };
|
|
370
384
|
/** Field types that render no value text in the table, so offer no sort. */
|
|
@@ -635,8 +649,10 @@ var buildOntologyGraph = (entries) => {
|
|
|
635
649
|
};
|
|
636
650
|
};
|
|
637
651
|
//#endregion
|
|
652
|
+
exports.ACCENT_COLORS = require_itemId.ACCENT_COLORS;
|
|
638
653
|
exports.AGENT_INGEST_KIND = require_itemId.AGENT_INGEST_KIND;
|
|
639
654
|
exports.COMPUTED_TYPES = require_itemId.COMPUTED_TYPES;
|
|
655
|
+
exports.DEFAULT_ICON = require_iconGlyph.DEFAULT_ICON;
|
|
640
656
|
exports.ENUM_ALERT = ENUM_ALERT;
|
|
641
657
|
exports.ENUM_NEUTRAL = ENUM_NEUTRAL;
|
|
642
658
|
exports.ENUM_NUDGE = ENUM_NUDGE;
|
|
@@ -647,6 +663,7 @@ exports.SAFE_RECORD_ID_PATTERN = require_itemId.SAFE_RECORD_ID_PATTERN;
|
|
|
647
663
|
exports.SAFE_SLUG_PATTERN = require_itemId.SAFE_SLUG_PATTERN;
|
|
648
664
|
exports.TOOL_DEFINITION = TOOL_DEFINITION;
|
|
649
665
|
exports.TOOL_NAME = TOOL_NAME;
|
|
666
|
+
exports.accentChipClasses = require_itemId.accentChipClasses;
|
|
650
667
|
exports.actionVisible = require_promptSafety.actionVisible;
|
|
651
668
|
exports.agentActionRunKey = require_promptSafety.agentActionRunKey;
|
|
652
669
|
exports.assignLanes = require_itemId.assignLanes;
|
|
@@ -694,6 +711,7 @@ exports.firstMissingRequiredField = firstMissingRequiredField;
|
|
|
694
711
|
exports.firstUnknownDefault = require_promptSafety.firstUnknownDefault;
|
|
695
712
|
exports.flagFieldValue = require_promptSafety.flagFieldValue;
|
|
696
713
|
exports.generateUniqueId = require_itemId.generateUniqueId;
|
|
714
|
+
exports.isAccentColor = require_itemId.isAccentColor;
|
|
697
715
|
exports.isCanonicalServerTime = require_itemId.isCanonicalServerTime;
|
|
698
716
|
exports.isFieldDrivenEvery = require_itemId.isFieldDrivenEvery;
|
|
699
717
|
exports.isLocalCollectionKey = require_itemId.isLocalCollectionKey;
|
|
@@ -725,6 +743,7 @@ exports.projectRecordFields = require_project.projectRecordFields;
|
|
|
725
743
|
exports.recordSpan = require_itemId.recordSpan;
|
|
726
744
|
exports.resolveEnumColor = resolveEnumColor;
|
|
727
745
|
exports.resolveIcon = require_promptSafety.resolveIcon;
|
|
746
|
+
exports.resolveIconGlyph = require_iconGlyph.resolveIconGlyph;
|
|
728
747
|
exports.resolveRowRefs = require_promptSafety.resolveRowRefs;
|
|
729
748
|
exports.rollupValue = require_promptSafety.rollupValue;
|
|
730
749
|
exports.rowFromItem = rowFromItem;
|
|
@@ -746,6 +765,7 @@ exports.spanCoversDay = require_itemId.spanCoversDay;
|
|
|
746
765
|
exports.storageKindFor = require_itemId.storageKindFor;
|
|
747
766
|
exports.stringSortValue = stringSortValue;
|
|
748
767
|
exports.toPresentCollectionData = toPresentCollectionData;
|
|
768
|
+
exports.toShortcutInfo = toShortcutInfo;
|
|
749
769
|
exports.toggleChecked = require_promptSafety.toggleChecked;
|
|
750
770
|
exports.uniqueBacklinkSources = require_promptSafety.uniqueBacklinkSources;
|
|
751
771
|
exports.uniqueEmbedTargets = require_promptSafety.uniqueEmbedTargets;
|