@trdrs/quickcharts 1.3.0 → 2.0.1

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +209 -200
  3. package/THIRD-PARTY-NOTICES.md +4 -4
  4. package/dist/adapters/rest.d.ts +3 -3
  5. package/dist/adapters/rest.js +2 -2
  6. package/dist/adapters/rest.js.map +1 -1
  7. package/dist/{ar-G7MVXRE4.js → ar-KSXQ2USS.js} +20 -20
  8. package/dist/ar-KSXQ2USS.js.map +1 -0
  9. package/dist/{assets-ySndlUS3.d.ts → assets-DOuBBsOp.d.ts} +50 -50
  10. package/dist/{ca_ES-EUSRU4VG.js → ca_ES-JG4FM55E.js} +20 -20
  11. package/dist/ca_ES-JG4FM55E.js.map +1 -0
  12. package/dist/{chunk-KFBQSPNA.js → chunk-D3S26ARP.js} +2 -2
  13. package/dist/chunk-D3S26ARP.js.map +1 -0
  14. package/dist/{chunk-AKXIFHQN.js → chunk-KLPTCWA2.js} +1 -1
  15. package/dist/{chunk-AKXIFHQN.js.map → chunk-KLPTCWA2.js.map} +1 -1
  16. package/dist/{chunk-AA7XKXH4.js → chunk-YR7NT5ZW.js} +67 -33
  17. package/dist/chunk-YR7NT5ZW.js.map +1 -0
  18. package/dist/{de-2SVTG25S.js → de-VSW2LHNF.js} +20 -20
  19. package/dist/de-VSW2LHNF.js.map +1 -0
  20. package/dist/drawings.d.ts +16 -16
  21. package/dist/drawings.js +12 -12
  22. package/dist/{es-UDHZM4W7.js → es-5ZXIPVOB.js} +20 -20
  23. package/dist/es-5ZXIPVOB.js.map +1 -0
  24. package/dist/feature-manifest.json +3 -3
  25. package/dist/{fr-35VN4G4S.js → fr-4F6DAWGD.js} +20 -20
  26. package/dist/fr-4F6DAWGD.js.map +1 -0
  27. package/dist/{he_IL-JPZMCURP.js → he_IL-B7PBQUEV.js} +20 -20
  28. package/dist/he_IL-B7PBQUEV.js.map +1 -0
  29. package/dist/{id_ID-XSTFHHJQ.js → id_ID-CYJRMXPC.js} +20 -20
  30. package/dist/id_ID-CYJRMXPC.js.map +1 -0
  31. package/dist/index.d.ts +167 -163
  32. package/dist/index.js +342 -268
  33. package/dist/index.js.map +1 -1
  34. package/dist/{it-EIY6JESQ.js → it-KTX4SG5P.js} +20 -20
  35. package/dist/it-KTX4SG5P.js.map +1 -0
  36. package/dist/{ja-42N4XRCW.js → ja-V7OURU5D.js} +20 -20
  37. package/dist/ja-V7OURU5D.js.map +1 -0
  38. package/dist/{ko-3VRPTIP3.js → ko-2RNEBYVE.js} +20 -20
  39. package/dist/ko-2RNEBYVE.js.map +1 -0
  40. package/dist/{ms_MY-SKN7O6M5.js → ms_MY-EY235E7Q.js} +20 -20
  41. package/dist/ms_MY-EY235E7Q.js.map +1 -0
  42. package/dist/{pl-TYARHCLT.js → pl-ADNVRP55.js} +20 -20
  43. package/dist/pl-ADNVRP55.js.map +1 -0
  44. package/dist/{pt-72IIFLL4.js → pt-OL2DR3DO.js} +20 -20
  45. package/dist/pt-OL2DR3DO.js.map +1 -0
  46. package/dist/quickcharts.css +97 -96
  47. package/dist/{resources-Ch8eA1Mw.d.ts → resources-Le6flcEn.d.ts} +3 -3
  48. package/dist/rest-openapi.json +11 -11
  49. package/dist/{ru-TPH4F7IB.js → ru-V6YFEPAF.js} +20 -20
  50. package/dist/ru-V6YFEPAF.js.map +1 -0
  51. package/dist/{sv-DC5RS67Z.js → sv-TORXQVE7.js} +20 -20
  52. package/dist/sv-TORXQVE7.js.map +1 -0
  53. package/dist/{th-I6H6AOZ3.js → th-K6SJSYSY.js} +20 -20
  54. package/dist/th-K6SJSYSY.js.map +1 -0
  55. package/dist/theme-manifest.json +10 -10
  56. package/dist/{tr-7FSUVCNW.js → tr-4AEM66KW.js} +20 -20
  57. package/dist/tr-4AEM66KW.js.map +1 -0
  58. package/dist/{vi-DLRPHLLH.js → vi-YU5XCEUU.js} +20 -20
  59. package/dist/vi-YU5XCEUU.js.map +1 -0
  60. package/dist/{zh-ZXIQBBEP.js → zh-QXKRYHU5.js} +20 -20
  61. package/dist/zh-QXKRYHU5.js.map +1 -0
  62. package/dist/{zh_TW-TYNUJU7U.js → zh_TW-AYPDKG4Q.js} +20 -20
  63. package/dist/zh_TW-AYPDKG4Q.js.map +1 -0
  64. package/package.json +2 -2
  65. package/dist/ar-G7MVXRE4.js.map +0 -1
  66. package/dist/ca_ES-EUSRU4VG.js.map +0 -1
  67. package/dist/chunk-AA7XKXH4.js.map +0 -1
  68. package/dist/chunk-KFBQSPNA.js.map +0 -1
  69. package/dist/de-2SVTG25S.js.map +0 -1
  70. package/dist/es-UDHZM4W7.js.map +0 -1
  71. package/dist/fr-35VN4G4S.js.map +0 -1
  72. package/dist/he_IL-JPZMCURP.js.map +0 -1
  73. package/dist/id_ID-XSTFHHJQ.js.map +0 -1
  74. package/dist/it-EIY6JESQ.js.map +0 -1
  75. package/dist/ja-42N4XRCW.js.map +0 -1
  76. package/dist/ko-3VRPTIP3.js.map +0 -1
  77. package/dist/ms_MY-SKN7O6M5.js.map +0 -1
  78. package/dist/pl-TYARHCLT.js.map +0 -1
  79. package/dist/pt-72IIFLL4.js.map +0 -1
  80. package/dist/ru-TPH4F7IB.js.map +0 -1
  81. package/dist/sv-DC5RS67Z.js.map +0 -1
  82. package/dist/th-I6H6AOZ3.js.map +0 -1
  83. package/dist/tr-7FSUVCNW.js.map +0 -1
  84. package/dist/vi-DLRPHLLH.js.map +0 -1
  85. package/dist/zh-ZXIQBBEP.js.map +0 -1
  86. package/dist/zh_TW-TYNUJU7U.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  drawingContextKey
3
- } from "./chunk-AKXIFHQN.js";
3
+ } from "./chunk-KLPTCWA2.js";
4
4
 
5
5
  // src/resources.ts
