@vosjs/shared 0.1.0 → 0.2.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.
@@ -200,6 +200,7 @@ var ID_TRACKS = [
200
200
  ["tilt", "tilt"],
201
201
  ["camMove", "camMotion"],
202
202
  ["speed", "speed"],
203
+ ["rejected", "rejected"],
203
204
  ["overlay", "overlays"],
204
205
  ["object", "objects"],
205
206
  ["audio", "audio"]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/diff/internal.ts","../../src/diff/diffConfig.ts","../../src/diff/diffDoc.ts","../../src/diff/types.ts","../../src/diff/coalesce.ts","../../src/diff/summarize.ts"],"sourcesContent":["import type { Op } from './types'\n\nexport type AnyRecord = Record<string, unknown>\n\nexport function isRecord(v: unknown): v is AnyRecord {\n return !!v && typeof v === 'object' && !Array.isArray(v)\n}\n\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true\n if (typeof a !== typeof b) return false\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]))\n }\n if (isRecord(a) && isRecord(b)) {\n const ka = Object.keys(a)\n const kb = Object.keys(b)\n if (ka.length !== kb.length) return false\n return ka.every((k) => deepEqual(a[k], b[k]))\n }\n return false\n}\n\n/**\n * Shallow property diff of two node objects: changed keys → [from, to].\n * Nested values compare deep but report as whole-value pairs — span/clip\n * props are overwhelmingly scalars, and a whole-value pair stays honest for\n * the rest. `ignore` keys (identity fields) never report.\n */\nexport function shallowDiff(\n a: AnyRecord,\n b: AnyRecord,\n ignore: ReadonlySet<string> = new Set(),\n): Record<string, [unknown, unknown]> | undefined {\n const props: Record<string, [unknown, unknown]> = {}\n const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort()\n for (const k of keys) {\n if (ignore.has(k)) continue\n if (!deepEqual(a[k], b[k])) props[k] = [a[k], b[k]]\n }\n return Object.keys(props).length ? props : undefined\n}\n\n/**\n * Diff two id-keyed node lists into add/remove/modify ops. Identity is the\n * id — a moved span with the same id is a modify (the trap case); a\n * remove+add pair is two different nodes by construction.\n */\nexport function diffIdTrack(\n track: string,\n a: readonly AnyRecord[] | undefined,\n b: readonly AnyRecord[] | undefined,\n): Op[] {\n const ops: Op[] = []\n const aById = new Map((a ?? []).map((n) => [String(n.id), n]))\n const bById = new Map((b ?? []).map((n) => [String(n.id), n]))\n for (const [id, node] of aById) {\n if (!bById.has(id)) {\n ops.push({ op: 'remove', track, id, props: pickAnchor(node) })\n }\n }\n for (const [id, node] of bById) {\n const prev = aById.get(id)\n if (!prev) {\n ops.push({ op: 'add', track, id, props: pickAnchor(node) })\n continue\n }\n const props = shallowDiff(prev, node, ID_KEYS)\n if (props) ops.push({ op: 'modify', track, id, props })\n }\n return ops\n}\n\nconst ID_KEYS = new Set(['id'])\n\n/**\n * Anchor props carried on add/remove ops so the summary can place the node\n * in time (\"zoom z5 added (0.8s–2.4s)\") without shipping the whole node.\n */\nfunction pickAnchor(\n node: AnyRecord,\n): Record<string, [unknown, unknown]> | undefined {\n const out: Record<string, [unknown, unknown]> = {}\n for (const k of ['in', 'out', 'at', 'start', 'kind', 'key']) {\n if (node[k] !== undefined) out[k] = [undefined, node[k]]\n }\n return Object.keys(out).length ? out : undefined\n}\n\n/** Magnitude of a function-string change: max(lines added, lines removed). */\nexport function lineDelta(a: string, b: string): number {\n const count = (s: string): Map<string, number> => {\n const m = new Map<string, number>()\n for (const line of s.split('\\n')) m.set(line, (m.get(line) ?? 0) + 1)\n return m\n }\n const ca = count(a)\n const cb = count(b)\n let removed = 0\n let added = 0\n for (const [line, n] of ca) removed += Math.max(0, n - (cb.get(line) ?? 0))\n for (const [line, n] of cb) added += Math.max(0, n - (ca.get(line) ?? 0))\n return Math.max(added, removed)\n}\n","import {\n deepEqual,\n diffIdTrack,\n isRecord,\n lineDelta,\n shallowDiff,\n} from './internal'\nimport type { AnyRecord } from './internal'\nimport type { Op } from './types'\n\n/**\n * Semantic diff over two VosConfigJson states: param keys are\n * program mode's stable ids, so knob values and specs diff fully typed;\n * function-string bodies collapse to a magnitude (\"createContent changed,\n * 12 lines\") — the accepted asymmetry, in the direction nobody reads JSON.\n */\n\nconst FUNCTION_FIELDS = ['setup', 'createContent', 'createTimeline', 'onFrame']\n\n/** Top-level structured fields worth reporting individually. */\nconst CONFIG_FIELDS = ['duration', 'camera', 'postprocessing', 'elements']\n\nexport function diffConfig(a: AnyRecord, b: AnyRecord): Op[] {\n const ops: Op[] = []\n\n const aParams = paramsByKey(a)\n const bParams = paramsByKey(b)\n const aData = isRecord(a.data) ? a.data : {}\n const bData = isRecord(b.data) ? b.data : {}\n\n // --- Knobs: spec + value changes, keyed by param key -------------------\n const removedKeys: string[] = []\n const addedKeys: string[] = []\n for (const key of aParams.keys()) if (!bParams.has(key)) removedKeys.push(key)\n for (const key of bParams.keys()) if (!aParams.has(key)) addedKeys.push(key)\n\n // Rename detection (the \"replaced knob\" trap): a removed and an added key\n // whose specs match ignoring identity/labeling are ONE knob renamed.\n const renamed = new Map<string, string>() // old → new\n for (const oldKey of [...removedKeys]) {\n const oldSpec = aParams.get(oldKey)!\n const match = addedKeys.find((newKey) =>\n specEqualIgnoringIdentity(oldSpec, bParams.get(newKey)!),\n )\n if (match) {\n renamed.set(oldKey, match)\n removedKeys.splice(removedKeys.indexOf(oldKey), 1)\n addedKeys.splice(addedKeys.indexOf(match), 1)\n }\n }\n\n for (const [oldKey, newKey] of renamed) {\n const props: Record<string, [unknown, unknown]> = {\n key: [oldKey, newKey],\n }\n const fromVal = valueOf(aData, aParams.get(oldKey)!, oldKey)\n const toVal = valueOf(bData, bParams.get(newKey)!, newKey)\n if (!deepEqual(fromVal, toVal)) props.value = [fromVal, toVal]\n ops.push({ op: 'modify', track: 'knob', id: newKey, props })\n }\n for (const key of removedKeys) {\n ops.push({ op: 'remove', track: 'knob', id: key })\n }\n for (const key of addedKeys) {\n const spec = bParams.get(key)!\n ops.push({\n op: 'add',\n track: 'knob',\n id: key,\n props: {\n kind: [undefined, spec.kind],\n value: [undefined, valueOf(bData, spec, key)],\n },\n })\n }\n for (const [key, aSpec] of aParams) {\n const bSpec = bParams.get(key)\n if (!bSpec) continue\n const props: Record<string, [unknown, unknown]> = {}\n const aVal = valueOf(aData, aSpec, key)\n const bVal = valueOf(bData, bSpec, key)\n if (!deepEqual(aVal, bVal)) props.value = [aVal, bVal]\n const specDiff = shallowDiff(aSpec, bSpec, KNOB_IDENTITY)\n if (specDiff) {\n // Default changes only matter when they aren't already the value change\n // (applyParamValue bakes default AND data together).\n if ('default' in specDiff && 'value' in props) delete specDiff.default\n Object.assign(props, specDiff)\n }\n if (Object.keys(props).length) {\n ops.push({ op: 'modify', track: 'knob', id: key, props })\n }\n }\n\n // --- Data keys not declared as knobs ------------------------------------\n const declared = new Set([...aParams.keys(), ...bParams.keys()])\n const dataKeys = [\n ...new Set([...Object.keys(aData), ...Object.keys(bData)]),\n ].sort()\n for (const key of dataKeys) {\n if (declared.has(key)) continue\n const from = aData[key]\n const to = bData[key]\n if (deepEqual(from, to)) continue\n const op: Op['op'] =\n from === undefined ? 'add' : to === undefined ? 'remove' : 'modify'\n ops.push({ op, track: 'data', id: key, props: { value: [from, to] } })\n }\n\n // --- Looks (presets), keyed by name -------------------------------------\n ops.push(\n ...diffIdTrack('look', looksAsNodes(a.presets), looksAsNodes(b.presets)),\n )\n\n // --- Function strings collapse to magnitude -----------------------------\n for (const fn of FUNCTION_FIELDS) {\n const va = a[fn]\n const vb = b[fn]\n const fa = typeof va === 'string' ? va : ''\n const fb = typeof vb === 'string' ? vb : ''\n if (fa === fb) continue\n ops.push({\n op: 'modify',\n track: 'code',\n id: fn,\n lines: lineDelta(fa, fb),\n })\n }\n\n // --- Remaining structured top-level fields ------------------------------\n for (const field of CONFIG_FIELDS) {\n if (deepEqual(a[field], b[field])) continue\n ops.push({\n op: 'modify',\n track: 'config',\n id: field,\n props: { value: [a[field], b[field]] },\n })\n }\n\n return ops\n}\n\n/** Spec fields that are identity/labeling, not behavior. */\nconst KNOB_IDENTITY = new Set(['key', 'label', 'order'])\n\ninterface ParamSpecLike extends AnyRecord {\n key: string\n kind?: unknown\n default?: unknown\n}\n\nfunction paramsByKey(config: AnyRecord): Map<string, ParamSpecLike> {\n const out = new Map<string, ParamSpecLike>()\n if (!Array.isArray(config.params)) return out\n for (const entry of config.params) {\n if (isRecord(entry) && typeof entry.key === 'string' && entry.key) {\n out.set(entry.key, entry as ParamSpecLike)\n }\n }\n return out\n}\n\n/** A knob's effective value: data wins (what playback reads), else default. */\nfunction valueOf(data: AnyRecord, spec: ParamSpecLike, key: string): unknown {\n return data[key] !== undefined ? data[key] : spec.default\n}\n\nfunction specEqualIgnoringIdentity(\n a: ParamSpecLike,\n b: ParamSpecLike,\n): boolean {\n return !shallowDiff(a, b, KNOB_IDENTITY)\n}\n\nfunction looksAsNodes(presets: unknown): AnyRecord[] {\n if (!Array.isArray(presets)) return []\n return presets\n .filter((p): p is AnyRecord => isRecord(p) && typeof p.name === 'string')\n .map((p) => ({ ...p, id: p.name }))\n}\n","import { diffConfig } from './diffConfig'\nimport { deepEqual, diffIdTrack, isRecord, shallowDiff } from './internal'\nimport type { AnyRecord } from './internal'\nimport type { Op } from './types'\n\n/**\n * Semantic diff over two ProjectDoc states (wired end-to-end once hosted\n * versions persist take docs). Every editable node has a stable id, so id-keyed\n * tracks diff exactly; singleton surfaces report as scoped modifies. Typed\n * structurally (Record-based) — the differ must not depend on studio-core.\n */\n\n/** Track name → doc field, for the id-keyed span/clip tracks. */\nconst ID_TRACKS: readonly [track: string, field: string][] = [\n ['zoom', 'zoom'],\n ['tilt', 'tilt'],\n ['camMove', 'camMotion'],\n ['speed', 'speed'],\n ['overlay', 'overlays'],\n ['object', 'objects'],\n ['audio', 'audio'],\n]\n\n/** Singleton surfaces that report as one scoped modify each. */\nconst SCOPED_FIELDS = ['frame', 'cursor', 'cam', 'export'] as const\n\nexport function diffDoc(a: AnyRecord, b: AnyRecord): Op[] {\n const ops: Op[] = []\n\n // The program anchor: its config diffs as a program does (code /\n // knob / data / look / config tracks), and its tween-timing overlay as\n // one `tween` track keyed by spec index.\n const pa = isRecord(a.program) ? a.program : undefined\n const pb = isRecord(b.program) ? b.program : undefined\n if (pa || pb) {\n ops.push(\n ...diffConfig(\n pa && isRecord(pa.config) ? pa.config : {},\n pb && isRecord(pb.config) ? pb.config : {},\n ),\n )\n const ea = pa && isRecord(pa.tweenEdits) ? pa.tweenEdits : {}\n const eb = pb && isRecord(pb.tweenEdits) ? pb.tweenEdits : {}\n for (const key of new Set([...Object.keys(ea), ...Object.keys(eb)])) {\n const va = isRecord(ea[key]) ? ea[key] : undefined\n const vb = isRecord(eb[key]) ? eb[key] : undefined\n if (va && !vb) ops.push({ op: 'remove', track: 'tween', id: `#${key}` })\n else if (!va && vb) ops.push({ op: 'add', track: 'tween', id: `#${key}` })\n else if (va && vb) {\n const props = shallowDiff(va, vb)\n if (props)\n ops.push({ op: 'modify', track: 'tween', id: `#${key}`, props })\n }\n }\n // A changed `program.duration` (the anchor's own length) reports as the\n // config's duration would; the config's own duration already did above.\n const da = programLength(pa)\n const db = programLength(pb)\n if (\n da !== db &&\n (pa?.duration !== undefined || pb?.duration !== undefined)\n ) {\n ops.push({\n op: 'modify',\n track: 'config',\n id: 'duration',\n props: { value: [da, db] },\n })\n }\n }\n\n for (const [track, field] of ID_TRACKS) {\n ops.push(...diffIdTrack(track, asNodeList(a[field]), asNodeList(b[field])))\n }\n\n // A speed edit's cost in OUTPUT seconds rides the first\n // speed op, so the history line says what the cut gained or lost\n // (\"speed sp0: rate 2→4, output 25s→22.5s\").\n const speedOps = ops.filter((o) => o.track === 'speed')\n if (speedOps.length) {\n const [oa, ob] = [outputDuration(a), outputDuration(b)]\n if (oa !== null && ob !== null && Math.abs(oa - ob) >= 0.05) {\n speedOps[0].props = { ...(speedOps[0].props ?? {}), output: [oa, ob] }\n }\n }\n\n // Segments: the trim surface. Report count + kept-duration movement, not\n // the raw arrays (payloads stay small; the compare player shows the rest).\n const aSeg = asNodeList(a.segments) ?? []\n const bSeg = asNodeList(b.segments) ?? []\n if (!deepEqual(a.segments, b.segments)) {\n const props: Record<string, [unknown, unknown]> = {}\n if (aSeg.length !== bSeg.length) props.count = [aSeg.length, bSeg.length]\n const [da, db] = [keptDuration(aSeg), keptDuration(bSeg)]\n if (da !== db) props.duration = [da, db]\n ops.push({\n op: 'modify',\n track: 'segments',\n id: 'segments',\n props: Object.keys(props).length\n ? props\n : { value: ['edited', 'edited'] },\n })\n }\n\n for (const field of SCOPED_FIELDS) {\n const va = a[field]\n const vb = b[field]\n const fa = isRecord(va) ? va : {}\n const fb = isRecord(vb) ? vb : {}\n const props = shallowDiff(fa, fb)\n if (props) ops.push({ op: 'modify', track: field, id: field, props })\n }\n\n // Camera personality: style + overrides + wand intensity as ONE surface.\n const cameraProps: Record<string, [unknown, unknown]> = {}\n for (const k of ['zoomStyle', 'tiltStyle', 'micGain'] as const) {\n if (!deepEqual(a[k], b[k])) cameraProps[k] = [a[k], b[k]]\n }\n if (!deepEqual(a.zoomParams, b.zoomParams)) {\n cameraProps.zoomParams = [a.zoomParams, b.zoomParams]\n }\n if (Object.keys(cameraProps).length) {\n ops.push({\n op: 'modify',\n track: 'camera',\n id: 'camera',\n props: cameraProps,\n })\n }\n\n return ops\n}\n\nfunction asNodeList(v: unknown): AnyRecord[] | undefined {\n if (!Array.isArray(v)) return undefined\n return v.filter(isRecord)\n}\n\n/**\n * OUTPUT duration of a doc: kept segments with speed spans applied — the\n * same arithmetic the lowering's rate map performs, kept structural so the\n * differ stays dependency-free. Null when the doc cannot say (no source\n * duration and no explicit segments).\n */\nfunction programLength(program: AnyRecord | undefined): number | null {\n if (!program) return null\n if (typeof program.duration === 'number') return program.duration\n const cfg = isRecord(program.config) ? program.config : undefined\n return cfg && typeof cfg.duration === 'number' ? cfg.duration : null\n}\n\nfunction outputDuration(doc: AnyRecord): number | null {\n // A program document is one source span, its own length: its speed\n // spans rate it the way a recording's rate the footage.\n const source = isRecord(doc.source) ? doc.source : undefined\n const meta = source && isRecord(source.meta) ? source.meta : undefined\n const durS = isRecord(doc.program)\n ? programLength(doc.program)\n : meta && typeof meta.durationMs === 'number'\n ? meta.durationMs / 1000\n : null\n const explicit = (asNodeList(doc.segments) ?? [])\n .map((s) => ({\n in: typeof s.in === 'number' ? s.in : 0,\n out: typeof s.out === 'number' ? s.out : 0,\n }))\n .filter((s) => s.out > s.in)\n const segments = explicit.length\n ? explicit\n : durS !== null\n ? [{ in: 0, out: durS }]\n : null\n if (!segments) return null\n const spans = (asNodeList(doc.speed) ?? [])\n .map((s) => ({\n in: typeof s.in === 'number' ? s.in : 0,\n out: typeof s.out === 'number' ? s.out : 0,\n rate: typeof s.rate === 'number' && s.rate > 0 ? s.rate : 1,\n }))\n .filter((s) => s.out > s.in)\n let total = 0\n for (const seg of segments) {\n total += seg.out - seg.in\n for (const sp of spans) {\n const ov = Math.min(seg.out, sp.out) - Math.max(seg.in, sp.in)\n if (ov > 0) total -= ov - ov / sp.rate\n }\n }\n return Math.round(total * 1000) / 1000\n}\n\nfunction keptDuration(segments: AnyRecord[]): number {\n let total = 0\n for (const s of segments) {\n const start = typeof s.in === 'number' ? s.in : 0\n const end = typeof s.out === 'number' ? s.out : start\n total += Math.max(0, end - start)\n }\n return Math.round(total * 1000) / 1000\n}\n","/**\n * The semantic differ: pure functions over two\n * immutable version states — no I/O, no recorded gesture log (\"derive,\n * don't record\"). Runs identically in Workers, the CLI, and the studio;\n * same two inputs ⇒ byte-identical ops and summary.\n */\n\nexport interface Op {\n op: 'add' | 'remove' | 'modify'\n /**\n * Doc tracks: zoom | tilt | speed | overlay | object | audio, plus scoped\n * singletons (segments | frame | cursor | cam | export | camera).\n * Config tracks: knob (param key space) | look | data | code | config.\n */\n track: string\n /** Span/clip id, param key, function name, or field name. */\n id: string\n /** Changed properties: key → [from, to]. Absent on code ops. */\n props?: Record<string, [unknown, unknown]>\n /** Code-function modifies only: magnitude of the line-level change. */\n lines?: number\n /**\n * True when the change is a cascaded consequence of another gesture\n * (e.g. a trim shifting downstream spans), not a direct edit.\n */\n cascade?: boolean\n}\n\n/** One intervening version's worth of changes, as the pull contract ships it. */\nexport interface VersionChanges {\n versionId: string\n versionNumber: number\n origin: string\n label: string | null\n note: string | null\n ops: Op[]\n summary: string\n}\n\n/** Stable composite key for net-effect folding. */\nexport function opKey(op: Op): string {\n return `${op.track}\u0000${op.id}`\n}\n","import { opKey } from './types'\nimport type { Op } from './types'\n\n/**\n * Net effect per node across a sequence of op sets (the coalescing rules):\n * initial→final only, no intermediate drags. add→modify folds into the add;\n * add→remove vanishes; modify chains keep the first `from` and last `to`\n * (dropping props that net to no change); remove→add of the same id is one\n * modify (the node was replaced in place). Output is deterministically\n * ordered: track declaration order, then output-time anchor, then id.\n */\nexport function coalesce(opsInOrder: readonly Op[]): Op[] {\n const byNode = new Map<string, Op>()\n\n for (const next of opsInOrder) {\n const key = opKey(next)\n const prev = byNode.get(key)\n if (!prev) {\n byNode.set(key, cloneOp(next))\n continue\n }\n const folded = foldPair(prev, next)\n if (folded) byNode.set(key, folded)\n else byNode.delete(key)\n }\n\n return [...byNode.values()].sort(compareOps)\n}\n\nfunction foldPair(prev: Op, next: Op): Op | null {\n // add then remove: the node never existed for the reader.\n if (prev.op === 'add' && next.op === 'remove') return null\n // add then modify: still an add, with the final properties.\n if (prev.op === 'add' && next.op === 'modify') {\n return { ...prev, props: mergeProps(prev.props, next.props) }\n }\n // modify then remove: the removal is the story.\n if (next.op === 'remove') return { ...next }\n // remove then add: replaced in place — one modify.\n if (prev.op === 'remove' && next.op === 'add') {\n return { ...next, op: 'modify', props: mergeProps(prev.props, next.props) }\n }\n // modify then modify: first from, last to; drop props that net out.\n if (prev.op === 'modify' && next.op === 'modify') {\n const merged = mergeProps(prev.props, next.props, true)\n const lines =\n prev.lines !== undefined || next.lines !== undefined\n ? (prev.lines ?? 0) + (next.lines ?? 0)\n : undefined\n if (!merged && lines === undefined) return null\n return { ...prev, props: merged, lines }\n }\n // Remaining combinations (add→add, remove→remove) shouldn't occur between\n // well-formed sets; keep the latest as the honest answer.\n return { ...next }\n}\n\nfunction mergeProps(\n a: Op['props'],\n b: Op['props'],\n dropNoNet = false,\n): Op['props'] {\n if (!a) return b ? { ...b } : undefined\n if (!b) return { ...a }\n const out: NonNullable<Op['props']> = { ...a }\n for (const [k, [from, to]] of Object.entries(b)) {\n out[k] = k in out ? [out[k][0], to] : [from, to]\n }\n if (dropNoNet) {\n for (const [k, [from, to]] of Object.entries(out)) {\n if (JSON.stringify(from) === JSON.stringify(to)) delete out[k]\n }\n }\n return Object.keys(out).length ? out : undefined\n}\n\nfunction cloneOp(op: Op): Op {\n return { ...op, props: op.props ? { ...op.props } : undefined }\n}\n\n/** Track presentation order: content tracks in output order, then meta. */\nconst TRACK_ORDER = [\n 'segments',\n 'frame',\n 'camera',\n 'cursor',\n 'cam',\n 'zoom',\n 'tilt',\n 'speed',\n 'overlay',\n 'object',\n 'audio',\n 'export',\n 'knob',\n 'look',\n 'data',\n 'code',\n 'config',\n]\n\nfunction compareOps(a: Op, b: Op): number {\n const ta = TRACK_ORDER.indexOf(a.track)\n const tb = TRACK_ORDER.indexOf(b.track)\n if (ta !== tb) return (ta === -1 ? 99 : ta) - (tb === -1 ? 99 : tb)\n const anchorA = timeAnchor(a)\n const anchorB = timeAnchor(b)\n if (anchorA !== anchorB) return anchorA - anchorB\n return a.id < b.id ? -1 : a.id > b.id ? 1 : 0\n}\n\nfunction timeAnchor(op: Op): number {\n for (const k of ['in', 'at', 'start']) {\n const pair = op.props?.[k]\n if (pair) {\n const v = pair[1] ?? pair[0]\n if (typeof v === 'number') return v\n }\n }\n return Number.MAX_SAFE_INTEGER\n}\n","import type { Op } from './types'\n\n/**\n * Deterministic prose over an op list — the line that lands in the agent's\n * context (\"zoom z3: end 4.2s→4.6s, level 2.2→1.8; tilt t1 removed\").\n * Same ops ⇒ byte-identical string; unit-pinned like the geometry mirrors.\n */\nexport function summarize(\n ops: readonly Op[],\n options: { maxItems?: number } = {},\n): string {\n const max = options.maxItems ?? 12\n if (ops.length === 0) return 'no changes'\n const shown = ops.slice(0, max)\n const parts = shown.map(phrase)\n const hidden = ops.length - shown.length\n if (hidden > 0)\n parts.push(`and ${hidden} smaller change${hidden === 1 ? '' : 's'}`)\n return parts.join('; ')\n}\n\nfunction phrase(op: Op): string {\n if (op.track === 'code') {\n return `code ${op.id} changed (${op.lines ?? 0} line${op.lines === 1 ? '' : 's'})`\n }\n if (op.track === 'knob') return knobPhrase(op)\n if (op.track === 'config') {\n if (op.id === 'duration' && op.props?.value) {\n const [from, to] = op.props.value\n return `duration ${fmt(from, true)}→${fmt(to, true)}`\n }\n return `${op.id} changed`\n }\n if (op.track === 'data') {\n const pair = op.props?.value\n if (op.op === 'add') return `data ${op.id} set to ${fmt(pair?.[1])}`\n if (op.op === 'remove') return `data ${op.id} removed`\n return `data ${op.id}: ${fmt(pair?.[0])}→${fmt(pair?.[1])}`\n }\n if (op.track === 'look') {\n if (op.op === 'add') return `look \"${op.id}\" added`\n if (op.op === 'remove') return `look \"${op.id}\" removed`\n return `look \"${op.id}\" changed`\n }\n if (op.track === 'segments') {\n const d = op.props?.duration\n if (d) return `trim: kept footage ${fmt(d[0], true)}→${fmt(d[1], true)}`\n return 'segments edited'\n }\n if (SCOPED_TRACKS.has(op.track)) {\n return `${op.track} ${propList(op.props)}`\n }\n\n // Id-keyed span/clip tracks.\n const name = `${op.track} ${op.id}`\n // The output-duration pair a speed edit carries (what the cut gained\n // or lost in screen seconds) rides add/remove phrases too — a new 2× span\n // shortens the video without modifying anything.\n const output = op.props?.output\n const outputNote = output\n ? `output ${fmt(output[0], true)}→${fmt(output[1], true)}`\n : null\n if (op.op === 'add') {\n const range = rangeOf(op)\n const bits = [range, outputNote].filter(Boolean).join(', ')\n return bits ? `${name} added (${bits})` : `${name} added`\n }\n if (op.op === 'remove')\n return outputNote ? `${name} removed (${outputNote})` : `${name} removed`\n return `${name}: ${propList(op.props)}`\n}\n\nfunction knobPhrase(op: Op): string {\n const rename = op.props?.key\n if (rename) return `replaced knob ${fmt(rename[0])} with ${fmt(rename[1])}`\n if (op.op === 'add') {\n const kind = (op.props?.kind ?? [])[1]\n return typeof kind === 'string'\n ? `knob ${op.id} added (${kind})`\n : `knob ${op.id} added`\n }\n if (op.op === 'remove') return `knob ${op.id} removed`\n const value = op.props?.value\n const parts: string[] = []\n if (value) parts.push(`${fmt(value[0])}→${fmt(value[1])}`)\n for (const [k, [from, to]] of Object.entries(op.props ?? {})) {\n if (k === 'value') continue\n parts.push(`${k} ${fmt(from)}→${fmt(to)}`)\n }\n return `knob ${op.id}: ${parts.join(', ')}`\n}\n\nconst SCOPED_TRACKS = new Set(['frame', 'cursor', 'cam', 'export', 'camera'])\n\n/** Humanized property names for the take vocabulary. */\nconst PROP_LABELS: Record<string, string> = {\n in: 'start',\n out: 'end',\n cx: 'focus x',\n cy: 'focus y',\n}\n\n/** Properties whose numbers are seconds. */\nconst TIME_PROPS = new Set([\n 'in',\n 'out',\n 'at',\n 'start',\n 'end',\n 'duration',\n 'output',\n])\n\nfunction propList(props: Op['props']): string {\n if (!props) return 'changed'\n return Object.entries(props)\n .map(([k, [from, to]]) => {\n const time = TIME_PROPS.has(k)\n return `${PROP_LABELS[k] ?? k} ${fmt(from, time)}→${fmt(to, time)}`\n })\n .join(', ')\n}\n\nfunction rangeOf(op: Op): string | null {\n const startPair = anchorPair(op, ['in', 'at', 'start'])\n const start = startPair ? startPair[1] : undefined\n if (typeof start !== 'number') return null\n const endPair = anchorPair(op, ['out'])\n const end = endPair ? endPair[1] : undefined\n return typeof end === 'number'\n ? `${fmt(start, true)}–${fmt(end, true)}`\n : `at ${fmt(start, true)}`\n}\n\nfunction anchorPair(\n op: Op,\n keys: readonly string[],\n): [unknown, unknown] | undefined {\n for (const k of keys) {\n const pair = op.props?.[k]\n if (pair) return pair\n }\n return undefined\n}\n\n/**\n * Long strings (content knobs carry whole headlines) collapse to\n * one line and truncate — the summary is a changelog sentence, and it also\n * rides 409 stale_base bodies, so a multiline value must never spill raw.\n */\nconst FMT_STRING_MAX = 40\n\nfunction fmt(v: unknown, time = false): string {\n if (v === undefined) return '∅'\n if (v === null) return 'null'\n if (typeof v === 'number') {\n const rounded = Math.round(v * 100) / 100\n return time ? `${rounded}s` : String(rounded)\n }\n if (typeof v === 'boolean') return v ? 'on' : 'off'\n if (typeof v === 'string') {\n if (v === '') return '\"\"'\n const flat = v.replace(/\\s*\\n\\s*/g, ' ')\n const clipped =\n flat.length > FMT_STRING_MAX\n ? `${flat.slice(0, FMT_STRING_MAX - 1)}…`\n : flat\n return clipped === v ? quoteIfNeeded(clipped) : `\"${clipped}\"`\n }\n return 'changed'\n}\n\nfunction quoteIfNeeded(s: string): string {\n return /^[\\w.#-]+$/.test(s) ? s : `\"${s}\"`\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/diff/internal.ts","../../src/diff/diffConfig.ts","../../src/diff/diffDoc.ts","../../src/diff/types.ts","../../src/diff/coalesce.ts","../../src/diff/summarize.ts"],"sourcesContent":["import type { Op } from './types'\n\nexport type AnyRecord = Record<string, unknown>\n\nexport function isRecord(v: unknown): v is AnyRecord {\n return !!v && typeof v === 'object' && !Array.isArray(v)\n}\n\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true\n if (typeof a !== typeof b) return false\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]))\n }\n if (isRecord(a) && isRecord(b)) {\n const ka = Object.keys(a)\n const kb = Object.keys(b)\n if (ka.length !== kb.length) return false\n return ka.every((k) => deepEqual(a[k], b[k]))\n }\n return false\n}\n\n/**\n * Shallow property diff of two node objects: changed keys → [from, to].\n * Nested values compare deep but report as whole-value pairs — span/clip\n * props are overwhelmingly scalars, and a whole-value pair stays honest for\n * the rest. `ignore` keys (identity fields) never report.\n */\nexport function shallowDiff(\n a: AnyRecord,\n b: AnyRecord,\n ignore: ReadonlySet<string> = new Set(),\n): Record<string, [unknown, unknown]> | undefined {\n const props: Record<string, [unknown, unknown]> = {}\n const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort()\n for (const k of keys) {\n if (ignore.has(k)) continue\n if (!deepEqual(a[k], b[k])) props[k] = [a[k], b[k]]\n }\n return Object.keys(props).length ? props : undefined\n}\n\n/**\n * Diff two id-keyed node lists into add/remove/modify ops. Identity is the\n * id — a moved span with the same id is a modify (the trap case); a\n * remove+add pair is two different nodes by construction.\n */\nexport function diffIdTrack(\n track: string,\n a: readonly AnyRecord[] | undefined,\n b: readonly AnyRecord[] | undefined,\n): Op[] {\n const ops: Op[] = []\n const aById = new Map((a ?? []).map((n) => [String(n.id), n]))\n const bById = new Map((b ?? []).map((n) => [String(n.id), n]))\n for (const [id, node] of aById) {\n if (!bById.has(id)) {\n ops.push({ op: 'remove', track, id, props: pickAnchor(node) })\n }\n }\n for (const [id, node] of bById) {\n const prev = aById.get(id)\n if (!prev) {\n ops.push({ op: 'add', track, id, props: pickAnchor(node) })\n continue\n }\n const props = shallowDiff(prev, node, ID_KEYS)\n if (props) ops.push({ op: 'modify', track, id, props })\n }\n return ops\n}\n\nconst ID_KEYS = new Set(['id'])\n\n/**\n * Anchor props carried on add/remove ops so the summary can place the node\n * in time (\"zoom z5 added (0.8s–2.4s)\") without shipping the whole node.\n */\nfunction pickAnchor(\n node: AnyRecord,\n): Record<string, [unknown, unknown]> | undefined {\n const out: Record<string, [unknown, unknown]> = {}\n for (const k of ['in', 'out', 'at', 'start', 'kind', 'key']) {\n if (node[k] !== undefined) out[k] = [undefined, node[k]]\n }\n return Object.keys(out).length ? out : undefined\n}\n\n/** Magnitude of a function-string change: max(lines added, lines removed). */\nexport function lineDelta(a: string, b: string): number {\n const count = (s: string): Map<string, number> => {\n const m = new Map<string, number>()\n for (const line of s.split('\\n')) m.set(line, (m.get(line) ?? 0) + 1)\n return m\n }\n const ca = count(a)\n const cb = count(b)\n let removed = 0\n let added = 0\n for (const [line, n] of ca) removed += Math.max(0, n - (cb.get(line) ?? 0))\n for (const [line, n] of cb) added += Math.max(0, n - (ca.get(line) ?? 0))\n return Math.max(added, removed)\n}\n","import {\n deepEqual,\n diffIdTrack,\n isRecord,\n lineDelta,\n shallowDiff,\n} from './internal'\nimport type { AnyRecord } from './internal'\nimport type { Op } from './types'\n\n/**\n * Semantic diff over two VosConfigJson states: param keys are\n * program mode's stable ids, so knob values and specs diff fully typed;\n * function-string bodies collapse to a magnitude (\"createContent changed,\n * 12 lines\") — the accepted asymmetry, in the direction nobody reads JSON.\n */\n\nconst FUNCTION_FIELDS = ['setup', 'createContent', 'createTimeline', 'onFrame']\n\n/** Top-level structured fields worth reporting individually. */\nconst CONFIG_FIELDS = ['duration', 'camera', 'postprocessing', 'elements']\n\nexport function diffConfig(a: AnyRecord, b: AnyRecord): Op[] {\n const ops: Op[] = []\n\n const aParams = paramsByKey(a)\n const bParams = paramsByKey(b)\n const aData = isRecord(a.data) ? a.data : {}\n const bData = isRecord(b.data) ? b.data : {}\n\n // --- Knobs: spec + value changes, keyed by param key -------------------\n const removedKeys: string[] = []\n const addedKeys: string[] = []\n for (const key of aParams.keys()) if (!bParams.has(key)) removedKeys.push(key)\n for (const key of bParams.keys()) if (!aParams.has(key)) addedKeys.push(key)\n\n // Rename detection (the \"replaced knob\" trap): a removed and an added key\n // whose specs match ignoring identity/labeling are ONE knob renamed.\n const renamed = new Map<string, string>() // old → new\n for (const oldKey of [...removedKeys]) {\n const oldSpec = aParams.get(oldKey)!\n const match = addedKeys.find((newKey) =>\n specEqualIgnoringIdentity(oldSpec, bParams.get(newKey)!),\n )\n if (match) {\n renamed.set(oldKey, match)\n removedKeys.splice(removedKeys.indexOf(oldKey), 1)\n addedKeys.splice(addedKeys.indexOf(match), 1)\n }\n }\n\n for (const [oldKey, newKey] of renamed) {\n const props: Record<string, [unknown, unknown]> = {\n key: [oldKey, newKey],\n }\n const fromVal = valueOf(aData, aParams.get(oldKey)!, oldKey)\n const toVal = valueOf(bData, bParams.get(newKey)!, newKey)\n if (!deepEqual(fromVal, toVal)) props.value = [fromVal, toVal]\n ops.push({ op: 'modify', track: 'knob', id: newKey, props })\n }\n for (const key of removedKeys) {\n ops.push({ op: 'remove', track: 'knob', id: key })\n }\n for (const key of addedKeys) {\n const spec = bParams.get(key)!\n ops.push({\n op: 'add',\n track: 'knob',\n id: key,\n props: {\n kind: [undefined, spec.kind],\n value: [undefined, valueOf(bData, spec, key)],\n },\n })\n }\n for (const [key, aSpec] of aParams) {\n const bSpec = bParams.get(key)\n if (!bSpec) continue\n const props: Record<string, [unknown, unknown]> = {}\n const aVal = valueOf(aData, aSpec, key)\n const bVal = valueOf(bData, bSpec, key)\n if (!deepEqual(aVal, bVal)) props.value = [aVal, bVal]\n const specDiff = shallowDiff(aSpec, bSpec, KNOB_IDENTITY)\n if (specDiff) {\n // Default changes only matter when they aren't already the value change\n // (applyParamValue bakes default AND data together).\n if ('default' in specDiff && 'value' in props) delete specDiff.default\n Object.assign(props, specDiff)\n }\n if (Object.keys(props).length) {\n ops.push({ op: 'modify', track: 'knob', id: key, props })\n }\n }\n\n // --- Data keys not declared as knobs ------------------------------------\n const declared = new Set([...aParams.keys(), ...bParams.keys()])\n const dataKeys = [\n ...new Set([...Object.keys(aData), ...Object.keys(bData)]),\n ].sort()\n for (const key of dataKeys) {\n if (declared.has(key)) continue\n const from = aData[key]\n const to = bData[key]\n if (deepEqual(from, to)) continue\n const op: Op['op'] =\n from === undefined ? 'add' : to === undefined ? 'remove' : 'modify'\n ops.push({ op, track: 'data', id: key, props: { value: [from, to] } })\n }\n\n // --- Looks (presets), keyed by name -------------------------------------\n ops.push(\n ...diffIdTrack('look', looksAsNodes(a.presets), looksAsNodes(b.presets)),\n )\n\n // --- Function strings collapse to magnitude -----------------------------\n for (const fn of FUNCTION_FIELDS) {\n const va = a[fn]\n const vb = b[fn]\n const fa = typeof va === 'string' ? va : ''\n const fb = typeof vb === 'string' ? vb : ''\n if (fa === fb) continue\n ops.push({\n op: 'modify',\n track: 'code',\n id: fn,\n lines: lineDelta(fa, fb),\n })\n }\n\n // --- Remaining structured top-level fields ------------------------------\n for (const field of CONFIG_FIELDS) {\n if (deepEqual(a[field], b[field])) continue\n ops.push({\n op: 'modify',\n track: 'config',\n id: field,\n props: { value: [a[field], b[field]] },\n })\n }\n\n return ops\n}\n\n/** Spec fields that are identity/labeling, not behavior. */\nconst KNOB_IDENTITY = new Set(['key', 'label', 'order'])\n\ninterface ParamSpecLike extends AnyRecord {\n key: string\n kind?: unknown\n default?: unknown\n}\n\nfunction paramsByKey(config: AnyRecord): Map<string, ParamSpecLike> {\n const out = new Map<string, ParamSpecLike>()\n if (!Array.isArray(config.params)) return out\n for (const entry of config.params) {\n if (isRecord(entry) && typeof entry.key === 'string' && entry.key) {\n out.set(entry.key, entry as ParamSpecLike)\n }\n }\n return out\n}\n\n/** A knob's effective value: data wins (what playback reads), else default. */\nfunction valueOf(data: AnyRecord, spec: ParamSpecLike, key: string): unknown {\n return data[key] !== undefined ? data[key] : spec.default\n}\n\nfunction specEqualIgnoringIdentity(\n a: ParamSpecLike,\n b: ParamSpecLike,\n): boolean {\n return !shallowDiff(a, b, KNOB_IDENTITY)\n}\n\nfunction looksAsNodes(presets: unknown): AnyRecord[] {\n if (!Array.isArray(presets)) return []\n return presets\n .filter((p): p is AnyRecord => isRecord(p) && typeof p.name === 'string')\n .map((p) => ({ ...p, id: p.name }))\n}\n","import { diffConfig } from './diffConfig'\nimport { deepEqual, diffIdTrack, isRecord, shallowDiff } from './internal'\nimport type { AnyRecord } from './internal'\nimport type { Op } from './types'\n\n/**\n * Semantic diff over two ProjectDoc states (wired end-to-end once hosted\n * versions persist take docs). Every editable node has a stable id, so id-keyed\n * tracks diff exactly; singleton surfaces report as scoped modifies. Typed\n * structurally (Record-based) — the differ must not depend on studio-core.\n */\n\n/** Track name → doc field, for the id-keyed span/clip tracks. */\nconst ID_TRACKS: readonly [track: string, field: string][] = [\n ['zoom', 'zoom'],\n ['tilt', 'tilt'],\n ['camMove', 'camMotion'],\n ['speed', 'speed'],\n ['rejected', 'rejected'],\n ['overlay', 'overlays'],\n ['object', 'objects'],\n ['audio', 'audio'],\n]\n\n/** Singleton surfaces that report as one scoped modify each. */\nconst SCOPED_FIELDS = ['frame', 'cursor', 'cam', 'export'] as const\n\nexport function diffDoc(a: AnyRecord, b: AnyRecord): Op[] {\n const ops: Op[] = []\n\n // The program anchor: its config diffs as a program does (code /\n // knob / data / look / config tracks), and its tween-timing overlay as\n // one `tween` track keyed by spec index.\n const pa = isRecord(a.program) ? a.program : undefined\n const pb = isRecord(b.program) ? b.program : undefined\n if (pa || pb) {\n ops.push(\n ...diffConfig(\n pa && isRecord(pa.config) ? pa.config : {},\n pb && isRecord(pb.config) ? pb.config : {},\n ),\n )\n const ea = pa && isRecord(pa.tweenEdits) ? pa.tweenEdits : {}\n const eb = pb && isRecord(pb.tweenEdits) ? pb.tweenEdits : {}\n for (const key of new Set([...Object.keys(ea), ...Object.keys(eb)])) {\n const va = isRecord(ea[key]) ? ea[key] : undefined\n const vb = isRecord(eb[key]) ? eb[key] : undefined\n if (va && !vb) ops.push({ op: 'remove', track: 'tween', id: `#${key}` })\n else if (!va && vb) ops.push({ op: 'add', track: 'tween', id: `#${key}` })\n else if (va && vb) {\n const props = shallowDiff(va, vb)\n if (props)\n ops.push({ op: 'modify', track: 'tween', id: `#${key}`, props })\n }\n }\n // A changed `program.duration` (the anchor's own length) reports as the\n // config's duration would; the config's own duration already did above.\n const da = programLength(pa)\n const db = programLength(pb)\n if (\n da !== db &&\n (pa?.duration !== undefined || pb?.duration !== undefined)\n ) {\n ops.push({\n op: 'modify',\n track: 'config',\n id: 'duration',\n props: { value: [da, db] },\n })\n }\n }\n\n for (const [track, field] of ID_TRACKS) {\n ops.push(...diffIdTrack(track, asNodeList(a[field]), asNodeList(b[field])))\n }\n\n // A speed edit's cost in OUTPUT seconds rides the first\n // speed op, so the history line says what the cut gained or lost\n // (\"speed sp0: rate 2→4, output 25s→22.5s\").\n const speedOps = ops.filter((o) => o.track === 'speed')\n if (speedOps.length) {\n const [oa, ob] = [outputDuration(a), outputDuration(b)]\n if (oa !== null && ob !== null && Math.abs(oa - ob) >= 0.05) {\n speedOps[0].props = { ...(speedOps[0].props ?? {}), output: [oa, ob] }\n }\n }\n\n // Segments: the trim surface. Report count + kept-duration movement, not\n // the raw arrays (payloads stay small; the compare player shows the rest).\n const aSeg = asNodeList(a.segments) ?? []\n const bSeg = asNodeList(b.segments) ?? []\n if (!deepEqual(a.segments, b.segments)) {\n const props: Record<string, [unknown, unknown]> = {}\n if (aSeg.length !== bSeg.length) props.count = [aSeg.length, bSeg.length]\n const [da, db] = [keptDuration(aSeg), keptDuration(bSeg)]\n if (da !== db) props.duration = [da, db]\n ops.push({\n op: 'modify',\n track: 'segments',\n id: 'segments',\n props: Object.keys(props).length\n ? props\n : { value: ['edited', 'edited'] },\n })\n }\n\n for (const field of SCOPED_FIELDS) {\n const va = a[field]\n const vb = b[field]\n const fa = isRecord(va) ? va : {}\n const fb = isRecord(vb) ? vb : {}\n const props = shallowDiff(fa, fb)\n if (props) ops.push({ op: 'modify', track: field, id: field, props })\n }\n\n // Camera personality: style + overrides + wand intensity as ONE surface.\n const cameraProps: Record<string, [unknown, unknown]> = {}\n for (const k of ['zoomStyle', 'tiltStyle', 'micGain'] as const) {\n if (!deepEqual(a[k], b[k])) cameraProps[k] = [a[k], b[k]]\n }\n if (!deepEqual(a.zoomParams, b.zoomParams)) {\n cameraProps.zoomParams = [a.zoomParams, b.zoomParams]\n }\n if (Object.keys(cameraProps).length) {\n ops.push({\n op: 'modify',\n track: 'camera',\n id: 'camera',\n props: cameraProps,\n })\n }\n\n return ops\n}\n\nfunction asNodeList(v: unknown): AnyRecord[] | undefined {\n if (!Array.isArray(v)) return undefined\n return v.filter(isRecord)\n}\n\n/**\n * OUTPUT duration of a doc: kept segments with speed spans applied — the\n * same arithmetic the lowering's rate map performs, kept structural so the\n * differ stays dependency-free. Null when the doc cannot say (no source\n * duration and no explicit segments).\n */\nfunction programLength(program: AnyRecord | undefined): number | null {\n if (!program) return null\n if (typeof program.duration === 'number') return program.duration\n const cfg = isRecord(program.config) ? program.config : undefined\n return cfg && typeof cfg.duration === 'number' ? cfg.duration : null\n}\n\nfunction outputDuration(doc: AnyRecord): number | null {\n // A program document is one source span, its own length: its speed\n // spans rate it the way a recording's rate the footage.\n const source = isRecord(doc.source) ? doc.source : undefined\n const meta = source && isRecord(source.meta) ? source.meta : undefined\n const durS = isRecord(doc.program)\n ? programLength(doc.program)\n : meta && typeof meta.durationMs === 'number'\n ? meta.durationMs / 1000\n : null\n const explicit = (asNodeList(doc.segments) ?? [])\n .map((s) => ({\n in: typeof s.in === 'number' ? s.in : 0,\n out: typeof s.out === 'number' ? s.out : 0,\n }))\n .filter((s) => s.out > s.in)\n const segments = explicit.length\n ? explicit\n : durS !== null\n ? [{ in: 0, out: durS }]\n : null\n if (!segments) return null\n const spans = (asNodeList(doc.speed) ?? [])\n .map((s) => ({\n in: typeof s.in === 'number' ? s.in : 0,\n out: typeof s.out === 'number' ? s.out : 0,\n rate: typeof s.rate === 'number' && s.rate > 0 ? s.rate : 1,\n }))\n .filter((s) => s.out > s.in)\n let total = 0\n for (const seg of segments) {\n total += seg.out - seg.in\n for (const sp of spans) {\n const ov = Math.min(seg.out, sp.out) - Math.max(seg.in, sp.in)\n if (ov > 0) total -= ov - ov / sp.rate\n }\n }\n return Math.round(total * 1000) / 1000\n}\n\nfunction keptDuration(segments: AnyRecord[]): number {\n let total = 0\n for (const s of segments) {\n const start = typeof s.in === 'number' ? s.in : 0\n const end = typeof s.out === 'number' ? s.out : start\n total += Math.max(0, end - start)\n }\n return Math.round(total * 1000) / 1000\n}\n","/**\n * The semantic differ: pure functions over two\n * immutable version states — no I/O, no recorded gesture log (\"derive,\n * don't record\"). Runs identically in Workers, the CLI, and the studio;\n * same two inputs ⇒ byte-identical ops and summary.\n */\n\nexport interface Op {\n op: 'add' | 'remove' | 'modify'\n /**\n * Doc tracks: zoom | tilt | speed | overlay | object | audio, plus scoped\n * singletons (segments | frame | cursor | cam | export | camera).\n * Config tracks: knob (param key space) | look | data | code | config.\n */\n track: string\n /** Span/clip id, param key, function name, or field name. */\n id: string\n /** Changed properties: key → [from, to]. Absent on code ops. */\n props?: Record<string, [unknown, unknown]>\n /** Code-function modifies only: magnitude of the line-level change. */\n lines?: number\n /**\n * True when the change is a cascaded consequence of another gesture\n * (e.g. a trim shifting downstream spans), not a direct edit.\n */\n cascade?: boolean\n}\n\n/** One intervening version's worth of changes, as the pull contract ships it. */\nexport interface VersionChanges {\n versionId: string\n versionNumber: number\n origin: string\n label: string | null\n note: string | null\n ops: Op[]\n summary: string\n}\n\n/** Stable composite key for net-effect folding. */\nexport function opKey(op: Op): string {\n return `${op.track}\u0000${op.id}`\n}\n","import { opKey } from './types'\nimport type { Op } from './types'\n\n/**\n * Net effect per node across a sequence of op sets (the coalescing rules):\n * initial→final only, no intermediate drags. add→modify folds into the add;\n * add→remove vanishes; modify chains keep the first `from` and last `to`\n * (dropping props that net to no change); remove→add of the same id is one\n * modify (the node was replaced in place). Output is deterministically\n * ordered: track declaration order, then output-time anchor, then id.\n */\nexport function coalesce(opsInOrder: readonly Op[]): Op[] {\n const byNode = new Map<string, Op>()\n\n for (const next of opsInOrder) {\n const key = opKey(next)\n const prev = byNode.get(key)\n if (!prev) {\n byNode.set(key, cloneOp(next))\n continue\n }\n const folded = foldPair(prev, next)\n if (folded) byNode.set(key, folded)\n else byNode.delete(key)\n }\n\n return [...byNode.values()].sort(compareOps)\n}\n\nfunction foldPair(prev: Op, next: Op): Op | null {\n // add then remove: the node never existed for the reader.\n if (prev.op === 'add' && next.op === 'remove') return null\n // add then modify: still an add, with the final properties.\n if (prev.op === 'add' && next.op === 'modify') {\n return { ...prev, props: mergeProps(prev.props, next.props) }\n }\n // modify then remove: the removal is the story.\n if (next.op === 'remove') return { ...next }\n // remove then add: replaced in place — one modify.\n if (prev.op === 'remove' && next.op === 'add') {\n return { ...next, op: 'modify', props: mergeProps(prev.props, next.props) }\n }\n // modify then modify: first from, last to; drop props that net out.\n if (prev.op === 'modify' && next.op === 'modify') {\n const merged = mergeProps(prev.props, next.props, true)\n const lines =\n prev.lines !== undefined || next.lines !== undefined\n ? (prev.lines ?? 0) + (next.lines ?? 0)\n : undefined\n if (!merged && lines === undefined) return null\n return { ...prev, props: merged, lines }\n }\n // Remaining combinations (add→add, remove→remove) shouldn't occur between\n // well-formed sets; keep the latest as the honest answer.\n return { ...next }\n}\n\nfunction mergeProps(\n a: Op['props'],\n b: Op['props'],\n dropNoNet = false,\n): Op['props'] {\n if (!a) return b ? { ...b } : undefined\n if (!b) return { ...a }\n const out: NonNullable<Op['props']> = { ...a }\n for (const [k, [from, to]] of Object.entries(b)) {\n out[k] = k in out ? [out[k][0], to] : [from, to]\n }\n if (dropNoNet) {\n for (const [k, [from, to]] of Object.entries(out)) {\n if (JSON.stringify(from) === JSON.stringify(to)) delete out[k]\n }\n }\n return Object.keys(out).length ? out : undefined\n}\n\nfunction cloneOp(op: Op): Op {\n return { ...op, props: op.props ? { ...op.props } : undefined }\n}\n\n/** Track presentation order: content tracks in output order, then meta. */\nconst TRACK_ORDER = [\n 'segments',\n 'frame',\n 'camera',\n 'cursor',\n 'cam',\n 'zoom',\n 'tilt',\n 'speed',\n 'overlay',\n 'object',\n 'audio',\n 'export',\n 'knob',\n 'look',\n 'data',\n 'code',\n 'config',\n]\n\nfunction compareOps(a: Op, b: Op): number {\n const ta = TRACK_ORDER.indexOf(a.track)\n const tb = TRACK_ORDER.indexOf(b.track)\n if (ta !== tb) return (ta === -1 ? 99 : ta) - (tb === -1 ? 99 : tb)\n const anchorA = timeAnchor(a)\n const anchorB = timeAnchor(b)\n if (anchorA !== anchorB) return anchorA - anchorB\n return a.id < b.id ? -1 : a.id > b.id ? 1 : 0\n}\n\nfunction timeAnchor(op: Op): number {\n for (const k of ['in', 'at', 'start']) {\n const pair = op.props?.[k]\n if (pair) {\n const v = pair[1] ?? pair[0]\n if (typeof v === 'number') return v\n }\n }\n return Number.MAX_SAFE_INTEGER\n}\n","import type { Op } from './types'\n\n/**\n * Deterministic prose over an op list — the line that lands in the agent's\n * context (\"zoom z3: end 4.2s→4.6s, level 2.2→1.8; tilt t1 removed\").\n * Same ops ⇒ byte-identical string; unit-pinned like the geometry mirrors.\n */\nexport function summarize(\n ops: readonly Op[],\n options: { maxItems?: number } = {},\n): string {\n const max = options.maxItems ?? 12\n if (ops.length === 0) return 'no changes'\n const shown = ops.slice(0, max)\n const parts = shown.map(phrase)\n const hidden = ops.length - shown.length\n if (hidden > 0)\n parts.push(`and ${hidden} smaller change${hidden === 1 ? '' : 's'}`)\n return parts.join('; ')\n}\n\nfunction phrase(op: Op): string {\n if (op.track === 'code') {\n return `code ${op.id} changed (${op.lines ?? 0} line${op.lines === 1 ? '' : 's'})`\n }\n if (op.track === 'knob') return knobPhrase(op)\n if (op.track === 'config') {\n if (op.id === 'duration' && op.props?.value) {\n const [from, to] = op.props.value\n return `duration ${fmt(from, true)}→${fmt(to, true)}`\n }\n return `${op.id} changed`\n }\n if (op.track === 'data') {\n const pair = op.props?.value\n if (op.op === 'add') return `data ${op.id} set to ${fmt(pair?.[1])}`\n if (op.op === 'remove') return `data ${op.id} removed`\n return `data ${op.id}: ${fmt(pair?.[0])}→${fmt(pair?.[1])}`\n }\n if (op.track === 'look') {\n if (op.op === 'add') return `look \"${op.id}\" added`\n if (op.op === 'remove') return `look \"${op.id}\" removed`\n return `look \"${op.id}\" changed`\n }\n if (op.track === 'segments') {\n const d = op.props?.duration\n if (d) return `trim: kept footage ${fmt(d[0], true)}→${fmt(d[1], true)}`\n return 'segments edited'\n }\n if (SCOPED_TRACKS.has(op.track)) {\n return `${op.track} ${propList(op.props)}`\n }\n\n // Id-keyed span/clip tracks.\n const name = `${op.track} ${op.id}`\n // The output-duration pair a speed edit carries (what the cut gained\n // or lost in screen seconds) rides add/remove phrases too — a new 2× span\n // shortens the video without modifying anything.\n const output = op.props?.output\n const outputNote = output\n ? `output ${fmt(output[0], true)}→${fmt(output[1], true)}`\n : null\n if (op.op === 'add') {\n const range = rangeOf(op)\n const bits = [range, outputNote].filter(Boolean).join(', ')\n return bits ? `${name} added (${bits})` : `${name} added`\n }\n if (op.op === 'remove')\n return outputNote ? `${name} removed (${outputNote})` : `${name} removed`\n return `${name}: ${propList(op.props)}`\n}\n\nfunction knobPhrase(op: Op): string {\n const rename = op.props?.key\n if (rename) return `replaced knob ${fmt(rename[0])} with ${fmt(rename[1])}`\n if (op.op === 'add') {\n const kind = (op.props?.kind ?? [])[1]\n return typeof kind === 'string'\n ? `knob ${op.id} added (${kind})`\n : `knob ${op.id} added`\n }\n if (op.op === 'remove') return `knob ${op.id} removed`\n const value = op.props?.value\n const parts: string[] = []\n if (value) parts.push(`${fmt(value[0])}→${fmt(value[1])}`)\n for (const [k, [from, to]] of Object.entries(op.props ?? {})) {\n if (k === 'value') continue\n parts.push(`${k} ${fmt(from)}→${fmt(to)}`)\n }\n return `knob ${op.id}: ${parts.join(', ')}`\n}\n\nconst SCOPED_TRACKS = new Set(['frame', 'cursor', 'cam', 'export', 'camera'])\n\n/** Humanized property names for the take vocabulary. */\nconst PROP_LABELS: Record<string, string> = {\n in: 'start',\n out: 'end',\n cx: 'focus x',\n cy: 'focus y',\n}\n\n/** Properties whose numbers are seconds. */\nconst TIME_PROPS = new Set([\n 'in',\n 'out',\n 'at',\n 'start',\n 'end',\n 'duration',\n 'output',\n])\n\nfunction propList(props: Op['props']): string {\n if (!props) return 'changed'\n return Object.entries(props)\n .map(([k, [from, to]]) => {\n const time = TIME_PROPS.has(k)\n return `${PROP_LABELS[k] ?? k} ${fmt(from, time)}→${fmt(to, time)}`\n })\n .join(', ')\n}\n\nfunction rangeOf(op: Op): string | null {\n const startPair = anchorPair(op, ['in', 'at', 'start'])\n const start = startPair ? startPair[1] : undefined\n if (typeof start !== 'number') return null\n const endPair = anchorPair(op, ['out'])\n const end = endPair ? endPair[1] : undefined\n return typeof end === 'number'\n ? `${fmt(start, true)}–${fmt(end, true)}`\n : `at ${fmt(start, true)}`\n}\n\nfunction anchorPair(\n op: Op,\n keys: readonly string[],\n): [unknown, unknown] | undefined {\n for (const k of keys) {\n const pair = op.props?.[k]\n if (pair) return pair\n }\n return undefined\n}\n\n/**\n * Long strings (content knobs carry whole headlines) collapse to\n * one line and truncate — the summary is a changelog sentence, and it also\n * rides 409 stale_base bodies, so a multiline value must never spill raw.\n */\nconst FMT_STRING_MAX = 40\n\nfunction fmt(v: unknown, time = false): string {\n if (v === undefined) return '∅'\n if (v === null) return 'null'\n if (typeof v === 'number') {\n const rounded = Math.round(v * 100) / 100\n return time ? `${rounded}s` : String(rounded)\n }\n if (typeof v === 'boolean') return v ? 'on' : 'off'\n if (typeof v === 'string') {\n if (v === '') return '\"\"'\n const flat = v.replace(/\\s*\\n\\s*/g, ' ')\n const clipped =\n flat.length > FMT_STRING_MAX\n ? `${flat.slice(0, FMT_STRING_MAX - 1)}…`\n : flat\n return clipped === v ? quoteIfNeeded(clipped) : `\"${clipped}\"`\n }\n return 'changed'\n}\n\nfunction quoteIfNeeded(s: string): string {\n return /^[\\w.#-]+$/.test(s) ? s : `\"${s}\"`\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -42,65 +42,6 @@ declare function fontManifest(): {
42
42
  }[];
43
43
  };
44
44
 
45
- /**
46
- * Generated by the vosso build; do not edit by hand.
47
- * Curation (and the CC0-only licensing rule) lives in the build script;
48
- * re-run it (then `pnpm assets:push`) to change the catalog.
49
- * Files: https://assets.vos.so/music/{slug}.mp3 (−16 LUFS normalized).
50
- */
51
- interface MusicCatalogEntry {
52
- slug: string;
53
- title: string;
54
- artist: string;
55
- mood: 'upbeat' | 'minimal' | 'chill' | 'groove' | 'cinematic';
56
- /** Track length in seconds (probed from the normalized file). */
57
- duration: number;
58
- license: 'CC0';
59
- }
60
- declare const MUSIC_CATALOG: MusicCatalogEntry[];
61
-
62
- declare const MUSIC_CDN_BASE = "https://assets.vos.so/music";
63
- declare const SFX_CDN_BASE = "https://assets.vos.so/audio-sfx";
64
- type MusicMood = MusicCatalogEntry['mood'];
65
- /** Display order + label for the mood chips (the product vocabulary). */
66
- declare const MUSIC_MOODS: {
67
- mood: MusicMood;
68
- label: string;
69
- }[];
70
- interface SfxCatalogEntry {
71
- slug: string;
72
- title: string;
73
- duration: number;
74
- }
75
- /** Built-in SFX (synthesized by the vosso build, CC0). */
76
- declare const SFX_CATALOG: SfxCatalogEntry[];
77
- /** Public URL of a hosted music track (−16 LUFS normalized MP3). */
78
- declare function musicTrackUrl(slug: string): string;
79
- /** Public URL of a built-in SFX file. */
80
- declare function sfxUrl(slug: string): string;
81
- /** Look a track up by slug or by its hosted URL (for UI labels). */
82
- declare function findMusicTrack(slugOrUrl: string): MusicCatalogEntry | undefined;
83
- /** The manifest shape served by GET /api/music (mirrors music.json + SFX). */
84
- declare function musicManifest(): {
85
- version: number;
86
- base: string;
87
- tracks: {
88
- url: string;
89
- slug: string;
90
- title: string;
91
- artist: string;
92
- mood: "upbeat" | "minimal" | "chill" | "groove" | "cinematic";
93
- duration: number;
94
- license: "CC0";
95
- }[];
96
- sfx: {
97
- url: string;
98
- slug: string;
99
- title: string;
100
- duration: number;
101
- }[];
102
- };
103
-
104
45
  /**
105
46
  * Generated by the vosso build; do not edit by hand.
106
47
  * Curation lives in the build script; regenerate + `pnpm assets:push`, then
@@ -123,4 +64,4 @@ declare function typefaceUrl(slug: string): string;
123
64
  /** Case-insensitive lookup by slug or family name (null = not in catalog). */