6
6
  var ResourceAbortError = class extends Error {
@@ -104,4 +104,4 @@ export {
104
104
  ResourceAbortError,
105
105
  memorySaveLoadAdapter
106
106
  };
107
- //# sourceMappingURL=chunk-KFBQSPNA.js.map
107
+ //# sourceMappingURL=chunk-D3S26ARP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resources.ts"],"sourcesContent":["// SAVED RESOURCES — the revisioned, async persistence contract for the entities a chart names and\n// a viewer can lose: saved charts, layouts, drawing documents, and templates.\n//\n// The contract exists because a saved chart is shared, mutable, remote state. Two tabs, two\n// devices, or one slow save and one fast one all end at the same question: whose version wins?\n// A store that answers \"the last write\" silently destroys work. So every read hands back a\n// `ResourceRef` (a stable id plus an opaque revision token), every write states the revision it\n// believes it is replacing, and a write against a stale revision comes back as a typed `conflict`\n// carrying the current ref. The chart never resolves that by overwriting; it surfaces it.\n//\n// Three rules the shape enforces:\n// - The revision is OPAQUE. It is a token the host minted and the chart only ever echoes back.\n// No ordering, no arithmetic, no timestamp parsing; a host is free to use an ETag, a counter,\n// or a content hash.\n// - Every call takes an `AbortSignal`. A symbol switch, a closed dialog, or a fast retype must be\n// able to abandon in-flight work, and a late answer to an abandoned ask must never land.\n// - A successful write RETURNS the stored revision, so the caller's next write is conditional on\n// what the store actually holds rather than on what it hoped it wrote.\n//\n// A chart's, a layout's and a template's body is contractually OPAQUE to the store: no parsing, no\n// per-entity logic, so the chart can evolve those formats freely. The drawings family is the one\n// deliberate exception: its body is the structured document in `drawings/document.ts`, because\n// deletion tombstones, ordered groups and per-drawing ownership have to survive a merge between two\n// surfaces, and a merge cannot be performed over a string. Each drawing's own state stays opaque\n// inside its entry.\n//\n// Flat viewer preferences that need no entity identity are NOT here; they stay on `ChartStorage`,\n// the small settings port beside this one.\nimport type { DrawingResourceContext, DrawingsBody } from './drawings/document'\nimport { drawingContextKey } from './drawings/document'\n\n/** A resource's identity plus the exact version this reader saw. A write quotes it back. */\nexport interface ResourceRef {\n id: string\n /** Opaque to the chart: minted by the store, compared only for equality. */\n revision: string\n}\n\n/** What a write did. `ok` carries the ref the store now holds (and, where the store computes one,\n * the resulting metadata); `conflict` carries the ref that beat this write, so a caller can reload\n * and re-decide; `not-found` means the id is gone. */\nexport type WriteOutcome<T = void> =\n | { kind: 'ok'; ref: ResourceRef; value?: T }\n | { kind: 'conflict'; current: ResourceRef }\n | { kind: 'not-found' }\n\n/** One family of resources. `Meta` is the listing row a picker shows without fetching content\n * (it carries the row's ref, so a picker can delete or open a row without a second read); `Body`\n * is the stored document. */\nexport interface ResourceStore<Meta, Body> {\n /** Listing rows, metadata only. */\n list(signal?: AbortSignal): Promise<Meta[]>\n /** The stored document and the revision it was read at, or null when the id is unknown. */\n load(id: string, signal?: AbortSignal): Promise<{ ref: ResourceRef; body: Body } | null>\n /** Store a new document. The store mints the id; a create conflicts only where the store keys a\n * document by identity and one already stands there (a drawings context that holds a document, a\n * name already taken), and then it answers the ref that stands. */\n create(body: Body, signal?: AbortSignal): Promise<WriteOutcome<Meta>>\n /** Replace the document at `ref.id`, but only while it still stands at `ref.revision`. */\n update(ref: ResourceRef, body: Body, signal?: AbortSignal): Promise<WriteOutcome<Meta>>\n /** Delete the document at `ref.id`, but only while it still stands at `ref.revision`. */\n remove(ref: ResourceRef, signal?: AbortSignal): Promise<WriteOutcome<void>>\n}\n\n/** A saved chart's listing row. */\nexport interface ChartMeta extends ResourceRef {\n name: string\n symbol: string\n timeframe: string\n /** Last save, ms since epoch (server-stamped by real backends). */\n updatedAt: number\n}\n\n/** A saved chart. `content` is the chart's own serialized state, opaque to the store. */\nexport interface ChartBody {\n name: string\n symbol: string\n timeframe: string\n content: string\n}\n\n/** A saved multi-chart layout's listing row. `symbol` is the active chart's market as the chart\n * writes it (`symbolNames(...).mark`, such as `BTCUSDC`) and `timeframe` its timeframe token, both\n * as they stood at the last save, so a listing can say what each layout shows without opening it. A\n * store that does not keep them, or a layout saved before they were stated, leaves them out. */\nexport interface LayoutMeta extends ResourceRef {\n name: string\n symbol?: string\n timeframe?: string\n updatedAt: number\n}\n\n/** A saved multi-chart layout. `content` is the arrangement's serialized state, opaque to the store.\n * The widget states the listing's `symbol` and `timeframe` on every save; they are display text and\n * play no part in restoring the layout. */\nexport interface LayoutBody {\n name: string\n symbol?: string\n timeframe?: string\n content: string\n}\n\n/** A drawings document's listing row. A context holds at most one, so `list` returns zero rows or\n * one, and the ref is the store's handle on it. */\nexport interface DrawingsMeta extends ResourceRef {\n updatedAt: number\n}\n\nexport type TemplateKind = 'indicator' | 'drawing' | 'palette'\n\n/** A named template's listing row. `tool` scopes DRAWING templates to their tool (a trend-line\n * template is meaningless on a rectangle); indicator and palette templates carry no tool. */\nexport interface TemplateMeta extends ResourceRef {\n name: string\n tool?: string\n updatedAt: number\n}\n\nexport interface TemplateBody {\n name: string\n tool?: string\n content: string\n}\n\n/** The adapter a host implements: four resource families over one revisioned contract. Viewer\n * settings are deliberately absent; they belong to `ChartStorage`. */\nexport interface ChartSaveLoadAdapter {\n charts: ResourceStore<ChartMeta, ChartBody>\n layouts: ResourceStore<LayoutMeta, LayoutBody>\n /** The store for one drawing-resource context. Calling with an equal context returns an\n * equivalent store; `drawingContextKey` is the canonical way to compare two. */\n drawings(context: DrawingResourceContext): ResourceStore<DrawingsMeta, DrawingsBody>\n templates(kind: TemplateKind): ResourceStore<TemplateMeta, TemplateBody>\n}\n\n/** The error every store rejects with when its signal aborts. `name` is 'AbortError', which is what\n * callers branch on, and what `DOMException` uses, so a host may reject with either. */\nexport class ResourceAbortError extends Error {\n override readonly name = 'AbortError'\n constructor(message = 'the resource operation was aborted') {\n super(message)\n }\n}\n\n/* ── The in-memory reference implementation ────────────────────────────────────────────────────\n The shape's own proof and the fixture every consumer test mounts: revisions on every write,\n conflicts on a stale ref, not-found on a vanished id, and an abort that lands before any state\n changes. It persists nothing, so it is also the right adapter for SSR, an ephemeral embed, and\n the clean-room consumer. */\n\ninterface Record_<Body> {\n id: string\n revision: string\n body: Body\n updatedAt: number\n}\n\n/** An aborted signal REJECTS the returned promise rather than throwing out of the call, so every\n * caller handles abandonment on one path. */\nconst aborted = (signal: AbortSignal | undefined): Promise<never> | null =>\n signal?.aborted ? Promise.reject(new ResourceAbortError()) : null\n\nconst newId = (): string =>\n typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `r${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`\n\n/** Options for {@link memorySaveLoadAdapter}: a clock, so a test can pin `updatedAt`. */\nexport interface MemoryResourcesOptions {\n now?: () => number\n}\n\nfunction memoryStore<Meta, Body>(\n metaOf: (record: Record_<Body>) => Meta,\n now: () => number,\n): ResourceStore<Meta, Body> {\n const rows = new Map<string, Record_<Body>>()\n let counter = 0\n const nextRevision = (): string => `rev-${++counter}`\n const refOf = (record: Record_<Body>): ResourceRef => ({ id: record.id, revision: record.revision })\n\n return {\n list(signal) {\n return aborted(signal) ?? Promise.resolve([...rows.values()].map(metaOf))\n },\n load(id, signal) {\n const stop = aborted(signal)\n if (stop) return stop\n const record = rows.get(id)\n return Promise.resolve(record ? { ref: refOf(record), body: record.body } : null)\n },\n create(body, signal) {\n const stop = aborted(signal)\n if (stop) return stop\n const record: Record_<Body> = { id: newId(), revision: nextRevision(), body, updatedAt: now() }\n rows.set(record.id, record)\n const outcome: WriteOutcome<Meta> = { kind: 'ok', ref: refOf(record), value: metaOf(record) }\n return Promise.resolve(outcome)\n },\n update(ref, body, signal) {\n const stop = aborted(signal)\n if (stop) return stop\n const record = rows.get(ref.id)\n if (!record) return Promise.resolve<WriteOutcome<Meta>>({ kind: 'not-found' })\n if (record.revision !== ref.revision) return Promise.resolve<WriteOutcome<Meta>>({ kind: 'conflict', current: refOf(record) })\n record.body = body\n record.revision = nextRevision()\n record.updatedAt = now()\n const outcome: WriteOutcome<Meta> = { kind: 'ok', ref: refOf(record), value: metaOf(record) }\n return Promise.resolve(outcome)\n },\n remove(ref, signal) {\n const stop = aborted(signal)\n if (stop) return stop\n const record = rows.get(ref.id)\n if (!record) return Promise.resolve<WriteOutcome<void>>({ kind: 'not-found' })\n if (record.revision !== ref.revision) return Promise.resolve<WriteOutcome<void>>({ kind: 'conflict', current: refOf(record) })\n rows.delete(ref.id)\n return Promise.resolve<WriteOutcome<void>>({ kind: 'ok', ref: refOf(record) })\n },\n }\n}\n\n/** An in-memory {@link ChartSaveLoadAdapter}. Each drawing context and each template kind gets its\n * own store, created on first ask and kept, so two calls for one context see the same document. */\nexport function memorySaveLoadAdapter(options?: MemoryResourcesOptions): ChartSaveLoadAdapter {\n const now = options?.now ?? (() => Date.now())\n const drawingStores = new Map<string, ResourceStore<DrawingsMeta, DrawingsBody>>()\n const templateStores = new Map<TemplateKind, ResourceStore<TemplateMeta, TemplateBody>>()\n\n return {\n charts: memoryStore<ChartMeta, ChartBody>(\n (r) => ({ id: r.id, revision: r.revision, name: r.body.name, symbol: r.body.symbol, timeframe: r.body.timeframe, updatedAt: r.updatedAt }),\n now,\n ),\n layouts: memoryStore<LayoutMeta, LayoutBody>(\n (r) => ({\n id: r.id,\n revision: r.revision,\n name: r.body.name,\n ...(r.body.symbol === undefined ? {} : { symbol: r.body.symbol }),\n ...(r.body.timeframe === undefined ? {} : { timeframe: r.body.timeframe }),\n updatedAt: r.updatedAt,\n }),\n now,\n ),\n drawings(context) {\n const key = drawingContextKey(context)\n let store = drawingStores.get(key)\n if (!store) {\n store = memoryStore<DrawingsMeta, DrawingsBody>((r) => ({ id: r.id, revision: r.revision, updatedAt: r.updatedAt }), now)\n drawingStores.set(key, store)\n }\n return store\n },\n templates(kind) {\n let store = templateStores.get(kind)\n if (!store) {\n store = memoryStore<TemplateMeta, TemplateBody>(\n (r) => ({ id: r.id, revision: r.revision, name: r.body.name, ...(r.body.tool === undefined ? {} : { tool: r.body.tool }), updatedAt: r.updatedAt }),\n now,\n )\n templateStores.set(kind, store)\n }\n return store\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -169,4 +169,4 @@ export {
169
169
  mergeDrawingDocuments,
170
170
  parseDrawingDocument
171
171
  };