124
65
  declare function findTypeface(nameOrSlug: string): TypefaceEntry | null;
125
66
 
126
- export { DEFAULT_TYPEFACE_SLUG, FONT_CATALOG, FONT_CDN_BASE, type FontCatalogEntry, type FontCategory, MUSIC_CATALOG, MUSIC_CDN_BASE, MUSIC_MOODS, type MusicCatalogEntry, type MusicMood, SFX_CATALOG, SFX_CDN_BASE, type SfxCatalogEntry, TYPEFACE_CATALOG, TYPEFACE_CDN_BASE, type TypefaceEntry, findFontFamily, findMusicTrack, findTypeface, fontFaceUrl, fontManifest, fontStack, musicManifest, musicTrackUrl, nearestFontWeight, sfxUrl, typefaceUrl };
67
+ export { DEFAULT_TYPEFACE_SLUG, FONT_CATALOG, FONT_CDN_BASE, type FontCatalogEntry, type FontCategory, TYPEFACE_CATALOG, TYPEFACE_CDN_BASE, type TypefaceEntry, findFontFamily, findTypeface, fontFaceUrl, fontManifest, fontStack, nearestFontWeight, typefaceUrl };
package/dist/index.js CHANGED
@@ -15,141 +15,6 @@ import {
15
15
  nearestFontWeight
16
16
  } from "./chunk-YBEIOW7L.js";