172
- //# sourceMappingURL=chunk-AKXIFHQN.js.map
172
+ //# sourceMappingURL=chunk-KLPTCWA2.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/drawings/document.ts"],"sourcesContent":["// The drawings DOCUMENT: the pure rules over the drawing-resource contract's document shape.\n//\n// Nothing here touches a chart, a store or a clock. That is deliberate: what a document means when\n// two surfaces write it, what a deletion does to it, and which of its entries are live are exactly\n// the questions that go wrong in a persistence layer, and they are all decidable from two values.\n//\n// Three rules the module enforces:\n// - A deletion is a TOMBSTONE, never an omission. Two charts hold the same document; if a\n// deletion were just a shorter list, the other chart's copy would put the row back on the next\n// merge, and a drawing the trader deleted would return after a save, a reconnect or a reload.\n// - A merge is not a last-write-wins overwrite. The stored document is the other surface's work\n// and wins by identity and by order; this surface's own additions follow it. Neither side\n// loses a row it made, and neither side resurrects a row the other buried.\n// - Deleting a GROUP deletes the grouping, not the drawings. The members survive, ungrouped, and\n// the group id can never come back.\n//\n// The document's own SHAPE lives here too, beside the rules, because the two only make sense\n// together: the resource contract in `resources.ts` names this the drawings family's body.\n\n/* ── The drawing-resource context ───────────────────────────────────────────────────────────────\n Where a drawings document lives, as a discriminated, versioned context rather than a pair of\n loose fields. The kind IS the sharing rule, so a reader never infers one from which fields\n happen to be set:\n\n chart-local one chart of one layout, on one symbol. Nothing is shared.\n layout-shared every chart of one layout, on one symbol.\n symbol-global every chart the host mounts, on one symbol.\n\n `version` is the context's own format, not the document's: a store keys its rows by the whole\n context, so a later kind or field arrives as a different key rather than as a silently widened\n old one. */\n\nexport const DRAWING_CONTEXT_VERSION = 1\n\nexport type DrawingContextKind = 'chart-local' | 'layout-shared' | 'symbol-global'\n\n/** One chart of one layout, on one symbol. */\nexport interface ChartLocalDrawingContext {\n version: typeof DRAWING_CONTEXT_VERSION\n kind: 'chart-local'\n /** The host's stable identity for the layout the chart is tiled in. */\n layoutId: string\n /** The chart's stable identity WITHIN that layout, so the document is found again after a\n * reload; a per-mount instance id would key a document nothing could ever read back. */\n chartId: string\n symbol: string\n}\n\n/** Every chart of one layout, on one symbol. */\nexport interface LayoutSharedDrawingContext {\n version: typeof DRAWING_CONTEXT_VERSION\n kind: 'layout-shared'\n layoutId: string\n symbol: string\n}\n\n/** Every chart the host mounts, on one symbol. */\nexport interface SymbolGlobalDrawingContext {\n version: typeof DRAWING_CONTEXT_VERSION\n kind: 'symbol-global'\n symbol: string\n}\n\nexport type DrawingResourceContext = ChartLocalDrawingContext | LayoutSharedDrawingContext | SymbolGlobalDrawingContext\n\n/** The drawings document format. Bumping this is the only reason a reader ever branches. */\nexport const DRAWING_DOCUMENT_VERSION = 1\n\n/** One drawing in a document. The ENVELOPE is structured so a restore is validated without parsing\n * the drawing itself: the id is stable for the drawing's life, `source` names what owns it (the\n * main series, or an indicator's own source), `pane` names where it is drawn, and `type` is its\n * tool. `state` is the drawing's own serialized form and is opaque to the document, the store and\n * the transport. */\nexport interface DrawingEntry {\n id: string\n /** The owning source: `main` for the chart's main series, else an indicator instance's id. */\n source: string\n /** The pane the drawing is drawn in: `main` for the price pane, else the pane owner's id. */\n pane: string\n type: string\n /** Opaque: nothing between the layer and the backend reads it. */\n state: unknown\n /** The group this drawing belongs to, when it belongs to one. */\n group?: string\n}\n\n/** A group of drawings. Membership is ORDERED and holds drawing ids, so a group survives a round\n * trip with its order intact rather than being rebuilt from however a document happened to list\n * its entries. */\nexport interface DrawingGroup {\n id: string\n name?: string\n members: readonly string[]\n}\n\n/** A deletion, kept. A document that listed only what survives lets a concurrent save, a reconnect\n * or a reload resurrect what the trader deleted: the other side still holds the row and a merge\n * takes it back. A tombstone says the deletion happened, and at which document revision, so the\n * merge drops it instead. */\nexport interface DrawingTombstone {\n kind: 'drawing' | 'group'\n id: string\n /** The document revision the deletion was stamped at. */\n at: number\n}\n\n/** One context's drawings document. `revision` is the DOCUMENT's own counter, which orders\n * tombstones and detects a stale apply; it is not the store's `ResourceRef.revision`, which stays\n * opaque and is the store's alone. */\nexport interface DrawingsBody {\n version: typeof DRAWING_DOCUMENT_VERSION\n context: DrawingResourceContext\n revision: number\n entries: readonly DrawingEntry[]\n groups: readonly DrawingGroup[]\n tombstones: readonly DrawingTombstone[]\n}\n\n\n/** The canonical key for a drawing-resource context: equal contexts key equally, and no two\n * different contexts collide. The parts join on an escaped null, which no symbol, layout id or\n * chart id carries, so a symbol ending in a separator cannot spell another context's key. The\n * kind and version lead, so a later kind is a different key rather than a widened old one. */\nexport function drawingContextKey(context: DrawingResourceContext): string {\n const parts: string[] = [String(context.version), context.kind]\n if (context.kind === 'chart-local') parts.push(context.layoutId, context.chartId)\n else if (context.kind === 'layout-shared') parts.push(context.layoutId)\n parts.push(context.symbol)\n return parts.join('\\u0000')\n}\n\n/** Whether two contexts name the same document. */\nexport const sameDrawingContext = (a: DrawingResourceContext, b: DrawingResourceContext): boolean => drawingContextKey(a) === drawingContextKey(b)\n\n/** A document with nothing in it, at revision 0: what a context that has never been written reads\n * as, so every caller works over a document rather than over a null. */\nexport function emptyDrawingDocument(context: DrawingResourceContext): DrawingsBody {\n return { version: DRAWING_DOCUMENT_VERSION, context, revision: 0, entries: [], groups: [], tombstones: [] }\n}\n\nconst tombstoneKey = (kind: DrawingTombstone['kind'], id: string): string => `${kind}\\u0000${id}`\n\n/** The tombstones of both documents, one per (kind, id), each at the later revision it was seen at. */\nfunction mergeTombstones(a: readonly DrawingTombstone[], b: readonly DrawingTombstone[]): DrawingTombstone[] {\n const byKey = new Map<string, DrawingTombstone>()\n for (const stone of [...a, ...b]) {\n const key = tombstoneKey(stone.kind, stone.id)\n const held = byKey.get(key)\n if (!held || stone.at > held.at) byKey.set(key, { kind: stone.kind, id: stone.id, at: stone.at })\n }\n return [...byKey.values()]\n}\n\nconst buriedSet = (tombstones: readonly DrawingTombstone[], kind: DrawingTombstone['kind']): Set<string> =>\n new Set(tombstones.filter((stone) => stone.kind === kind).map((stone) => stone.id))\n\n/** Whether a document buries this id. Package-internal, beside the rules it serves: the layer asks\n * it to tell a live entry from one whose group is gone. */\nexport const drawingBuried = (document: DrawingsBody, kind: DrawingTombstone['kind'], id: string): boolean =>\n document.tombstones.some((stone) => stone.kind === kind && stone.id === id)\n\n/** The entries a document actually holds: nothing buried, no duplicate id, and no membership of a\n * group that is gone (the drawing survives, its grouping does not). */\nexport function liveDrawingEntries(document: DrawingsBody): DrawingEntry[] {\n const buriedDrawings = buriedSet(document.tombstones, 'drawing')\n const groups = new Set(liveDrawingGroups(document).map((group) => group.id))\n const out: DrawingEntry[] = []\n const seen = new Set<string>()\n for (const entry of document.entries) {\n if (buriedDrawings.has(entry.id) || seen.has(entry.id)) continue\n seen.add(entry.id)\n out.push(entry.group !== undefined && !groups.has(entry.group) ? omitGroup(entry) : entry)\n }\n return out\n}\n\nconst omitGroup = (entry: DrawingEntry): DrawingEntry => {\n const { group: _buried, ...rest } = entry\n return rest\n}\n\n/** The groups a document actually holds: nothing buried, no duplicate id, and no member that is\n * buried or absent, so a group never names a drawing that is not there. */\nexport function liveDrawingGroups(document: DrawingsBody): DrawingGroup[] {\n const buriedGroups = buriedSet(document.tombstones, 'group')\n const buriedDrawings = buriedSet(document.tombstones, 'drawing')\n const present = new Set(document.entries.filter((entry) => !buriedDrawings.has(entry.id)).map((entry) => entry.id))\n const out: DrawingGroup[] = []\n const seen = new Set<string>()\n for (const group of document.groups) {\n if (buriedGroups.has(group.id) || seen.has(group.id)) continue\n seen.add(group.id)\n out.push({ ...group, members: group.members.filter((id) => present.has(id)) })\n }\n return out\n}\n\n/** Replace what a document holds and stamp the difference. Every id the document held and the next\n * state does not is buried at the new revision; every id that arrives is kept in the order it\n * arrives in. One verb, one revision bump, so a save can never write entries at one revision and\n * groups at another. */\nexport function reviseDrawingDocument(\n document: DrawingsBody,\n next: { entries?: readonly DrawingEntry[]; groups?: readonly DrawingGroup[] },\n): DrawingsBody {\n const revision = document.revision + 1\n const entries = next.entries ?? liveDrawingEntries(document)\n const groups = next.groups ?? liveDrawingGroups(document)\n const keptDrawings = new Set(entries.map((entry) => entry.id))\n const keptGroups = new Set(groups.map((group) => group.id))\n const buried: DrawingTombstone[] = []\n for (const entry of liveDrawingEntries(document)) if (!keptDrawings.has(entry.id)) buried.push({ kind: 'drawing', id: entry.id, at: revision })\n for (const group of liveDrawingGroups(document)) if (!keptGroups.has(group.id)) buried.push({ kind: 'group', id: group.id, at: revision })\n return {\n version: DRAWING_DOCUMENT_VERSION,\n context: document.context,\n revision,\n entries: [...entries],\n groups: [...groups],\n tombstones: mergeTombstones(document.tombstones, buried),\n }\n}\n\n/** Merge the document another surface wrote first with this surface's own. The stored rows win by\n * identity and keep the stored order, because they are what everyone else can already see; rows\n * only this surface holds follow, because they are work the other surface never saw. Every\n * deletion either side made survives the merge. The result's revision is past both, so the write\n * that carries it is the newer document by either side's counter. */\nexport function mergeDrawingDocuments(stored: DrawingsBody, mine: DrawingsBody): DrawingsBody {\n const tombstones = mergeTombstones(stored.tombstones, mine.tombstones)\n const buriedDrawings = buriedSet(tombstones, 'drawing')\n const buriedGroups = buriedSet(tombstones, 'group')\n\n const entries: DrawingEntry[] = []\n const seen = new Set<string>()\n for (const entry of [...stored.entries, ...mine.entries]) {\n if (seen.has(entry.id) || buriedDrawings.has(entry.id)) continue\n seen.add(entry.id)\n entries.push(entry.group !== undefined && buriedGroups.has(entry.group) ? omitGroup(entry) : entry)\n }\n\n const groups: DrawingGroup[] = []\n const byId = new Map<string, DrawingGroup>()\n for (const group of [...stored.groups, ...mine.groups]) {\n if (buriedGroups.has(group.id)) continue\n const held = byId.get(group.id)\n if (!held) {\n const row = { ...group, members: [...group.members] }\n byId.set(group.id, row)\n groups.push(row)\n continue\n }\n // One group written by both surfaces: the stored order stands and the members only this\n // surface knows about are appended, the same rule the entries follow.\n for (const id of group.members) if (!held.members.includes(id)) (held.members as string[]).push(id)\n }\n const present = new Set(entries.map((entry) => entry.id))\n\n return {\n version: DRAWING_DOCUMENT_VERSION,\n context: stored.context,\n revision: Math.max(stored.revision, mine.revision) + 1,\n entries,\n groups: groups.map((group) => ({ ...group, members: group.members.filter((id) => present.has(id)) })),\n tombstones,\n }\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null\n\nconst entryOf = (value: unknown): DrawingEntry | null => {\n if (!isRecord(value)) return null\n const { id, source, pane, type, group } = value\n if (typeof id !== 'string' || !id || typeof source !== 'string' || typeof pane !== 'string' || typeof type !== 'string') return null\n return { id, source, pane, type, state: value.state, ...(typeof group === 'string' ? { group } : {}) }\n}\n\nconst groupOf = (value: unknown): DrawingGroup | null => {\n if (!isRecord(value)) return null\n const { id, name, members } = value\n if (typeof id !== 'string' || !id || !Array.isArray(members)) return null\n return { id, ...(typeof name === 'string' ? { name } : {}), members: members.filter((m): m is string => typeof m === 'string') }\n}\n\n/** The context a payload states, when it states a whole one this build understands. */\nconst contextOf = (value: unknown): DrawingResourceContext | null => {\n if (!isRecord(value) || value.version !== DRAWING_CONTEXT_VERSION) return null\n const { kind, symbol, layoutId, chartId } = value\n if (typeof symbol !== 'string') return null\n if (kind === 'symbol-global') return { version: DRAWING_CONTEXT_VERSION, kind, symbol }\n if (typeof layoutId !== 'string') return null\n if (kind === 'layout-shared') return { version: DRAWING_CONTEXT_VERSION, kind, layoutId, symbol }\n if (kind === 'chart-local' && typeof chartId === 'string') return { version: DRAWING_CONTEXT_VERSION, kind, layoutId, chartId, symbol }\n return null\n}\n\nconst tombstoneOf = (value: unknown): DrawingTombstone | null => {\n if (!isRecord(value)) return null\n const { kind, id, at } = value\n if ((kind !== 'drawing' && kind !== 'group') || typeof id !== 'string' || !id) return null\n return { kind, id, at: typeof at === 'number' ? at : 0 }\n}\n\n/** Read whatever a transport handed back as a document for this context. TOTAL: a null, a string, a\n * wrong version, a junk row or a whole junk body reads as the empty document, never a throw, so a\n * host can hand the chart what its backend returned without guarding it first. The context is the\n * caller's, not the payload's: a document is identified by where it was read from, and a body\n * claiming another context is not merged into this one. */\nexport function parseDrawingDocument(value: unknown, context: DrawingResourceContext): DrawingsBody {\n const raw: unknown = typeof value === 'string' ? safeJson(value) : value\n if (!isRecord(raw) || raw.version !== DRAWING_DOCUMENT_VERSION) return emptyDrawingDocument(context)\n const stated = contextOf(raw.context)\n if (stated && !sameDrawingContext(stated, context)) return emptyDrawingDocument(context)\n return {\n version: DRAWING_DOCUMENT_VERSION,\n context,\n revision: typeof raw.revision === 'number' && Number.isFinite(raw.revision) ? raw.revision : 0,\n entries: Array.isArray(raw.entries) ? raw.entries.map(entryOf).filter((e): e is DrawingEntry => e !== null) : [],\n groups: Array.isArray(raw.groups) ? raw.groups.map(groupOf).filter((g): g is DrawingGroup => g !== null) : [],\n tombstones: Array.isArray(raw.tombstones) ? raw.tombstones.map(tombstoneOf).filter((t): t is DrawingTombstone => t !== null) : [],\n }\n}\n\nconst safeJson = (text: string): unknown => {\n try {\n return JSON.parse(text)\n } catch {\n return null\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/drawings/document.ts"],"sourcesContent":["// The drawings DOCUMENT: the pure rules over the drawing-resource contract's document shape.\n//\n// Nothing here touches a chart, a store or a clock. That is deliberate: what a document means when\n// two surfaces write it, what a deletion does to it, and which of its entries are live are exactly\n// the questions that go wrong in a persistence layer, and they are all decidable from two values.\n//\n// Three rules the module enforces:\n// - A deletion is a TOMBSTONE, never an omission. Two charts hold the same document; if a\n// deletion were just a shorter list, the other chart's copy would put the row back on the next\n// merge, and a drawing the viewer deleted would return after a save, a reconnect or a reload.\n// - A merge is not a last-write-wins overwrite. The stored document is the other surface's work\n// and wins by identity and by order; this surface's own additions follow it. Neither side\n// loses a row it made, and neither side resurrects a row the other buried.\n// - Deleting a GROUP deletes the grouping, not the drawings. The members survive, ungrouped, and\n// the group id can never come back.\n//\n// The document's own SHAPE lives here too, beside the rules, because the two only make sense\n// together: the resource contract in `resources.ts` names this the drawings family's body.\n\n/* ── The drawing-resource context ───────────────────────────────────────────────────────────────\n Where a drawings document lives, as a discriminated, versioned context rather than a pair of\n loose fields. The kind IS the sharing rule, so a reader never infers one from which fields\n happen to be set:\n\n chart-local one chart of one layout, on one symbol. Nothing is shared.\n layout-shared every chart of one layout, on one symbol.\n symbol-global every chart the host mounts, on one symbol.\n\n `version` is the context's own format, not the document's: a store keys its rows by the whole\n context, so a later kind or field arrives as a different key rather than as a silently widened\n old one. */\n\nexport const DRAWING_CONTEXT_VERSION = 1\n\nexport type DrawingContextKind = 'chart-local' | 'layout-shared' | 'symbol-global'\n\n/** One chart of one layout, on one symbol. */\nexport interface ChartLocalDrawingContext {\n version: typeof DRAWING_CONTEXT_VERSION\n kind: 'chart-local'\n /** The host's stable identity for the layout the chart is tiled in. */\n layoutId: string\n /** The chart's stable identity WITHIN that layout, so the document is found again after a\n * reload; a per-mount instance id would key a document nothing could ever read back. */\n chartId: string\n symbol: string\n}\n\n/** Every chart of one layout, on one symbol. */\nexport interface LayoutSharedDrawingContext {\n version: typeof DRAWING_CONTEXT_VERSION\n kind: 'layout-shared'\n layoutId: string\n symbol: string\n}\n\n/** Every chart the host mounts, on one symbol. */\nexport interface SymbolGlobalDrawingContext {\n version: typeof DRAWING_CONTEXT_VERSION\n kind: 'symbol-global'\n symbol: string\n}\n\nexport type DrawingResourceContext = ChartLocalDrawingContext | LayoutSharedDrawingContext | SymbolGlobalDrawingContext\n\n/** The drawings document format. Bumping this is the only reason a reader ever branches. */\nexport const DRAWING_DOCUMENT_VERSION = 1\n\n/** One drawing in a document. The ENVELOPE is structured so a restore is validated without parsing\n * the drawing itself: the id is stable for the drawing's life, `source` names what owns it (the\n * main series, or an indicator's own source), `pane` names where it is drawn, and `type` is its\n * tool. `state` is the drawing's own serialized form and is opaque to the document, the store and\n * the transport. */\nexport interface DrawingEntry {\n id: string\n /** The owning source: `main` for the chart's main series, else an indicator instance's id. */\n source: string\n /** The pane the drawing is drawn in: `main` for the price pane, else the pane owner's id. */\n pane: string\n type: string\n /** Opaque: nothing between the layer and the backend reads it. */\n state: unknown\n /** The group this drawing belongs to, when it belongs to one. */\n group?: string\n}\n\n/** A group of drawings. Membership is ORDERED and holds drawing ids, so a group survives a round\n * trip with its order intact rather than being rebuilt from however a document happened to list\n * its entries. */\nexport interface DrawingGroup {\n id: string\n name?: string\n members: readonly string[]\n}\n\n/** A deletion, kept. A document that listed only what survives lets a concurrent save, a reconnect\n * or a reload resurrect what the viewer deleted: the other side still holds the row and a merge\n * takes it back. A tombstone says the deletion happened, and at which document revision, so the\n * merge drops it instead. */\nexport interface DrawingTombstone {\n kind: 'drawing' | 'group'\n id: string\n /** The document revision the deletion was stamped at. */\n at: number\n}\n\n/** One context's drawings document. `revision` is the DOCUMENT's own counter, which orders\n * tombstones and detects a stale apply; it is not the store's `ResourceRef.revision`, which stays\n * opaque and is the store's alone. */\nexport interface DrawingsBody {\n version: typeof DRAWING_DOCUMENT_VERSION\n context: DrawingResourceContext\n revision: number\n entries: readonly DrawingEntry[]\n groups: readonly DrawingGroup[]\n tombstones: readonly DrawingTombstone[]\n}\n\n\n/** The canonical key for a drawing-resource context: equal contexts key equally, and no two\n * different contexts collide. The parts join on an escaped null, which no symbol, layout id or\n * chart id carries, so a symbol ending in a separator cannot spell another context's key. The\n * kind and version lead, so a later kind is a different key rather than a widened old one. */\nexport function drawingContextKey(context: DrawingResourceContext): string {\n const parts: string[] = [String(context.version), context.kind]\n if (context.kind === 'chart-local') parts.push(context.layoutId, context.chartId)\n else if (context.kind === 'layout-shared') parts.push(context.layoutId)\n parts.push(context.symbol)\n return parts.join('\\u0000')\n}\n\n/** Whether two contexts name the same document. */\nexport const sameDrawingContext = (a: DrawingResourceContext, b: DrawingResourceContext): boolean => drawingContextKey(a) === drawingContextKey(b)\n\n/** A document with nothing in it, at revision 0: what a context that has never been written reads\n * as, so every caller works over a document rather than over a null. */\nexport function emptyDrawingDocument(context: DrawingResourceContext): DrawingsBody {\n return { version: DRAWING_DOCUMENT_VERSION, context, revision: 0, entries: [], groups: [], tombstones: [] }\n}\n\nconst tombstoneKey = (kind: DrawingTombstone['kind'], id: string): string => `${kind}\\u0000${id}`\n\n/** The tombstones of both documents, one per (kind, id), each at the later revision it was seen at. */\nfunction mergeTombstones(a: readonly DrawingTombstone[], b: readonly DrawingTombstone[]): DrawingTombstone[] {\n const byKey = new Map<string, DrawingTombstone>()\n for (const stone of [...a, ...b]) {\n const key = tombstoneKey(stone.kind, stone.id)\n const held = byKey.get(key)\n if (!held || stone.at > held.at) byKey.set(key, { kind: stone.kind, id: stone.id, at: stone.at })\n }\n return [...byKey.values()]\n}\n\nconst buriedSet = (tombstones: readonly DrawingTombstone[], kind: DrawingTombstone['kind']): Set<string> =>\n new Set(tombstones.filter((stone) => stone.kind === kind).map((stone) => stone.id))\n\n/** Whether a document buries this id. Package-internal, beside the rules it serves: the layer asks\n * it to tell a live entry from one whose group is gone. */\nexport const drawingBuried = (document: DrawingsBody, kind: DrawingTombstone['kind'], id: string): boolean =>\n document.tombstones.some((stone) => stone.kind === kind && stone.id === id)\n\n/** The entries a document actually holds: nothing buried, no duplicate id, and no membership of a\n * group that is gone (the drawing survives, its grouping does not). */\nexport function liveDrawingEntries(document: DrawingsBody): DrawingEntry[] {\n const buriedDrawings = buriedSet(document.tombstones, 'drawing')\n const groups = new Set(liveDrawingGroups(document).map((group) => group.id))\n const out: DrawingEntry[] = []\n const seen = new Set<string>()\n for (const entry of document.entries) {\n if (buriedDrawings.has(entry.id) || seen.has(entry.id)) continue\n seen.add(entry.id)\n out.push(entry.group !== undefined && !groups.has(entry.group) ? omitGroup(entry) : entry)\n }\n return out\n}\n\nconst omitGroup = (entry: DrawingEntry): DrawingEntry => {\n const { group: _buried, ...rest } = entry\n return rest\n}\n\n/** The groups a document actually holds: nothing buried, no duplicate id, and no member that is\n * buried or absent, so a group never names a drawing that is not there. */\nexport function liveDrawingGroups(document: DrawingsBody): DrawingGroup[] {\n const buriedGroups = buriedSet(document.tombstones, 'group')\n const buriedDrawings = buriedSet(document.tombstones, 'drawing')\n const present = new Set(document.entries.filter((entry) => !buriedDrawings.has(entry.id)).map((entry) => entry.id))\n const out: DrawingGroup[] = []\n const seen = new Set<string>()\n for (const group of document.groups) {\n if (buriedGroups.has(group.id) || seen.has(group.id)) continue\n seen.add(group.id)\n out.push({ ...group, members: group.members.filter((id) => present.has(id)) })\n }\n return out\n}\n\n/** Replace what a document holds and stamp the difference. Every id the document held and the next\n * state does not is buried at the new revision; every id that arrives is kept in the order it\n * arrives in. One verb, one revision bump, so a save can never write entries at one revision and\n * groups at another. */\nexport function reviseDrawingDocument(\n document: DrawingsBody,\n next: { entries?: readonly DrawingEntry[]; groups?: readonly DrawingGroup[] },\n): DrawingsBody {\n const revision = document.revision + 1\n const entries = next.entries ?? liveDrawingEntries(document)\n const groups = next.groups ?? liveDrawingGroups(document)\n const keptDrawings = new Set(entries.map((entry) => entry.id))\n const keptGroups = new Set(groups.map((group) => group.id))\n const buried: DrawingTombstone[] = []\n for (const entry of liveDrawingEntries(document)) if (!keptDrawings.has(entry.id)) buried.push({ kind: 'drawing', id: entry.id, at: revision })\n for (const group of liveDrawingGroups(document)) if (!keptGroups.has(group.id)) buried.push({ kind: 'group', id: group.id, at: revision })\n return {\n version: DRAWING_DOCUMENT_VERSION,\n context: document.context,\n revision,\n entries: [...entries],\n groups: [...groups],\n tombstones: mergeTombstones(document.tombstones, buried),\n }\n}\n\n/** Merge the document another surface wrote first with this surface's own. The stored rows win by\n * identity and keep the stored order, because they are what everyone else can already see; rows\n * only this surface holds follow, because they are work the other surface never saw. Every\n * deletion either side made survives the merge. The result's revision is past both, so the write\n * that carries it is the newer document by either side's counter. */\nexport function mergeDrawingDocuments(stored: DrawingsBody, mine: DrawingsBody): DrawingsBody {\n const tombstones = mergeTombstones(stored.tombstones, mine.tombstones)\n const buriedDrawings = buriedSet(tombstones, 'drawing')\n const buriedGroups = buriedSet(tombstones, 'group')\n\n const entries: DrawingEntry[] = []\n const seen = new Set<string>()\n for (const entry of [...stored.entries, ...mine.entries]) {\n if (seen.has(entry.id) || buriedDrawings.has(entry.id)) continue\n seen.add(entry.id)\n entries.push(entry.group !== undefined && buriedGroups.has(entry.group) ? omitGroup(entry) : entry)\n }\n\n const groups: DrawingGroup[] = []\n const byId = new Map<string, DrawingGroup>()\n for (const group of [...stored.groups, ...mine.groups]) {\n if (buriedGroups.has(group.id)) continue\n const held = byId.get(group.id)\n if (!held) {\n const row = { ...group, members: [...group.members] }\n byId.set(group.id, row)\n groups.push(row)\n continue\n }\n // One group written by both surfaces: the stored order stands and the members only this\n // surface knows about are appended, the same rule the entries follow.\n for (const id of group.members) if (!held.members.includes(id)) (held.members as string[]).push(id)\n }\n const present = new Set(entries.map((entry) => entry.id))\n\n return {\n version: DRAWING_DOCUMENT_VERSION,\n context: stored.context,\n revision: Math.max(stored.revision, mine.revision) + 1,\n entries,\n groups: groups.map((group) => ({ ...group, members: group.members.filter((id) => present.has(id)) })),\n tombstones,\n }\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null\n\nconst entryOf = (value: unknown): DrawingEntry | null => {\n if (!isRecord(value)) return null\n const { id, source, pane, type, group } = value\n if (typeof id !== 'string' || !id || typeof source !== 'string' || typeof pane !== 'string' || typeof type !== 'string') return null\n return { id, source, pane, type, state: value.state, ...(typeof group === 'string' ? { group } : {}) }\n}\n\nconst groupOf = (value: unknown): DrawingGroup | null => {\n if (!isRecord(value)) return null\n const { id, name, members } = value\n if (typeof id !== 'string' || !id || !Array.isArray(members)) return null\n return { id, ...(typeof name === 'string' ? { name } : {}), members: members.filter((m): m is string => typeof m === 'string') }\n}\n\n/** The context a payload states, when it states a whole one this build understands. */\nconst contextOf = (value: unknown): DrawingResourceContext | null => {\n if (!isRecord(value) || value.version !== DRAWING_CONTEXT_VERSION) return null\n const { kind, symbol, layoutId, chartId } = value\n if (typeof symbol !== 'string') return null\n if (kind === 'symbol-global') return { version: DRAWING_CONTEXT_VERSION, kind, symbol }\n if (typeof layoutId !== 'string') return null\n if (kind === 'layout-shared') return { version: DRAWING_CONTEXT_VERSION, kind, layoutId, symbol }\n if (kind === 'chart-local' && typeof chartId === 'string') return { version: DRAWING_CONTEXT_VERSION, kind, layoutId, chartId, symbol }\n return null\n}\n\nconst tombstoneOf = (value: unknown): DrawingTombstone | null => {\n if (!isRecord(value)) return null\n const { kind, id, at } = value\n if ((kind !== 'drawing' && kind !== 'group') || typeof id !== 'string' || !id) return null\n return { kind, id, at: typeof at === 'number' ? at : 0 }\n}\n\n/** Read whatever a transport handed back as a document for this context. TOTAL: a null, a string, a\n * wrong version, a junk row or a whole junk body reads as the empty document, never a throw, so a\n * host can hand the chart what its backend returned without guarding it first. The context is the\n * caller's, not the payload's: a document is identified by where it was read from, and a body\n * claiming another context is not merged into this one. */\nexport function parseDrawingDocument(value: unknown, context: DrawingResourceContext): DrawingsBody {\n const raw: unknown = typeof value === 'string' ? safeJson(value) : value\n if (!isRecord(raw) || raw.version !== DRAWING_DOCUMENT_VERSION) return emptyDrawingDocument(context)\n const stated = contextOf(raw.context)\n if (stated && !sameDrawingContext(stated, context)) return emptyDrawingDocument(context)\n return {\n version: DRAWING_DOCUMENT_VERSION,\n context,\n revision: typeof raw.revision === 'number' && Number.isFinite(raw.revision) ? raw.revision : 0,\n entries: Array.isArray(raw.entries) ? raw.entries.map(entryOf).filter((e): e is DrawingEntry => e !== null) : [],\n groups: Array.isArray(raw.groups) ? raw.groups.map(groupOf).filter((g): g is DrawingGroup => g !== null) : [],\n tombstones: Array.isArray(raw.tombstones) ? raw.tombstones.map(tombstoneOf).filter((t): t is DrawingTombstone => t !== null) : [],\n }\n}\n\nconst safeJson = (text: string): unknown => {\n try {\n return JSON.parse(text)\n } catch {\n return null\n }\n}\n"],"mappings":"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","names":[]}
@@ -46,7 +46,7 @@ function visibilityPreset(kind, ctx) {
46
46
  }
47
47
  return out;
48
48
  }
49
- function parseIntervalContext(tf) {
49
+ function parseTimeframeContext(tf) {
50
50
  const match = /^(\d+)(t|s|m|h|d|w|mo)$/.exec(tf.trim().toLowerCase());
51
51
  if (!match) return null;
52
52
  let value = Number(match[1]);
@@ -340,9 +340,9 @@ function viewportOf(chart, series) {
340
340
  const firstTime = data.length > 0 ? numTime(0) : null;
341
341
  const lastTime = data.length > 0 ? numTime(lastIndex) : null;
342
342
  const prevTime = numTime(lastIndex - 1);
343
- const trailInterval = lastTime !== null && prevTime !== null && lastTime > prevTime ? lastTime - prevTime : null;
343
+ const trailStep = lastTime !== null && prevTime !== null && lastTime > prevTime ? lastTime - prevTime : null;
344
344
  const secondTime = numTime(1);
345
- const leadInterval = firstTime !== null && secondTime !== null && secondTime > firstTime ? secondTime - firstTime : null;
345
+ const leadStep = firstTime !== null && secondTime !== null && secondTime > firstTime ? secondTime - firstTime : null;
346
346
  const logicalOfTime = (time) => {
347
347
  const x = ts.timeToCoordinate(time);
348
348
  if (x !== null) {
@@ -350,8 +350,8 @@ function viewportOf(chart, series) {
350
350
  if (logical !== null) return logical;
351
351
  }
352
352
  if (typeof time !== "number" || firstTime === null || lastTime === null) return null;
353
- if (time >= lastTime) return trailInterval === null ? lastIndex : lastIndex + (time - lastTime) / trailInterval;
354
- if (time <= firstTime) return leadInterval === null ? 0 : (time - firstTime) / leadInterval;
353
+ if (time >= lastTime) return trailStep === null ? lastIndex : lastIndex + (time - lastTime) / trailStep;
354
+ if (time <= firstTime) return leadStep === null ? 0 : (time - firstTime) / leadStep;
355
355
  let lo = 0;
356
356
  let hi = lastIndex;
357
357
  while (hi - lo > 1) {
@@ -368,8 +368,8 @@ function viewportOf(chart, series) {
368
368
  };
369
369
  const timeOfLogicalIndex = (logical) => {
370
370
  if (firstTime === null || lastTime === null) return null;
371
- if (logical >= lastIndex) return trailInterval === null ? lastTime : lastTime + (logical - lastIndex) * trailInterval;
372
- if (logical <= 0) return leadInterval === null ? firstTime : firstTime + logical * leadInterval;
371
+ if (logical >= lastIndex) return trailStep === null ? lastTime : lastTime + (logical - lastIndex) * trailStep;
372
+ if (logical <= 0) return leadStep === null ? firstTime : firstTime + logical * leadStep;
373
373
  const i = Math.floor(logical);
374
374
  const tLo = numTime(i);
375
375
  const tHi = numTime(i + 1);
@@ -422,7 +422,7 @@ var Drawing = class {
422
422
  _options;
423
423
  _props;
424
424
  _state = "normal";
425
- _intervalContext = null;
425
+ _timeframeContext = null;
426
426
  _globalHidden = false;
427
427
  _barSource = null;
428
428
  _chart = null;
@@ -534,8 +534,8 @@ var Drawing = class {
534
534
  };
535
535
  this.requestUpdate();
536
536
  }
537
- setIntervalContext(context) {
538
- this._intervalContext = context;
537
+ setTimeframeContext(context) {
538
+ this._timeframeContext = context;
539
539
  this.requestUpdate();
540
540
  }
541
541
  /** Chart-wide hide-all switch (transient view state — never serialized). */
@@ -583,7 +583,7 @@ var Drawing = class {
583
583
  return this._glyphSource ? this._glyphSource(glyph) : null;
584
584
  }
585
585
  isVisibleNow() {
586
- return !this._globalHidden && this._options.visible && visibleAt(this._options.visibility, this._intervalContext);
586
+ return !this._globalHidden && this._options.visible && visibleAt(this._options.visibility, this._timeframeContext);
587
587
  }
588
588
  applyProps(patch) {
589
589
  this._props = { ...this._props, ...patch };
@@ -746,7 +746,7 @@ var DrawingManager = class {
746
746
  _selectedId = null;
747
747
  _chart = null;
748
748
  _series = null;
749
- _intervalContext = null;
749
+ _timeframeContext = null;
750
750
  _allHidden = false;
751
751
  _barSource = null;
752
752
  _tickSize = null;
@@ -775,10 +775,10 @@ var DrawingManager = class {
775
775
  this._glyphSource = source;
776
776
  for (const drawing of this._drawings.values()) drawing.setGlyphSource(source);
777
777
  }
778
- /** Broadcast the chart's interval so per-interval visibility rules apply. */
779
- setIntervalContext(context) {
780
- this._intervalContext = context;
781
- for (const drawing of this._drawings.values()) drawing.setIntervalContext(context);
778
+ /** Broadcast the chart's timeframe so per-timeframe visibility rules apply. */
779
+ setTimeframeContext(context) {
780
+ this._timeframeContext = context;
781
+ for (const drawing of this._drawings.values()) drawing.setTimeframeContext(context);
782
782
  }
783
783
  /** Chart-wide hide-all: every drawing (and its axis pills) disappears until toggled back. */
784
784
  setAllHidden(hidden) {
@@ -816,7 +816,7 @@ var DrawingManager = class {
816
816
  add(drawing) {
817
817
  if (this._drawings.has(drawing.id)) return;
818
818
  const concrete = drawing;
819
- concrete.setIntervalContext(this._intervalContext);
819
+ concrete.setTimeframeContext(this._timeframeContext);
820
820
  concrete.setGlobalHidden(this._allHidden);
821
821
  concrete.setBarSource(this._barSource);
822
822
  concrete.setTickSize(this._tickSize);
@@ -1691,6 +1691,14 @@ var Ellipse = class extends Drawing {
1691
1691
  ctx.stroke();
1692
1692
  if (this.props.text) paintLabel(ctx, this.props.text, geo.center, this.style, { align: "center" });
1693
1693
  }
1694
+ /** The third handle stands on the ellipse at its centre's time, where the vertical radius it
1695
+ * drags meets the outline: the anchor's own time paints nothing, so a handle at it would stay
1696
+ * behind as the diameter moves. */
1697
+ getControlPoints(viewport) {
1698
+ const points = super.getControlPoints(viewport);
1699
+ const geo = this.geometry(viewport);
1700
+ return geo ? points.map((point) => point.index === 2 ? { ...point, x: geo.center.x } : point) : points;
1701
+ }
1694
1702
  testHit(point, viewport) {
1695
1703
  const geo = this.geometry(viewport);
1696
1704
  if (!geo) return false;
@@ -2676,6 +2684,14 @@ var FlatTopBottom = class extends Drawing {
2676
2684
  strokeSegment(ctx, b.flat.a, b.flat.b);
2677
2685
  if (this.props.text) paintLabel(ctx, this.props.text, { x: b.slope.a.x + 6, y: b.slope.a.y - 12 }, this.style);
2678
2686
  }
2687
+ /** The flat side's handle stands at the flat side's end under the second anchor: the flat side
2688
+ * spans the sloped side's times at the third anchor's price, and that anchor's own time paints
2689
+ * nothing, so a handle at it would drift off the side as the sloped side moves. */
2690
+ getControlPoints(viewport) {
2691
+ const points = super.getControlPoints(viewport);
2692
+ const end = points.find((point) => point.index === 1);
2693
+ return end ? points.map((point) => point.index === 2 ? { ...point, x: end.x } : point) : points;
2694
+ }
2679
2695
  testHit(point, viewport) {
2680
2696
  const b = this.boundaries(viewport);
2681
2697
  if (!b) return false;
@@ -3458,6 +3474,16 @@ var FibWedge = class extends Drawing {
3458
3474
  }
3459
3475
  }
3460
3476
  }
3477
+ /** The third handle stands at the end of the second ray: that ray runs as long as the first, at
3478
+ * the third anchor's angle, so the anchor's own distance from the apex paints nothing and a
3479
+ * handle at it would float off the ray's end as the first ray changes length. */
3480
+ getControlPoints(viewport) {
3481
+ const points = super.getControlPoints(viewport);
3482
+ const geo = this.geometry(viewport);
3483
+ if (!geo) return points;
3484
+ const end = { x: geo.apex.x + Math.cos(geo.a2) * geo.radius, y: geo.apex.y + Math.sin(geo.a2) * geo.radius };
3485
+ return points.map((point) => point.index === 2 ? { ...point, ...end } : point);
3486
+ }
3461
3487
  testHit(point, viewport) {
3462
3488
  const geo = this.geometry(viewport);
3463
3489
  if (!geo) return false;
@@ -3755,6 +3781,14 @@ var LongPosition = class extends Drawing {
3755
3781
  paintLabel(ctx, this.formatPrice(entry.price), { x: z.right + 6, y: z.entryY }, { ...this.style, textColor: this.style.lineColor });
3756
3782
  }
3757
3783
  }
3784
+ /** The stop's handle stands on the box's far edge beside the target's, where the stop's level is
3785
+ * painted: the box's edges are the entry's time and the target's, and the stop's own time paints
3786
+ * nothing, so a handle at it would stay behind as the box's width changes. */
3787
+ getControlPoints(viewport) {
3788
+ const points = super.getControlPoints(viewport);
3789
+ const edge = points.find((point) => point.index === 1);
3790
+ return edge ? points.map((point) => point.index === 2 ? { ...point, x: edge.x } : point) : points;
3791
+ }
3758
3792
  testHit(point, viewport) {
3759
3793
  const z = this.zones(viewport);
3760
3794
  if (!z) return false;
@@ -5752,12 +5786,12 @@ function chooseMagnetStrength(mode, strength) {
5752
5786
  }
5753
5787
  var magnetActive = (mode) => mode !== "off";
5754
5788
 
5755
- // src/drawings/railModel.ts
5789
+ // src/drawings/drawingToolbarModel.ts
5756
5790
  var ARROW_TYPES = ["arrow_marker", "arrow", "arrow_up", "arrow_down"];
5757
5791
  var BRUSH_TYPES = ["brush", "highlighter"];
5758
5792
  var GLYPH_TYPES = ["emoji", "sticker", "icon"];
5759
5793
  var CARD_TYPES = ["image", "content_card"];
5760
- var RAIL_PLAN = [
5794
+ var DRAWING_TOOLBAR_PLAN = [
5761
5795
  {
5762
5796
  id: "trend",
5763
5797
  label: "drawing.groupTrend",
@@ -5818,9 +5852,9 @@ var RAIL_PLAN = [
5818
5852
  }
5819
5853
  ];
5820
5854
  var isTypeSection = (section) => "types" in section;
5821
- function buildRailGroups(catalog2 = drawingTools) {
5855
+ function buildDrawingToolbarGroups(catalog2 = drawingTools) {
5822
5856
  const toolsOf = (section) => isTypeSection(section) ? section.types.map((type) => catalog2.get(type)).filter((tool2) => !!tool2) : catalog2.byCategory(section.category).filter((tool2) => !(section.exclude ?? []).includes(tool2.type));
5823
- return RAIL_PLAN.map((group) => ({
5857
+ return DRAWING_TOOLBAR_PLAN.map((group) => ({
5824
5858
  id: group.id,
5825
5859
  label: group.label,
5826
5860
  sections: group.sections.map((section) => ({ label: section.label, tools: toolsOf(section) })).filter((section) => section.tools.length > 0)
@@ -5830,12 +5864,12 @@ function groupOfTool(groups, type) {
5830
5864
  if (!type) return null;
5831
5865
  return groups.find((group) => group.sections.some((section) => section.tools.some((tool2) => tool2.type === type)))?.id ?? null;
5832
5866
  }
5833
- function railFaceOf(group, lastTools) {
5867
+ function drawingToolbarFaceOf(group, lastTools) {
5834
5868
  const remembered = lastTools[group.id];
5835
5869
  if (remembered && group.sections.some((section) => section.tools.some((tool2) => tool2.type === remembered))) return remembered;
5836
5870
  return group.sections[0]?.tools[0]?.type ?? null;
5837
5871
  }
5838
- function rememberRailTool(groups, lastTools, type) {
5872
+ function rememberDrawingToolbarTool(groups, lastTools, type) {
5839
5873
  const group = groupOfTool(groups, type);
5840
5874
  if (!group) return { ...lastTools };
5841
5875
  return { ...lastTools, [group]: type };
@@ -6565,7 +6599,7 @@ var DEFAULT_DRAWING_PREFERENCES = {
6565
6599
  stayInDrawingMode: false,
6566
6600
  removeLocked: false,
6567
6601
  syncAcrossPanes: true,
6568
- railTools: {},
6602
+ drawingToolbarTools: {},
6569
6603
  favorites: DEFAULT_FAVORITES,
6570
6604
  settingsBarPosition: null,
6571
6605
  recentGlyphs: [],
@@ -6607,7 +6641,7 @@ function parseDrawingPreferences(raw) {
6607
6641
  stayInDrawingMode: bool(value.stayInDrawingMode, DEFAULT_DRAWING_PREFERENCES.stayInDrawingMode),
6608
6642
  removeLocked: bool(value.removeLocked, DEFAULT_DRAWING_PREFERENCES.removeLocked),
6609
6643
  syncAcrossPanes: bool(value.syncAcrossPanes, DEFAULT_DRAWING_PREFERENCES.syncAcrossPanes),
6610
- railTools: stringRecord(value.railTools),
6644
+ drawingToolbarTools: stringRecord(value.drawingToolbarTools),
6611
6645
  favorites: favoritesOf(value.favorites),
6612
6646
  settingsBarPosition: positionOf(value.settingsBarPosition),
6613
6647
  recentGlyphs: Array.isArray(value.recentGlyphs) ? value.recentGlyphs.filter((g) => typeof g === "string").slice(0, RECENT_GLYPHS_MAX) : [],
@@ -6650,7 +6684,7 @@ var DrawingTemplates = class {
6650
6684
  }
6651
6685
  /** Every row of the family, defaults included, each with its preset. A host that has to answer
6652
6686
  * synchronously (a placement path, a render) reads this once into a cache of its own rather
6653
- * fetched: one read per saved setup, once, against a family a trader keeps in the dozens. The
6687
+ * fetched: one read per saved setup, once, against a family a viewer keeps in the dozens. The
6654
6688
  * reads run together, `BODY_READS` at a time, and the answer keeps the store's order. */
6655
6689
  async listAll(signal) {
6656
6690
  const rows = await this.store.list(signal);
@@ -6701,7 +6735,7 @@ var DrawingTemplates = class {
6701
6735
  return found ? decodePreset(found.body.content) : {};
6702
6736
  }
6703
6737
  /** Remember a tool's default. This runs after an ordinary style edit, so a CONFLICT here is not
6704
- * worth surfacing: another surface already wrote a newer default and the trader's next edit
6738
+ * worth surfacing: another surface already wrote a newer default and the viewer's next edit
6705
6739
  * writes again. It is returned rather than swallowed so a caller may still look. */
6706
6740
  async rememberDefault(tool2, preset, signal) {
6707
6741
  return this.save(tool2, DEFAULT_PRESET_NAME, preset, signal);
@@ -6849,7 +6883,7 @@ export {
6849
6883
  parseCssColor,
6850
6884
  DEFAULT_VISIBILITY,
6851
6885
  visibilityPreset,
6852
- parseIntervalContext,
6886
+ parseTimeframeContext,
6853
6887
  normalizeVisibility,
6854
6888
  visibleAt,
6855
6889
  DEFAULT_STYLE,
@@ -6894,11 +6928,11 @@ export {
6894
6928
  BRUSH_TYPES,
6895
6929
  GLYPH_TYPES,
6896
6930
  CARD_TYPES,
6897
- RAIL_PLAN,
6898
- buildRailGroups,
6931
+ DRAWING_TOOLBAR_PLAN,
6932
+ buildDrawingToolbarGroups,
6899
6933
  groupOfTool,
6900
- railFaceOf,
6901
- rememberRailTool,
6934
+ drawingToolbarFaceOf,
6935
+ rememberDrawingToolbarTool,
6902
6936
  CHART_HIDE_LAYERS,
6903
6937
  hideOrder,
6904
6938
  DEFAULT_HIDE_STATE,
@@ -6960,4 +6994,4 @@ export {
6960
6994
  NO_DASH,
6961
6995
  INPUT_PROPS
6962
6996
  };
6963
- //# sourceMappingURL=chunk-AA7XKXH4.js.map
6997
+ //# sourceMappingURL=chunk-YR7NT5ZW.js.map