17
17
 
18
- // src/musicCatalog.ts
19
- var MUSIC_CATALOG = [
20
- {
21
- slug: "fresh-focus",
22
- title: "Fresh Focus",
23
- artist: "Kevin MacLeod",
24
- mood: "upbeat",
25
- duration: 124.1,
26
- license: "CC0"
27
- },
28
- {
29
- slug: "motions",
30
- title: "Motions",
31
- artist: "Rafael Krux",
32
- mood: "upbeat",
33
- duration: 117,
34
- license: "CC0"
35
- },
36
- {
37
- slug: "and-just-like-that",
38
- title: "And Just Like That",
39
- artist: "Bryan Teoh",
40
- mood: "upbeat",
41
- duration: 114.3,
42
- license: "CC0"
43
- },
44
- {
45
- slug: "arpent",
46
- title: "Arpent",
47
- artist: "Kevin MacLeod",
48
- mood: "minimal",
49
- duration: 162.1,
50
- license: "CC0"
51
- },
52
- {
53
- slug: "beat-one",
54
- title: "Beat One",
55
- artist: "Kevin MacLeod",
56
- mood: "minimal",
57
- duration: 180,
58
- license: "CC0"
59
- },
60
- {
61
- slug: "meditating-beat",
62
- title: "Meditating Beat",
63
- artist: "Kevin MacLeod",
64
- mood: "chill",
65
- duration: 157.2,
66
- license: "CC0"
67
- },
68
- {
69
- slug: "study-and-relax",
70
- title: "Study and Relax",
71
- artist: "Kevin MacLeod",
72
- mood: "chill",
73
- duration: 223.4,
74
- license: "CC0"
75
- },
76
- {
77
- slug: "starcourt-mall",
78
- title: "Starcourt Mall",
79
- artist: "Bryan Teoh",
80
- mood: "chill",
81
- duration: 217.5,
82
- license: "CC0"
83
- },
84
- {
85
- slug: "gotta-keep-on-movin",
86
- title: "Gotta Keep On Movin",
87
- artist: "Bryan Teoh",
88
- mood: "groove",
89
- duration: 144,
90
- license: "CC0"
91
- },
92
- {
93
- slug: "hold-on-a-sec",
94
- title: "Hold on a Sec",
95
- artist: "Bryan Teoh",
96
- mood: "groove",
97
- duration: 125.5,
98
- license: "CC0"
99
- },
100
- {
101
- slug: "slice-of-life",
102
- title: "Slice of Life",
103
- artist: "Bryan Teoh",
104
- mood: "cinematic",
105
- duration: 140.8,
106
- license: "CC0"
107
- },
108
- {
109
- slug: "ice-and-snow",
110
- title: "Ice and Snow",
111
- artist: "Rafael Krux",
112
- mood: "cinematic",
113
- duration: 141.5,
114
- license: "CC0"
115
- }
116
- ];
117
-
118
- // src/music.ts
119
- var MUSIC_CDN_BASE = "https://assets.vos.so/music";
120
- var SFX_CDN_BASE = "https://assets.vos.so/audio-sfx";
121
- var MUSIC_MOODS = [
122
- { mood: "upbeat", label: "Upbeat" },
123
- { mood: "minimal", label: "Minimal" },
124
- { mood: "chill", label: "Chill" },
125
- { mood: "groove", label: "Groove" },
126
- { mood: "cinematic", label: "Cinematic" }
127
- ];
128
- var SFX_CATALOG = [
129
- { slug: "sfx-click", title: "Click", duration: 0.07 },
130
- { slug: "sfx-pop", title: "Pop", duration: 0.12 },
131
- { slug: "sfx-whoosh", title: "Whoosh", duration: 0.6 },
132
- { slug: "sfx-chime", title: "Chime", duration: 1.4 }
133
- ];
134
- function musicTrackUrl(slug) {
135
- return `${MUSIC_CDN_BASE}/${slug}.mp3`;
136
- }
137
- function sfxUrl(slug) {
138
- return `${SFX_CDN_BASE}/${slug}.wav`;
139
- }
140
- function findMusicTrack(slugOrUrl) {
141
- const slug = slugOrUrl.startsWith(MUSIC_CDN_BASE) ? slugOrUrl.slice(MUSIC_CDN_BASE.length + 1).replace(/\.mp3$/, "") : slugOrUrl;
142
- return MUSIC_CATALOG.find((t) => t.slug === slug);
143
- }
144
- function musicManifest() {
145
- return {
146
- version: 1,
147
- base: MUSIC_CDN_BASE,
148
- tracks: MUSIC_CATALOG.map((t) => ({ ...t, url: musicTrackUrl(t.slug) })),
149
- sfx: SFX_CATALOG.map((s) => ({ ...s, url: sfxUrl(s.slug) }))
150
- };
151
- }
152
-
153
18
  // src/typefaceCatalog.ts
154
19
  var TYPEFACE_CDN_BASE = "https://assets.vos.so/typefaces";
155
20
  var DEFAULT_TYPEFACE_SLUG = "lexend";
@@ -178,26 +43,17 @@ export {
178
43
  DEFAULT_TYPEFACE_SLUG,
179
44
  FONT_CATALOG,
180
45
  FONT_CDN_BASE,
181
- MUSIC_CATALOG,
182
- MUSIC_CDN_BASE,
183
- MUSIC_MOODS,
184
- SFX_CATALOG,
185
- SFX_CDN_BASE,
186
46
  TYPEFACE_CATALOG,
187
47
  TYPEFACE_CDN_BASE,
188
48
  findFontFamily,
189
- findMusicTrack,
190
49
  findTypeface,
191
50
  fontFaceUrl,
192
51
  fontManifest,
193
52
  fontStack,
194
53
  formatLabel,
195
54
  generateId,
196
- musicManifest,
197
- musicTrackUrl,
198
55
  nearestFontWeight,
199
56
  safeJsonParse,
200
- sfxUrl,
201
57
  sleep,
202
58
  typefaceUrl
203
59
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/musicCatalog.ts","../src/music.ts","../src/typefaceCatalog.ts"],"sourcesContent":["/**\n * Generated by the vosso build; do not edit by hand.\n * Curation (and the CC0-only licensing rule) lives in the build script;\n * re-run it (then `pnpm assets:push`) to change the catalog.\n * Files: https://assets.vos.so/music/{slug}.mp3 (−16 LUFS normalized).\n */\nexport interface MusicCatalogEntry {\n slug: string\n title: string\n artist: string\n mood: 'upbeat' | 'minimal' | 'chill' | 'groove' | 'cinematic'\n /** Track length in seconds (probed from the normalized file). */\n duration: number\n license: 'CC0'\n}\n\nexport const MUSIC_CATALOG: MusicCatalogEntry[] = [\n {\n slug: 'fresh-focus',\n title: 'Fresh Focus',\n artist: 'Kevin MacLeod',\n mood: 'upbeat',\n duration: 124.1,\n license: 'CC0',\n },\n {\n slug: 'motions',\n title: 'Motions',\n artist: 'Rafael Krux',\n mood: 'upbeat',\n duration: 117,\n license: 'CC0',\n },\n {\n slug: 'and-just-like-that',\n title: 'And Just Like That',\n artist: 'Bryan Teoh',\n mood: 'upbeat',\n duration: 114.3,\n license: 'CC0',\n },\n {\n slug: 'arpent',\n title: 'Arpent',\n artist: 'Kevin MacLeod',\n mood: 'minimal',\n duration: 162.1,\n license: 'CC0',\n },\n {\n slug: 'beat-one',\n title: 'Beat One',\n artist: 'Kevin MacLeod',\n mood: 'minimal',\n duration: 180,\n license: 'CC0',\n },\n {\n slug: 'meditating-beat',\n title: 'Meditating Beat',\n artist: 'Kevin MacLeod',\n mood: 'chill',\n duration: 157.2,\n license: 'CC0',\n },\n {\n slug: 'study-and-relax',\n title: 'Study and Relax',\n artist: 'Kevin MacLeod',\n mood: 'chill',\n duration: 223.4,\n license: 'CC0',\n },\n {\n slug: 'starcourt-mall',\n title: 'Starcourt Mall',\n artist: 'Bryan Teoh',\n mood: 'chill',\n duration: 217.5,\n license: 'CC0',\n },\n {\n slug: 'gotta-keep-on-movin',\n title: 'Gotta Keep On Movin',\n artist: 'Bryan Teoh',\n mood: 'groove',\n duration: 144,\n license: 'CC0',\n },\n {\n slug: 'hold-on-a-sec',\n title: 'Hold on a Sec',\n artist: 'Bryan Teoh',\n mood: 'groove',\n duration: 125.5,\n license: 'CC0',\n },\n {\n slug: 'slice-of-life',\n title: 'Slice of Life',\n artist: 'Bryan Teoh',\n mood: 'cinematic',\n duration: 140.8,\n license: 'CC0',\n },\n {\n slug: 'ice-and-snow',\n title: 'Ice and Snow',\n artist: 'Rafael Krux',\n mood: 'cinematic',\n duration: 141.5,\n license: 'CC0',\n },\n]\n","/**\n * The ONE audio-catalog seam (mirrors the fonts pattern): the curated,\n * self-hosted music tracks and SFX every surface reads — the Audio panel\n * picker, the API's GET /music, and agent contracts. Generated by the vosso\n * build; do not edit by hand (curation + the CC0-only licensing rule live in\n * the build script); SFX are synthesized at build time, so their four\n * entries are stable literals here. Files live in the vosso-public bucket\n * (assets.vos.so) — docs store these absolute URLs (the backgrounds rule: a\n * ProjectDoc travels across environments, so keys must resolve everywhere).\n */\nimport { MUSIC_CATALOG } from './musicCatalog'\nimport type { MusicCatalogEntry } from './musicCatalog'\n\nexport { MUSIC_CATALOG } from './musicCatalog'\nexport type { MusicCatalogEntry } from './musicCatalog'\n\nexport const MUSIC_CDN_BASE = 'https://assets.vos.so/music'\nexport const SFX_CDN_BASE = 'https://assets.vos.so/audio-sfx'\n\nexport type MusicMood = MusicCatalogEntry['mood']\n\n/** Display order + label for the mood chips (the product vocabulary). */\nexport const MUSIC_MOODS: { mood: MusicMood; label: string }[] = [\n { mood: 'upbeat', label: 'Upbeat' },\n { mood: 'minimal', label: 'Minimal' },\n { mood: 'chill', label: 'Chill' },\n { mood: 'groove', label: 'Groove' },\n { mood: 'cinematic', label: 'Cinematic' },\n]\n\nexport interface SfxCatalogEntry {\n slug: string\n title: string\n duration: number\n}\n\n/** Built-in SFX (synthesized by the vosso build, CC0). */\nexport const SFX_CATALOG: SfxCatalogEntry[] = [\n { slug: 'sfx-click', title: 'Click', duration: 0.07 },\n { slug: 'sfx-pop', title: 'Pop', duration: 0.12 },\n { slug: 'sfx-whoosh', title: 'Whoosh', duration: 0.6 },\n { slug: 'sfx-chime', title: 'Chime', duration: 1.4 },\n]\n\n/** Public URL of a hosted music track (−16 LUFS normalized MP3). */\nexport function musicTrackUrl(slug: string): string {\n return `${MUSIC_CDN_BASE}/${slug}.mp3`\n}\n\n/** Public URL of a built-in SFX file. */\nexport function sfxUrl(slug: string): string {\n return `${SFX_CDN_BASE}/${slug}.wav`\n}\n\n/** Look a track up by slug or by its hosted URL (for UI labels). */\nexport function findMusicTrack(\n slugOrUrl: string,\n): MusicCatalogEntry | undefined {\n const slug = slugOrUrl.startsWith(MUSIC_CDN_BASE)\n ? slugOrUrl.slice(MUSIC_CDN_BASE.length + 1).replace(/\\.mp3$/, '')\n : slugOrUrl\n return MUSIC_CATALOG.find((t) => t.slug === slug)\n}\n\n/** The manifest shape served by GET /api/music (mirrors music.json + SFX). */\nexport function musicManifest() {\n return {\n version: 1,\n base: MUSIC_CDN_BASE,\n tracks: MUSIC_CATALOG.map((t) => ({ ...t, url: musicTrackUrl(t.slug) })),\n sfx: SFX_CATALOG.map((s) => ({ ...s, url: sfxUrl(s.slug) })),\n }\n}\n","/**\n * Generated by the vosso build; do not edit by hand.\n * Curation lives in the build script; regenerate + `pnpm assets:push`, then\n * commit this file. One catalog: the 3D prop panel, take lowering, and the\n * CLI lints all read it.\n */\n\nexport interface TypefaceEntry {\n /** Catalog family name (matches the webfont catalog). */\n family: string\n /** File slug — typefaces/{slug}.typeface.json on assets.vos.so. */\n slug: string\n /** The converted weight (one file per family; extrusion wants bold). */\n weight: number\n}\n\nexport const TYPEFACE_CDN_BASE = 'https://assets.vos.so/typefaces'\n\n/** The house default 3D face. */\nexport const DEFAULT_TYPEFACE_SLUG = 'lexend'\n\nexport const TYPEFACE_CATALOG: TypefaceEntry[] = [\n { family: 'Lexend', slug: 'lexend', weight: 700 },\n { family: 'Space Grotesk', slug: 'space-grotesk', weight: 700 },\n { family: 'Bebas Neue', slug: 'bebas-neue', weight: 400 },\n { family: 'Archivo Black', slug: 'archivo-black', weight: 400 },\n { family: 'Anton', slug: 'anton', weight: 400 },\n { family: 'Righteous', slug: 'righteous', weight: 400 },\n { family: 'Playfair Display', slug: 'playfair-display', weight: 700 },\n { family: 'Fraunces', slug: 'fraunces', weight: 700 },\n { family: 'Pacifico', slug: 'pacifico', weight: 400 },\n { family: 'JetBrains Mono', slug: 'jetbrains-mono', weight: 700 },\n]\n\nexport function typefaceUrl(slug: string): string {\n return `${TYPEFACE_CDN_BASE}/${slug}.typeface.json`\n}\n\n/** Case-insensitive lookup by slug or family name (null = not in catalog). */\nexport function findTypeface(nameOrSlug: string): TypefaceEntry | null {\n const needle = nameOrSlug.trim().toLowerCase()\n return (\n TYPEFACE_CATALOG.find(\n (t) => t.slug === needle || t.family.toLowerCase() === needle,\n ) ?? null\n )\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/typefaceCatalog.ts"],"sourcesContent":["/**\n * Generated by the vosso build; do not edit by hand.\n * Curation lives in the build script; regenerate + `pnpm assets:push`, then\n * commit this file. One catalog: the 3D prop panel, take lowering, and the\n * CLI lints all read it.\n */\n\nexport interface TypefaceEntry {\n /** Catalog family name (matches the webfont catalog). */\n family: string\n /** File slug — typefaces/{slug}.typeface.json on assets.vos.so. */\n slug: string\n /** The converted weight (one file per family; extrusion wants bold). */\n weight: number\n}\n\nexport const TYPEFACE_CDN_BASE = 'https://assets.vos.so/typefaces'\n\n/** The house default 3D face. */\nexport const DEFAULT_TYPEFACE_SLUG = 'lexend'\n\nexport const TYPEFACE_CATALOG: TypefaceEntry[] = [\n { family: 'Lexend', slug: 'lexend', weight: 700 },\n { family: 'Space Grotesk', slug: 'space-grotesk', weight: 700 },\n { family: 'Bebas Neue', slug: 'bebas-neue', weight: 400 },\n { family: 'Archivo Black', slug: 'archivo-black', weight: 400 },\n { family: 'Anton', slug: 'anton', weight: 400 },\n { family: 'Righteous', slug: 'righteous', weight: 400 },\n { family: 'Playfair Display', slug: 'playfair-display', weight: 700 },\n { family: 'Fraunces', slug: 'fraunces', weight: 700 },\n { family: 'Pacifico', slug: 'pacifico', weight: 400 },\n { family: 'JetBrains Mono', slug: 'jetbrains-mono', weight: 700 },\n]\n\nexport function typefaceUrl(slug: string): string {\n return `${TYPEFACE_CDN_BASE}/${slug}.typeface.json`\n}\n\n/** Case-insensitive lookup by slug or family name (null = not in catalog). */\nexport function findTypeface(nameOrSlug: string): TypefaceEntry | null {\n const needle = nameOrSlug.trim().toLowerCase()\n return (\n TYPEFACE_CATALOG.find(\n (t) => t.slug === needle || t.family.toLowerCase() === needle,\n ) ?? null\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgBO,IAAM,oBAAoB;AAG1B,IAAM,wBAAwB;AAE9B,IAAM,mBAAoC;AAAA,EAC/C,EAAE,QAAQ,UAAU,MAAM,UAAU,QAAQ,IAAI;AAAA,EAChD,EAAE,QAAQ,iBAAiB,MAAM,iBAAiB,QAAQ,IAAI;AAAA,EAC9D,EAAE,QAAQ,cAAc,MAAM,cAAc,QAAQ,IAAI;AAAA,EACxD,EAAE,QAAQ,iBAAiB,MAAM,iBAAiB,QAAQ,IAAI;AAAA,EAC9D,EAAE,QAAQ,SAAS,MAAM,SAAS,QAAQ,IAAI;AAAA,EAC9C,EAAE,QAAQ,aAAa,MAAM,aAAa,QAAQ,IAAI;AAAA,EACtD,EAAE,QAAQ,oBAAoB,MAAM,oBAAoB,QAAQ,IAAI;AAAA,EACpE,EAAE,QAAQ,YAAY,MAAM,YAAY,QAAQ,IAAI;AAAA,EACpD,EAAE,QAAQ,YAAY,MAAM,YAAY,QAAQ,IAAI;AAAA,EACpD,EAAE,QAAQ,kBAAkB,MAAM,kBAAkB,QAAQ,IAAI;AAClE;AAEO,SAAS,YAAY,MAAsB;AAChD,SAAO,GAAG,iBAAiB,IAAI,IAAI;AACrC;AAGO,SAAS,aAAa,YAA0C;AACrE,QAAM,SAAS,WAAW,KAAK,EAAE,YAAY;AAC7C,SACE,iBAAiB;AAAA,IACf,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,OAAO,YAAY,MAAM;AAAA,EACzD,KAAK;AAET;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vosjs/shared",
3
- "version": "0.1.0",
4
- "description": "The small shared layer under the vos CLI and the vosso studio: the semantic differ, the free-tier limits, frontmatter, params, the font, music and typeface catalogs.",
3
+ "version": "0.2.0",
4
+ "description": "The small shared layer under the vos CLI and the vosso studio: the semantic differ, the free-tier limits, frontmatter, params, the font and typeface catalogs.",
5
5
  "license": "MIT",
6
6
  "author": "vosso",
7
7
  "homepage": "https://vos.so",