[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M69AXPYV1F85RNSJM8FC9SXN":3,"skill-same:01M69AXPYV1F85RNSJM8FC9SXN":636},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":26,"publishedAt":31,"linkOnly":32,"markdownContent":33,"markdownBody":34,"frontMatter":35,"refName":36,"commitSha":37,"skillDirectory":38,"forksCount":39,"repositoryHtmlUrl":40,"repositoryHomepageUrl":41,"ownerHtmlUrl":42,"ownerBlogUrl":43,"ownerDescription":44,"updatedAt":45,"install":46,"resources":59},"01M69AXPYV1F85RNSJM8FC9SXN","flint-interaction-author",null,"Use when: a Flint chart should respond to the reader (highlight, zoom, brush, legend toggle, reorder, annotate, explore, link charts); an application or an agent changes a mounted chart from outside or reads what it shows; or a gesture no preset gives must be built. Produce one valid interaction_spec, one ChartUpdate, or code against flint-chart\u002Finteractive, with no invented names.","microsoft\u002Fflint-chart","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M69AXB6SP1R3N8FK0Y1CNSDW",4571,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002Fagent-skills\u002Fflint-interaction-author\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000013","分析与可视化","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[27,28,29,30],"flint interaction author","spec","valid","preset","2026-11-10 10:12:30",false,"---\nname: flint-interaction-author\ndescription: \"Use when: a Flint chart should respond to the reader (highlight, zoom, brush, legend toggle, reorder, annotate, explore, link charts); an application or an agent changes a mounted chart from outside or reads what it shows; or a gesture no preset gives must be built. Produce one valid interaction_spec, one ChartUpdate, or code against flint-chart\u002Finteractive, with no invented names.\"\n---\n\n# flint-chart: interactions and updates on a chart\n\nA Flint chart is shared state. A reader's gesture becomes an event, a handler\nturns the event into an update `{ id, ops }`, updates compose as layers, and\nthe layers render into a state the host and the agent read back. An\napplication and an agent write the same kind of update directly. The full\nmodel is in the documentation:\nhttps:\u002F\u002Fmicrosoft.github.io\u002Fflint-chart\u002F#\u002Fdocumentation\u002Finteraction-introduction\n\nThe chart itself (`chart_spec`, `semantic_types`) is the `flint-chart-author`\nskill. Everything here runs on the Vega-Lite backend; a static render and the\nother backends leave `interaction_spec` and updates untouched.\n\n## What you produce (and what you do NOT)\n\n| The request says | You produce | Section |\n| --- | --- | --- |\n| A gesture or a behaviour on the chart: \"highlight on click\", \"zoom\", \"brush a period\", \"make it interactive\", \"add behaviour to this chart\". | One `interaction_spec`: `{ \"interactions\": [ ... ] }`, presets only. | Add interactions to the chart |\n| A change to a chart that is already on screen, from outside: \"point out Japan\", \"frame 2010 to 2020\", \"note this value\", \"reorder by value\", an agent answering a user with the chart. | One `ChartUpdate`: `{ \"id\", \"ops\": [ ... ] }`. | Update the chart |\n| Code around a chart: \"in my app\", \"on click open the record\", \"a panel that follows the hover\". | TypeScript against `flint-chart\u002Finteractive`: mount, presets as factories, `onChange`, `dispatch`. | Use Flint in an application |\n| Code across charts or over time: \"link these two charts\", \"a table that follows the selection\", \"play the years\". | `linkCharts` or `useLinkedCharts`; `dispatch` on a timer. On an MCP board, `link_spec` instead (board skill, \"Step 5 — connect them\"). | Link charts |\n| A gesture no preset gives: \"drag along the path\", \"draw a guess\", \"a rule for the box\", \"a lens the app draws\". | A `CanvasInteractionDef`: a trigger, affordances, a handler. | When no preset fits |\n\n- **DO** use only the names in this skill: preset types, options, ops, states,\n  triggers, cursors, event actions. Every name here is one the library ships.\n  When the request asks for a behaviour no preset gives, say which preset\n  comes closest and what it leaves out, then offer a bespoke definition if the\n  host runs code.\n- **DO** add behaviour only when the request or the session asks for it. A\n  static image or a bare spec gets no `interaction_spec`.\n\n## Which workflow the user is asking for\n\n- **Presets through the MCP tools:** put the object beside `chart_spec` as\n  `interaction_spec` in the chart input; `validate_chart` reports its warnings\n  without drawing, and `show` draws it with the same report. `list_interaction_presets`\n  with `chartType` lists the presets that chart type supports, what each\n  does, and its options.\n- **An update through the MCP tools:** there is no handle on the chart. Pass\n  the layers as the `updates` argument beside the input, to `validate_chart`\n  and `show`; see \"Step 4 — send the layer and read the result\".\n- **Code, only when the user asks for code:** mount with `mountChart`, or\n  `\u003CFlintChart>` in React; presets as factory calls, updates with\n  `applyUpdate`, state with `getState` and `onChange`. The programming API\n  is documented at\n  https:\u002F\u002Fmicrosoft.github.io\u002Fflint-chart\u002F#\u002Fdocumentation\u002Finteraction-api\n- **A bespoke interaction** needs a host that runs code. In the MCP view, the\n  answer is the closest preset and what it leaves out.\n\n## Add interactions to the chart\n\n### Step 1 — read the request and the chart\n\nThree things in the request decide the entries, read in this order:\n\n1. **What the user named.** A gesture or a preset in their words: \"brush a\n   period\", \"zoom\", \"toggle the legend\", \"highlight on click\". Each is one job.\n2. **The goal they stated.** \"Explore\", \"compare the regions\", \"find\n   outliers\", \"present this\", \"a control in my app\". A goal is the set of\n   questions the reader will ask of this chart; each question is one job.\n3. **Nothing named**, only \"interactive\", a live view, a dashboard. The chart\n   and the data say what the reader can do with it.\n\nThen read the chart spec: the chart type, the field on each channel, and the\nshape of the data. What a chart invites a reader to do:\n\n| The chart | Invites |\n| --- | --- |\n| A time series, one or many series | Comparing the series at one x; focusing a period; hiding a series when a colour field is bound; moving along the series when it is long. |\n| A scatter or a dense point chart | Focusing a cluster; moving closer; hiding a series when a colour field is bound. |\n| A bar, lollipop, or other category chart | Singling out a category; ranking by hand; hiding a series when a colour field is bound. |\n| A pie, donut, rose, or radar | Singling out a sector; focusing adjacent sectors. |\n| A heatmap or a calendar | Singling out a cell; reading a whole row or column. |\n| A map | Singling out a region; moving closer. |\n| A chart that is a control in an app | A click the app reads as a selection; a stronger gesture the app reads as an activation. |\n| A chart read without a pointer | Walking the chart from the keyboard. |\n\n### Step 2 — pick presets\n\nBefore any entry, a mounted chart already shows a tooltip with the mark's\nvalues on hover and nothing else. An entry adds one behaviour.\n\nTake one row of the map per job. A named gesture takes the row whose\n\"Gesture\" is that gesture. A question takes the row whose \"Answers\" is that\nquestion, on a chart whose \"Needs\" it offers; a broad goal takes two or three\nrows, the ones the chart invites most. When nothing is named, the rows the\nchart invites, two or three. A row that answers no job on the request stays\nout.\n\n| Preset | Gesture | Answers | Needs | Default reset |\n| --- | --- | --- | --- | --- |\n| **Single one thing out** | | | | |\n| `click-highlight` | Clicks a mark, a legend item, or an axis label; it stays emphasised and the rest mute. Shift-click adds. | Which mark, series, or category is this one? | elements | click-none, escape |\n| `click-group-focus` | Clicks a mark; every mark in its group stays emphasised. `groupBy` names the group field. | Which marks belong with this one? | elements | click-none, escape |\n| `hover-group-focus` | Hovers a mark; its group stands out until the pointer leaves. `groupBy` names the group field; `emphasis` is `\"auto\"` (a line widens and the rest stay as drawn; points, bars, and slices dim the rest), `\"highlight\"`, or `\"spotlight\"`. | Which marks belong with this one, at a glance? | elements | none |\n| `axis-highlight` | Clicks (or with `event: \"hover\"`, hovers) a discrete axis label; that category's marks emphasise. | Which marks share this row or column? | discrete axis | click-none, escape |\n| **Read values** | | | | |\n| `inspect` | Moves over the plot; a guide follows the pointer and the nearest mark on the chosen axes reads out. `mode` picks the axes and a comparison (`\"x>\"` reads every mark past the pointer). | What is at this x, this y, or beyond it? | elements | none |\n| `inspect-index` | Moves along the index axis; every series reads out at that position on one guide, each value labelled in its series colour and the position named at the axis. `show: \"single\"` reads one series and switches it through the legend; `seriesBy` names the series field when the chart draws one series at a time; `displayValue: false` keeps the guides and drops the labels. | What does each series hold at this point? | index axis | escape |\n| **Focus a region** | | | | |\n| `select` | Drags a rectangle; the marks inside emphasise. `mode: \"stateful\"` keeps the rectangle on screen to move and resize. | Which marks lie in this area? | elements, cartesian region | click-none, escape |\n| `lasso-select` | Draws a freehand region; the marks inside emphasise. | Which marks lie in this irregular cluster? | elements, cartesian region | click-none, escape |\n| `brush-x`, `brush-y` | Drags an interval along one axis; the marks in it emphasise. `mode: \"stateful\"` keeps the interval on screen to move and resize. On a polar chart the x brush is a sector. | Which marks fall in this period or this range? | elements, cartesian region | click-none, escape |\n| `brush-angle` | Drags an angular sector on a pie, donut, rose, or radar; the slices in it emphasise. | Which adjacent slices make up this share? | elements, angular region | click-none, escape |\n| `linked-brush` | Drags a region; the groups inside emphasise in every chart that mounts the same preset. `groupBy` names the shared field; `mode: 'stateful'` keeps the rectangle on screen to move or resize. | Where are these groups in the other charts? | elements, cartesian region | click-none, escape |\n| **Move the view** | | | | |\n| `navigate` | Drags to pan; wheels or pinches to zoom a continuous axis. `axes` limits it; `pan: false` leaves the plot drag free. On a map it moves the projection; on a globe (a world Choropleth with `projection: \"orthographic\"`) a drag turns the sphere and the zoom stays centred. On a map, `geometry.domain.focus` names the region at the centre. | What is in this part of the range, closer? | navigation | double-click |\n| `brush-zoom` | Drags a rectangle; the chart flies into it. | What is in this box, closer? | navigation | double-click, escape |\n| **Reduce or rearrange** | | | | |\n| `legend-toggle` | Clicks a legend item; its series hides or returns. Hidden series are a setting and survive every reset. | What does the chart look like without this series? | discrete legend | none |\n| `drag-reorder` | Drags a discrete axis label to a new place; the categories reorder. | How do these rank in the order I choose? | reorderable axis | none |\n| `filter-controls` | Picks values in controls beside the chart; the rows that fail are removed, or muted with `mode: \"highlight\"`. One control per listed field. | What does the chart show for this subset of a field it does not draw? | inline data | none |\n| **Mark for the reader** | | | | |\n| `click-annotate` | Clicks a mark; a note with its value pins to it. | What is this value, left on the chart? | elements | click-none, escape |\n| **Hand a gesture to the app** | | | | |\n| `double-activate` | Double-clicks a mark; the mark activates and the host reads it. | Which record should the app open? | elements | click-none, escape |\n| `long-press` | Holds a mark (500 ms); the mark activates. The touch spelling of the same activation. | Which record should the app open, on touch? | elements | click-none, escape |\n| `context-activate` | Right-clicks or long-presses a mark; the host receives a context target and draws its own menu. | What can the app do with this mark? | elements | none |\n| `context-menu` | The reader picks marks and a menu of the app's actions opens beside them; picking one emits `menu-select` with the item's id and the marks. `gesture` picks how: `rectangle` (default), `lasso`, `brush-x`, `brush-y`, `click`, `right-click`, `long-press`. `items` is required: `[{ \"id\", \"label\" }]`. One entry; it brings its own selection. | What can the app do with these marks? | elements, cartesian region | click-none, escape |\n| **Read without a pointer** | | | | |\n| `accessible-navigation` | Tabs into the chart and walks titles, axes, legends, facet headers, series, and marks with the arrow keys; Enter goes a level in, and inside a level Tab steps through it before moving on. Each step says what the element is and emphasises its data. Space on a focused element runs the click presets. | What is on this chart, from the keyboard or a screen reader? | elements | none |\n\nWithin a family the rows answer neighbouring questions, so one chart takes\none of them: `inspect` reads the marks near the pointer, `inspect-index` reads\nevery series at one index; `select` takes an area, `brush-x` a period, `lasso`\na shape; `navigate` moves continuously, `brush-zoom` jumps into a box;\n`double-activate`, `long-press`, and `context-activate` are three spellings of\n\"hand this mark to the app\" for a mouse, a finger, and a menu.\n\n\"Needs\" is what the chart type offers; `list_chart_types` has already applied\nit to the list it returns. The data confirms a few at mount, in Step 3. A\nviewport a gesture commits, and a reset, fly over 400 ms;\n`\"transition\": { \"duration\": 0 }` or `\"resetTransition\": { \"duration\": 0 }`\njumps. Panning and wheel zooming follow the pointer.\n\n**`filter-controls`.** It draws real controls beside the chart and changes the\nrows the chart shows, so it answers a request that names a field to filter by\n(\"filter by region\", \"one year at a time\", \"let me pick the group\"), or a data\ncolumn the chart does not encode whose values are versions of the same chart:\na year in a snapshot scatter, a population group, a scenario. A field the\nchart already draws has its own preset: the colour field `legend-toggle`, a\ncontinuous axis `brush-x` or `navigate`, a category on an axis\n`click-highlight`; and the measure the chart plots is the chart. List `fields`\nexplicitly, one or two, three at most: unlisted, the preset picks up to four\ncategorical and temporal fields itself. One entry per chart. A fixed subset\nbelongs in the data, and slices side by side are a facet in the\n`flint-chart-author` skill.\n\n### Step 3 — resolve conflicts\n\nEach trigger has one owner. At mount, admission gives a trigger to the first\nentry that asks for it; a later entry that asks for the same trigger yields\nit with an `info` warning when it can keep the rest, or is dropped with a\nwarning when it cannot. The chart still renders. Four triggers are asked for\nby more than one preset:\n\n| Trigger | Asked for by | Free it with |\n| --- | --- | --- |\n| The plot drag | `navigate` with pan on, every region preset, `brush-zoom`, `drag-reorder` | `\"pan\": false` on `navigate` beside one region preset |\n| The double-click | `double-activate`, and any entry whose `reset` holds `double-click` (`navigate` and `brush-zoom` by default) | `\"reset\": [\"escape\"]` on `navigate` or `brush-zoom` |\n| The legend click | `legend-toggle`, `click-highlight`, `inspect-index` with `show: \"single\"` | `\"targets\": [\"mark\"]` on `click-highlight` |\n| The retained mark focus | `click-highlight`, `click-group-focus` | one of them |\n\n`click-highlight` yields a legend or an axis click on its own, with an `info`\nwarning; `targets` keeps the spec silent. `accessible-navigation` takes no\npointer trigger, so it sits beside any entry.\n\nThe data confirms what the chart type cannot promise. An entry that fails one\nof these at mount is dropped with a warning:\n\n- `legend-toggle` needs a colour field bound to a discrete legend.\n- `navigate` and `brush-zoom` need a continuous, unfaceted axis.\n- `drag-reorder` needs a discrete axis in the bound encodings.\n- `inspect-index` needs an index axis, which it finds itself: the temporal\n  axis, else the discrete axis beside a measure, else x. A horizontal bar\n  chart reads along y with no option; `axis` overrides the choice. `seriesBy`\n  names the series field when the chart draws one series at a time.\n- `click-group-focus`, `hover-group-focus`, and `linked-brush` need `groupBy`,\n  a bound field.\n\n### Step 4 — write and validate\n\nThe object is `{ \"interactions\": [ ... ] }`, beside `chart_spec` as\n`interaction_spec`. Every entry is\n`{ \"type\": \u003Cpreset>, \"id\"?: \u003Cstring>, \"options\"?: { ... } }`:\n\n- `type` is a preset from the map, supported by the chart type per\n  `list_chart_types`.\n- `options` holds the preset's own options; `list_interaction_presets` lists\n  every one with its type (see \"Preset options\"). `reset` is one of them: omit it for the default list in the map, set it to\n  free a trigger. A preset that keeps nothing (`hover-group-focus`, `inspect`,\n  `context-activate`) accepts no `reset`.\n- `id` sits on the entry and is needed only when one preset appears twice.\n- In code, the same entry is a factory call with the same options:\n  `clickHighlight({ targets: ['mark'] })`, `brushX()`, `navigate({ axes: 'x' })`.\n\n```json\n{\n  \"interactions\": [\n    { \"type\": \"click-highlight\", \"options\": { \"targets\": [\"mark\"] } },\n    { \"type\": \"navigate\", \"options\": { \"axes\": \"x\" } },\n    { \"type\": \"legend-toggle\" }\n  ]\n}\n```\n\n`validate_chart` checks the object without drawing, and `show` runs the same\ncheck when it draws. A malformed entry\nis an error: an unknown `type`, an option beside `type`, an `id` inside\n`options`, a bare string entry, a missing required option, an unsupported\n`reset` gesture, a duplicate `id`. A dropped or yielded entry is a warning\nthat names the entry, what it needed, and the chart type:\n\n```\nInteraction \"legend-toggle\" requires a discrete legend; Bar Chart has none. The interaction was dropped.\nInteraction \"click-highlight\" yields legend clicks to \"legend-toggle\".\n```\n\nFix or drop what it warns about. Return the bare object, strict JSON, unless\nthe user asks for commentary.\n\n### When no preset fits\n\nA host that runs code mounts a `CanvasInteractionDef` in `options.interactions`\nbeside presets: a trigger, what it affords, and a handler that returns the\nlayer for its id.\n\n```ts\nimport { rectangleTrigger, type CanvasInteractionDef } from 'flint-chart\u002Finteractive';\n\nconst timebox: CanvasInteractionDef = {\n  id: 'timebox',\n  eventSource: rectangleTrigger('contain'),\n  affordances: { plot: { cursor: 'region' } },\n  reset: ['click-none', 'escape'],\n  handle(event) {\n    if (event.action !== 'select-region' || event.phase !== 'commit') return null;\n    const x = event.geometry.domain?.x;\n    const y = event.geometry.domain?.y;\n    if (x?.kind !== 'interval' || y?.kind !== 'interval') return null;\n    const keep = seriesInsideBox(x.start, x.end, Number(y.start), Number(y.end)); \u002F\u002F host logic over host rows\n    return {\n      id: 'timebox',\n      ops: [{ op: 'set-style',\n        targets: keep.map((series) => ({ select: { key: { Series: series } } })),\n        value: { state: 'emphasized' } }],\n    };\n  },\n};\n```\n\n| Field | Role |\n| --- | --- |\n| `id` | Names the interaction in events and the layer it retains. |\n| `eventSource` | The trigger: what to capture and how to read it. |\n| `affordances` | The kinds of hit the handler receives, each with its cursor and hover; a hit of a kind not listed never reaches it. Keys: `mark`, `legend-item`, `axis-label`, `plot`. Cursors: `activate`, `drag`, `region`, `navigate`, `inspect`, `draw`. Hovers: `target` (the mark), `cohort` (its group). |\n| `handle(event, context)` | Returns the whole layer for this id, or `null` for no change. |\n| `reset` | Gestures that clear the layer: `click-none`, `double-click`, `escape`. A reset reports as a commit with no `interactionId`. It suits a layer of styles the state shows; an overlay or a data layer is cleared by the host. |\n\n**Triggers.**\n\n| Trigger | The reader does | `action` | `geometry.plot` | `geometry.domain` | Affordance key |\n| --- | --- | --- | --- | --- | --- |\n| `clickTrigger` | Clicks a mark, legend item, or axis label. | `click-element`, `click-legend`, `click-axis` | `point` | — | `mark`, `legend-item`, `axis-label` |\n| `hoverTrigger` | Moves over a mark. | `hover-element`, `hover-legend`, `hover-axis` | `point` | — | same |\n| `dragTrigger(tolerance?)` | Presses a mark, or a projectable overlay path, within `tolerance` px (12) and drags. | `drag` | `drag { start, current, delta }` | the pointer's values; `geometry.projection` while over a projectable overlay | `mark` |\n| `rectangleTrigger(match?, guide?)` | Drags a rectangle. | `select-region` | `rect` | `x`, `y` intervals | `plot` |\n| `xBrushTrigger(match?, mode?, guide?)`, `yBrushTrigger(…)` | Drags an interval along one axis. | `brush-x`, `brush-y` | `rect` | that axis's interval | `plot` |\n| `lassoTrigger(match?, guide?)` | Draws a freehand region; previews begin at the third point. | `select-lasso` | `polygon` | `points`: the stroke so far, in data values | `plot` |\n| `angularBrushTrigger(match?, mode?, guide?)` | Drags a sector on a polar chart. | `brush-angle` | `angular-sector` | — | `plot` |\n| `inspectTrigger(mode?)` | Moves over the plot. | `inspect-x`, `inspect-y`, `inspect-xy` | `point` | `x`, `y` values | `plot` |\n| `inspectIndexTrigger(axis?, show?, seriesBy?)` | Moves along the index axis. | `inspect-x`, `inspect-y` | `point` | the index value | `plot` |\n| `navigationTrigger({ axes?, pan?, zoom?, reset? })` | Drags, wheels, pinches. | `pan-viewport`, `zoom-viewport`, `reset-viewport` | `viewport { delta, factor, anchor }` | the viewport after the move | `plot` |\n| `contextTrigger`, `longPressTrigger(ms?)`, `doubleActivateTrigger` | Right-clicks, holds (500 ms), double-clicks a mark. | `context-element`, `long-press-element`, `double-activate-element` | `point` | — | `mark` |\n\n`match` is `'intersect'` (default) or `'contain'` and decides which marks the\nregion's `target` lists. `guide` styles the drawn shape; `false` hides it. A\nregion is ephemeral: its guide and its preview end at the commit. A brush or\n`select` with `mode: 'stateful'` keeps its region on screen and editable, and\n`event.operation` reads `create`, `move`, `resize-leading`, `resize-trailing`,\nor `clear`. Spread a trigger to tune it:\n`{ ...hoverTrigger, defaultAssistDistance: 28 }` acquires the nearest mark\nwithin 28 px. A chart admits a trigger when it offers what the trigger needs,\nper the map's \"Needs\"; a code definition the chart cannot honour throws at\nmount with a message that names what it needed.\n\n**The event.**\n\n```ts\ninterface CanvasInteractionEvent {\n  action: string;                                     \u002F\u002F from the table\n  phase: 'start' | 'preview' | 'commit' | 'cancel';   \u002F\u002F clicks and hovers skip start\n  operation?: string;                                 \u002F\u002F a stateful brush's edit, or pan | zoom | reset\n  geometry: { plot?, domain?, projection? };\n  target: { visual: { kind, role }, elements: [{ value, records? }] } | null;\n  modifiers?: { shift, ctrl, meta };\n  description?: { kind, type, content, text };  \u002F\u002F on focus-element from accessible-navigation\n}\n```\n\n- `target.elements[i].value` holds the mark's encoded values, derived fields\n  included; `records` holds the source rows when the runtime can prove them. A\n  region target lists every mark inside. A legend hit's `value` is a\n  `LegendTargetValue`: `{ channel, field, domain: { kind: 'value', value } }`,\n  or `{ kind: 'interval', start, end }` on a continuous legend. An axis-label\n  hit's `value` is an `AxisTargetValue`: `{ axis, field, value }`.\n- `geometry.domain` is inverted through the plot's scales: a brush on a time\n  axis gives `domain.x = { kind: 'interval', start: Date, end: Date }`, a linear\n  axis gives numbers, a discrete axis gives the category.\n- `description` arrives on `focus-element` while `accessible-navigation` walks\n  the chart: what the focused element is (`type`, such as \"Bar\") and what it\n  represents (`content`), with the spoken `text`. The walk's emphasis reports\n  through `onChange` as a `preview`.\n- `geometry.projection.kind === 'path'` during a drag over an overlay with\n  `projectable: true`: `segment.start.value` and `segment.end.value` are the\n  overlay rows on each side of the pointer and `segment.t` the fraction between\n  them.\n\n**The types.** `flint-chart\u002Finteractive` exports these, and `flint-chart\u002Freact`\nre-exports them. `onChange` receives a `ChartChange`: the gesture's own\n`target` and `geometry`, and the chart's `state` after it. Generated from the\nsource by `npm run gen:reference`:\n\n\u003C!-- event-types:start -->\n```ts\ninterface ChartChange {\n  \u002F\u002F `preview` while a gesture runs, `commit` for a committed change, `cancel` when a gesture ends with none.\n  phase: ChartChangePhase;\n  \u002F\u002F `host` for `updates`, `applyUpdate`, `clearUpdate` and `dispatch`; always `commit`.\n  source: ChartChangeSource;\n  changed: ChartStateFacet[];\n  \u002F\u002F The interaction behind the change: a reader's gesture, or an external interaction run by `dispatch`.\n  interactionId?: string;\n  action?: CanvasInteractionAction;\n  \u002F\u002F The gesture's own hit: the hovered point, or the marks at an inspected index.\n  target?: SemanticTarget | null;\n  \u002F\u002F The gesture's geometry: a brushed range, an index value, or the viewport after a move.\n  geometry?: CanvasInteractionEvent['geometry'];\n  \u002F\u002F The reset gesture a reader made: a click on nothing, Escape, or a double-click. Reported even when the chart held nothing to reset, so a host that shows a selection of its own can let it go.\n  reset?: InteractionResetGesture;\n  state: ChartState;\n  previous: ChartState;\n}\n\ninterface ChartState {\n  chartType: string;\n  \u002F\u002F The marks the chart emphasizes or focuses now, previews included.\n  selected: SemanticElement[];\n  \u002F\u002F The emphasized marks of each update, by update id; `selected` is their union.\n  entries?: ReadonlyMap\u003Cstring, ChartStateEntry>;\n  \u002F\u002F The legend values a toggle hides now.\n  hidden?: ChartHiddenValue[];\n  \u002F\u002F The domain the plot shows now, when the chart navigates.\n  viewport?: DomainGeometry;\n  \u002F\u002F The category window of each rail.\n  windows?: Partial\u003CRecord\u003C'x' | 'y', ChartCategoryWindow>>;\n  categoryOrder?: unknown[];\n  \u002F\u002F The notes the chart holds now, previews included; `inView` tells which ones it draws.\n  annotations?: ChartAnnotation[];\n  \u002F\u002F The active filter of each field, set by filter controls; absent fields show every value.\n  filters?: Readonly\u003CRecord\u003Cstring, FilterValue>>;\n}\n\ninterface SemanticTarget {\n  visual: {\n    kind: 'mark' | 'path' | 'region' | 'widget' | 'handle' | 'legend' | 'axis' | 'chart' | 'title' | 'header';\n    role: string;\n  };\n  elements: SemanticElement[];\n}\n\ninterface SemanticElement {\n  \u002F\u002F Values represented by the mark's channels, or by a semantic control such as a legend item.\n  value: Record\u003Cstring, unknown>;\n  \u002F\u002F Contributing input records when provenance is available; zero or many may support one value.\n  records?: Record\u003Cstring, unknown>[];\n}\n\ninterface LegendTargetValue {\n  channel?: string;\n  field?: string;\n  \u002F\u002F The entry's value on a reader's hit; read `domain`, which every legend target carries.\n  value?: unknown;\n  domain: LegendDomain;\n}\n\ntype LegendDomain =\n  | { kind: 'value'; value: unknown }\n  | { kind: 'interval'; start?: number; end?: number };\n\ninterface AxisTargetValue {\n  axis: 'x' | 'y';\n  field: string;\n  value: unknown;\n}\n\ninterface DomainGeometry {\n  x?: DomainCoordinate;\n  y?: DomainCoordinate;\n  points?: DomainPoint[];\n  \u002F\u002F The detail level a multi-level projected chart draws for this viewport.\n  level?: string;\n  \u002F\u002F On a multi-level projected chart, the coarsest-level region under the plot centre: its feature id and properties plus the joined row fields.\n  focus?: Record\u003Cstring, unknown>;\n}\n\ntype DomainCoordinate =\n  | { kind: 'value'; value: DomainValue }\n  | { kind: 'interval'; start: DomainValue; end: DomainValue };\n\ntype DomainValue = number | Date | string;\n\ninterface DomainPoint {\n  x?: DomainValue;\n  y?: DomainValue;\n}\n\ninterface ChartHiddenValue {\n  channel: string;\n  value: unknown;\n}\n\ntype FilterValue =\n  | { in: unknown[] }\n  | { range: [unknown, unknown] };\n```\n\u003C!-- event-types:end -->\n- `context` is the chart state plus `available`, every drawn mark, for\n  building refs, and `selected`, the current emphasis, for a toggle rule.\n\n**The handler.**\n\n- It returns the complete layer for the id on every `preview` and `commit`;\n  the runtime drops the preview on `cancel`. A `null` on a `preview` shows\n  nothing until the commit, which is how a rule applies to the committed box\n  only. A region gesture keeps a preview layer under the interaction's id\n  while the drag runs, so a commit after previews restates the layer; a\n  `null` there lets the last preview stand.\n- It maps geometry to the host's model and the model to ops. Host side\n  effects (state, panels, overlays the host draws) come from\n  `flint-interaction` or `onChange`, which carry the same event; the handler\n  itself returns ops and leaves the DOM to the runtime.\n- Triggers have one owner for code definitions as for presets; two code\n  definitions on one trigger throw at mount. `navigate({ pan: false })` goes\n  beside a region trigger.\n- An element drag owns the press on the marks it affords. A press there is a\n  `drag` with phase `start`; `target` is the pressed mark through every phase.\n  The layer returned at `start` is kept as the gesture's preview, and a release\n  without movement commits that preview with no further call. No click\n  definition fires on those marks, so the definition that drags a mark also\n  answers its press.\n- A click definition that affords `hover` on marks receives `preview` events\n  for the hover cue. Return `null` or an emphasis for them, and the layer on\n  `commit`.\n\n**Patterns.**\n\n| Pattern | Flow | Pieces |\n| --- | --- | --- |\n| Gesture rule | Flint in, Flint out | A region trigger reads `geometry.domain`; the host rule picks rows; `set-style` by key (the timebox above). |\n| Host-drawn detail | Flint in, custom out | `hoverTrigger` or `inspectTrigger` with a `null` handle; the host listens to `flint-interaction` for the resolved element and draws a lens, a cursor, or a badge over the mount. |\n| Application drives the chart | external in, Flint out | `externalInteraction` plus `dispatch` per frame: a playback year, a reveal progress. |\n| Drag along a path | press, then drag | One `dragTrigger()` definition and one layer. At `start` on a mark it returns the mark's path as a `set-overlay` `line` with `interactive: true, projectable: true` and `order`, plus `set-data` for the current position. On `preview` and `commit` with `geometry.projection` it returns the same two ops for the pointer's position. `intrinsicDomain` on the x and y fields holds the axes still across the swaps. |\n| Freehand stroke | draw | The rows to guess form their own series, and a mount update sets `opacity: 0` on that series, which keeps the axis. `lassoTrigger('contain', false)` with `plot: { cursor: 'draw' }` is the pen: `geometry.domain.points` become the rows of a `set-overlay` line. An `externalInteraction` reveals the truth as an overlay, then the host clears the hide. |\n| Chart-level zoom | wheel | When the layout itself must change (bar step, ticks, domain), filter the rows and `mountChart` again in a hidden layer; swap on `ready`. No preset re-lays out. |\n| Semantic zoom on a map | navigate | A Choropleth with `level: 'auto'` (US states, then counties) or with your own `levels: [{ value, url, feature?, enter?, exit? }]` (one GeoJSON or TopoJSON object per level, rows joined on the feature `id` or `featureKey`, `baseMapUrl` drawn beneath as land without data), or a Map with `levelField` and `levels`; `navigate()`, and `set-viewport { region: { key } }` with a `transition` to fly into a region. Navigation events carry `geometry.domain.level` and `focus` (the coarsest region under the centre). A hover reaches the host through a hover affordance (`hover: 'target'`); a handler that returns `null` leaves the map as drawn. |\n\nHost-drawn detail, the shape of it:\n\n```ts\nconst probe: CanvasInteractionDef = {\n  id: 'probe',\n  eventSource: { ...hoverTrigger, defaultAssistDistance: 28 },\n  affordances: { mark: { hover: 'target' } },\n  handle() { return null; },\n};\ncontainer.addEventListener('flint-interaction', (e) => {\n  const { interactionId, event } = (e as CustomEvent\u003CFlintInteractionEventDetail>).detail;\n  if (interactionId !== 'probe' || event.phase !== 'preview') return;\n  const row = event.target?.elements[0]?.records?.[0];\n  if (row) lens.show(row);                 \u002F\u002F the host draws; the chart is unchanged\n});\n```\n\n## Update the chart\n\n### Step 1 — read the state\n\nA host hands the model the chart input and the state, as text or as data. The\nMCP chart view does this after every build (first mount, new `updates`, a\npanel edit) and every committed gesture, and each send replaces the last:\n\n```\nChart \"Life expectancy\": Line Chart, theme economist.\nEdited in panel: theme default → economist.\nEncodings: x = year, y = life, color = continent.\nInteractions: legend-toggle, navigate.\nAgent updates (yours):\n- asia:\n  ✓ emphasizes 3 marks where continent = Asia\n- oceania:\n  ✗ emphasizes marks where continent = Oceania: no row has continent = Oceania.\nUser interactions:\n- Hidden: Africa.\nThe y axis shows life from 70 to 86.\n```\n\nThe state is in field terms: what the chart is after the reader's panel edits,\neach op of your updates as applied (✓) or not and why (✗), what the reader\nselected, hid or filtered, and the viewport on view when the chart navigates.\n`validate_chart` and `show` return the same report before the\nreader touches the chart, with `User interactions: none.` From it, decide what the\nreader should see next: which rows stand out, what one note says, which range\nframes them. The rows you were given are the source of every value; the chart\nhands over no copy of the data, and nothing is computed that the chart cannot\nverify.\n\n### Step 2 — choose ops\n\nEvery change from outside is one update, `{ id, ops }`. Each op does one kind\nof thing:\n\n| Op | It is for | Effect | Shape | Needs |\n| --- | --- | --- | --- | --- |\n| `set-style` | The marks the chart already draws: emphasize some, fade some, hide some, recolour some. A highlight is this op with `state: 'emphasized'`. | Emphasize the targets and mute the rest; recolour; fade; hide. | `{ op, targets: UpdateTarget[], value: StyleSpec }` | marks |\n| `set-annotation` | One mark that should carry a note. | Pin a note on one mark; `null` clears it. | `{ op, target: UpdateTarget, value: { text, anchor?: 'segment' \\| 'point' } \\| null }` | one resolved element |\n| `set-viewport` | The range or region the chart should frame. | Frame a continuous domain. | `{ op, axes: 'x' \\| 'y' \\| 'xy', value: { x?: [lo, hi], y?: [lo, hi] } }`; on a multi-level map `value: { region: { key } }` | `navigate` or `brush-zoom` mounted on that axis |\n| `set-order` | The order of the categories on a discrete axis. | Reorder the axis; list every category in the order wanted. | `{ op, scope: 'category', field, values: unknown[] }` | `drag-reorder` mounted on that axis |\n| `set-overlay` | Marks the chart does not draw: a reference line, a threshold band, a forecast, a sketch of rows. | Draw new rows through the plot's own scales. | `{ op, name, value: { mark, data: { values }, encodings: { x?, y?, x2?, y2?, order?, color?, text? }, role, interactive?, projectable?, style? } \\| null }` | x and y scales |\n| `set-freeform-overlay` | A custom shape with no data behind it. | Draw SVG, or a clone of marks, over the plot. | `{ op, name, value: { coordinateSpace: 'plot' \\| 'renderer', body: [{ type: 'svg', content } \\| { type: 'clone', targets, transform?, opacity? }] } \\| null }` | — |\n| `set-data` | The rows the chart draws, for playback or a swap. | Replace every row; the rows carry every encoded field. | `{ op, source: 'main', value: { rows } }` | inline data |\n\n`StyleSpec` is `{ state?, opacity?, visible?, fill?, stroke?, strokeWidth?,\nmutedOpacity? }`. Two states matter from outside: `emphasized` keeps the\ntargets at full ink and mutes every other mark; `normal` with `targets: []`\nclears the layer. `opacity` on the targets fades a subset and leaves the rest\nalone; `opacity: 0` hides marks and keeps the axes, where `visible: false`\nremoves the rows and shrinks the scales. The mute level is one per chart,\nfixed at mount from the lowest `mutedOpacity` the mounted interactions carry\n(0.25 when none).\n\nOverlay marks are `line`, `point`, `rule`, `rect`, `text`. `encodings` name\nfields of the overlay's own rows, projected through the chart's x, y, and\ncolour scales. `style` takes `stroke`, `strokeWidth`, `strokeDash`, `fill`,\n`fillOpacity`, `opacity`, `pointRadius`, `fontSize`, `fontWeight`, `textAlign`\n(`start`, `middle`, `end`), `dx`, `dy`. A `line` takes its stroke and a `text`\nits fill from the `color` encoding; a `point` takes `style.fill`. A `text` sits on\nthe data, so it draws a halo in the chart's background; `stroke` sets another\ncolour, `strokeWidth` its width, and `stroke: 'none'` drops it, as a faint\nwatermark wants.\n\nTwo ops need a preset the chart mounts: `set-viewport` needs `navigate` or\n`brush-zoom` on that axis, `set-order` needs `drag-reorder`. The host says\nwhich are mounted, or the state shows `viewport` and `categoryOrder`. On a\nchart that mounts neither, say so and offer an emphasis; `set-data` replaces\nwhat the chart shows and is not a frame. Legend values belong to\n`legend-toggle`: no update hides or restores one, and a series fades with\n`opacity` instead.\n\n### Step 3 — name targets and values\n\nA target is one of three forms:\n\n```ts\n{ select: { key: { Country: 'Japan' } } }                 \u002F\u002F marks with these values\n{ select: { where: { Region: { in: ['Asia', 'Europe'] }, Month: { range: ['2025-03', '2025-06'] } } } }  \u002F\u002F a set of marks\n{ visual: { kind: 'mark', role: 'mark' }, elements }      \u002F\u002F elements from an event or the state\n```\n\n- A `where` describes a set, in the words filters use: each field `in` a list\n  or within a `range` (ends included; `null` leaves an end open), and every\n  field must hold. Use it for a period, a band of values, or several categories\n  at once, where a key would need one target per row. Only `set-style` takes a\n  `where`; a note or an order names one mark, so it takes a key.\n\n- A selector key names fields the chart encodes (`x`, `y`, `color`, `detail`,\n  `size`, …) and matches rows by equality, so `'2007'` and `2007` differ. One\n  key may cover many marks (`{ Continent: 'Africa' }` takes every African\n  mark); several fields narrow it; an annotation's key covers one. On a line\n  or area chart, a key on the series field covers the whole line and a key on\n  the x field covers the segment that starts at that row, so a run of segments\n  is one target per row. The last row of a line starts no segment; its key\n  takes the segment that ends there. A note on an x key marks the segment, for\n  a change from one row to the next (\"2019 → 2020: +4%\"); `anchor: \"point\"`\n  marks the row's own point, for its value (\"Dec 2022: $0.87B\"). A note on\n  the last row is always at its point.\n- A field the chart does not encode resolves to nothing. Encode the identity\n  field (`detail` on a scatter), or pass elements from `event.target`,\n  `state.selected`, or `context.available` through as `{ visual, elements }`,\n  as they are: an element's `value` carries derived fields on a bar, pie,\n  heatmap, or waterfall mark (a stack start, a sort index), so the element\n  resolves where a key copied from it does not.\n- A target that matches nothing is listed in the result as unresolved, and\n  nothing nearby is taken in its place.\n\nA key value, an overlay row, and a viewport bound hold what the parsed row\nholds: a CSV `120` is the number `120`. A temporal field (`Year`,\n`YearMonth`, `Date`, …) takes a date written the way the data writes it\n(`\"2014-01\"`, `2014` on a `Year` field) or UTC epoch milliseconds. On a\ndiscrete axis the value is the category. Overlay rows need the fields their\nmark reads: `line`, `point`, `text` read `x` and `y`. A `rule` with only `x`\nis a vertical line across the plot, and one with only `y` a horizontal\nthreshold; a `rect` with only `x` and `x2` is a full-height band, and one with\nonly `y` and `y2` a full-width band. With all four channels, `rule` and `rect`\nrun from (`x`, `y`) to (`x2`, `y2`). An overlay whose rows do not project is\nreported as unsupported.\n\n```json\n{ \"id\": \"agent\", \"ops\": [\n  { \"op\": \"set-style\", \"value\": { \"state\": \"emphasized\" },\n    \"targets\": [{ \"select\": { \"key\": { \"month\": \"2014-01\" } } }, { \"select\": { \"key\": { \"month\": \"2014-02\" } } }] },\n  { \"op\": \"set-overlay\", \"name\": \"threshold\", \"value\": { \"mark\": \"rule\", \"role\": \"reference\",\n    \"data\": { \"values\": [{ \"y\": 1e9 }] }, \"encodings\": { \"y\": { \"field\": \"y\" } } } },\n  { \"op\": \"set-overlay\", \"name\": \"period\", \"value\": { \"mark\": \"rect\", \"role\": \"band\",\n    \"data\": { \"values\": [{ \"start\": \"2022-11\", \"end\": \"2026-08\" }] },\n    \"encodings\": { \"x\": { \"field\": \"start\" }, \"x2\": { \"field\": \"end\" } }, \"style\": { \"fillOpacity\": 0.12 } } }\n] }\n```\n\n### Step 4 — send the layer and read the result\n\n**Ids are layers.** One id holds one concern: the agent's update, a story\nstep, a linked selection. Use one stable id, such as `agent`, and send the\nwhole layer each time:\n\n- An update with an id already retained replaces that layer, and carries only\n  what it lists: an omitted viewport flies home, an omitted order returns to\n  the input order, an omitted annotation disappears. The ops that should hold\n  are repeated; the ops the request replaces are dropped.\n- Layers compose in insertion order, and a replaced layer keeps its place:\n  styles accumulate across layers; for an annotation, an order, a data swap,\n  or an overlay name, the layer inserted last wins. One kind of op lives in\n  one layer: a drag and a playback that both swap the rows write the same id.\n- A canvas interaction retains its layer under its own `id`; a host may write\n  or clear that layer by the id, as a Reset button does.\n- A gesture reset (`click-none`, `escape`, `double-click`) clears preset\n  layers only. A host layer stays until the host clears it, or until a layer\n  with `set-style`, `targets: []`, `state: 'normal'` replaces it.\n- After `set-data` the marks are new; a style layer is sent again with the\n  data.\n\n**The result.** `applyUpdate`, `setUpdates`, and `dispatch` return a\n`ChartUpdateResult`: `{ status: 'applied' | 'partially-applied' |\n'unsupported', resolvedTargets, unresolvedTargets, unsupportedOps }`. An\nunresolved target has a wrong field or value; an unsupported op needs a\npreset the chart does not mount. What the chart shows is what the result says\nit shows: fix an unresolved key or drop an unsupported op before describing\nthe chart.\n\n**From the MCP view.** There is no `applyUpdate` to call. Pass the same layers\nas the `updates` argument beside the chart input, not inside it, to\n`validate_chart` and then to `show` as part of `chart`:\n\n```json\n{\n  \"data\": { \"values\": [ ... ] },\n  \"chart_spec\": { \"chartType\": \"Bar Chart\", \"encodings\": { \"x\": \"region\", \"y\": \"revenue\" } },\n  \"updates\": [\n    { \"id\": \"agent\", \"ops\": [\n      { \"op\": \"set-style\", \"targets\": [{ \"select\": { \"key\": { \"region\": \"East\" } } }], \"value\": { \"state\": \"emphasized\" } },\n      { \"op\": \"set-annotation\", \"target\": { \"select\": { \"key\": { \"region\": \"West\" } } }, \"value\": { \"text\": \"Lowest in Q3\" } }\n    ] }\n  ]\n}\n```\n\n`show` opens the chart with the layers applied, with the same look as a\nuser action. To show the chart in a new state, call `show` again with the same\ninput and the layers wanted now. Each `show` opens a new card with\nexactly those layers; earlier cards keep what they showed. The argument is state, and `interaction_spec` is behaviour, so\neach stays where it is. The reader's own brush or selection does not survive\nthe new view; the context message reports it in field terms, and it is\ncarried as a layer when it still serves the reader.\n\nCheck the input and `updates` with `validate_chart` first. A malformed layer\nor an invalid spec is an error, and `show` draws nothing for it. Otherwise the\nresult is the report above, one line per op, led by a count when an op did not\napply. The chart draws without a ✗ op: fix it in the layer (a key no row has,\nan overlay encoding written as `\"x\": \"start\"` instead of `{ \"field\": \"start\" }`,\na viewport with no navigation preset) before you call `show`, or tell the\nreader what is missing. The\nreader's later brushes and clicks reach you with their next message, not in\nthe same turn.\n\n## Use Flint in an application\n\n### Mount\n\n```bash\nnpm install flint-chart vega vega-lite vega-tooltip\n```\n\n```ts\nimport { mountChart, navigate, legendToggle } from 'flint-chart\u002Finteractive';\n\nconst surface = mountChart(container, input, {\n  backend: 'vegalite',\n  renderer: 'svg',\n  interactions: [navigate({ axes: 'x' }), legendToggle()],\n  chartId: 'sales',\n});\nawait surface.ready;\nconst warnings = await surface.warnings; \u002F\u002F interactions the chart could not honour\n\u002F\u002F …\nsurface.destroy();\n```\n\n| Member | What it does |\n| --- | --- |\n| `ready`, `warnings` | Promises: the first render; the dropped or yielded interactions. Every call below waits for `ready` itself. |\n| `applyUpdate(update, options?)` | Retains one update by id; replaces the layer with the same id. `options.transition.duration` animates a viewport. |\n| `setUpdates(updates)` | Replaces every retained update. |\n| `clearUpdate(id)` | Removes one layer. |\n| `dispatch(id, payload)` | Runs an `externalInteraction` handler and applies what it returns. |\n| `getState()` | What the chart shows now; `undefined` before `ready`. |\n| `onChange(callback)` | Every change to that state, after the render; returns the unsubscribe. |\n| `layout` | Once ready: `plot`, the plot box `{ width, height }` on a chart that draws one plot; `views`, each drawn view as `{ role, box: { x, y, width, height } }` (role `plot`, `text`, `tile` or `rule`; facet panels row by row); `block`, the box the views take with their axes and headers, without title and keys. Boxes are in pixels from the drawn chart's top left, read from what it drew, so they follow filters and resizes. `undefined` on charts with a category rail and on non-Vega-Lite backends. |\n| `refresh()` | Re-projects overlays after the host rescales the chart. |\n| `element`, `chartId` | The mount's root and the stable chart id. |\n| `destroy()` | Removes listeners, the renderer, and the DOM. |\n\n- `input` is a `ChartAssemblyInput`. Presets go in `input.interaction_spec` as\n  JSON or in `options.interactions` as factory calls. Both may appear; spec\n  entries mount first, and a code definition with a spec entry's `id`\n  replaces it with an `interaction_overridden` info warning.\n- A malformed `interaction_spec`, a repeated code `id`, or a code definition\n  the chart cannot honour fails the mount with an error that names it. A spec\n  entry the chart cannot honour is dropped with a warning instead.\n- `options.availableSize: { width?, height? }` is the room the host gives the\n  chart. It becomes the `canvasSize` ceiling on the sides given, so the chart\n  lays out for that room rather than being scaled into it.\n- `options.updates` holds retained updates present at the first render, for a\n  chart that starts with a part hidden or framed.\n- `renderer: 'svg'` for charts whose marks a host reads from the DOM, or under\n  a few thousand marks; `'canvas'` for maps and dense charts.\n- A `Year` or `Date` field makes a temporal axis whose rows hold `Date`s or\n  ISO strings; a bare number such as `2013` reads as milliseconds after 1970.\n  A `semantic_types` entry may be an object,\n  `{ semanticType, intrinsicDomain: [lo, hi] }`; the intrinsic domain holds an\n  axis still while the rows change.\n- `options: { addTooltips: false }` on the input is the switch for the\n  tooltip, for a chart whose hidden values it would give away.\n- Build with a bundler. The interactive entry reaches every backend through\n  lazy imports with no file extension; when `echarts`, `chart.js`, and\n  `plotly.js-dist-min` are not installed, mark them external (esbuild\n  `--splitting --external:echarts --external:chart.js --external:plotly.js-dist-min`;\n  Vite `build.rollupOptions.external`).\n- In React, use `\u003CFlintChart>` from `flint-chart\u002Freact` rather than mounting\n  in an effect; see \"In React\" below.\n\n### In React\n\n```tsx\nimport { FlintChart, type FlintChartHandle } from 'flint-chart\u002Freact';\nimport { clickHighlight } from 'flint-chart\u002Finteractive';\n\nconst chartRef = useRef\u003CFlintChartHandle>(null);\n\n\u003CFlintChart\n  ref={chartRef}\n  spec={input}\n  interactions={[clickHighlight({ targets: ['mark'] })]}\n  width=\"100%\"\n  fit=\"relayout\"\n  onChange={(change) => setSelected(change.state.selected)}\n  onError={(error) => report(error)}\n\u002F>;\n```\n\n| Prop | What it does |\n| --- | --- |\n| `spec` | The `ChartAssemblyInput`, `interaction_spec` included. A change of content remounts; rows compare by reference. |\n| `interactions` | Factory calls added to the spec's entries, or `(fromSpec) => definitions` for the full list. A factory rebuilt with the same id and options keeps the chart; a changed option remounts it. |\n| `updates` | Host updates, diffed by id. Passing even `[]` mounts the runtime on a chart with no interactions. |\n| `width`, `height`, `fit` | The box. `fit` is `scale-down` (default; never scales up), `crop` (natural size, clipped), or `relayout` (the box becomes the layout room; a resize remounts once it settles, so a selection does not survive it). |\n| `onChange`, `onInteraction` | The state after a change, and the raw gesture record. |\n| `onRender`, `onWarnings`, `onError` | Each mount's render and warnings; a failed mount, shown in the box as a muted error. |\n| `fallback` | Shown until the chart mounts. |\n| `ariaLabel`, `chartId` | `ariaLabel` applies in place; a new `chartId` remounts. |\n\nThe ref's `FlintChartHandle` holds `surface` (the mounted surface or `null`),\n`applyUpdate`, `clearUpdate`, `dispatch`, `getState`, and `refresh`. A remount\nkeeps the old chart on screen until the new one is ready.\n\n### Read the chart\n\n| `getState()` field | Holds |\n| --- | --- |\n| `selected` | The marks the chart emphasizes now, previews included. |\n| `entries` | The same marks per update id, with `layer: 'retained' \\| 'preview'`. |\n| `hidden` | Legend values a `legend-toggle` hides, as `{ channel, value }`. |\n| `viewport` | The domain on view after a pan or zoom; absent on a chart that does not navigate. |\n| `categoryOrder` | The order of the reorderable axis now. |\n| `windows` | Category rail windows, `{ start, count, total }` per channel. |\n\nEach element's `value` is the mark in field terms: a bar's category and\nmeasure, a legend item's `{ channel, field, domain }`, a histogram bar's\n`{ field, range }`. The host owns the rows and queries them from the value and\nthe geometry.\n\n`onChange` fires after every render with a `ChartChange`: `phase` (`preview`\nwhile a gesture runs, `commit` for a committed change or a host call, `cancel`\nwhen a gesture ends with nothing), `interactionId` and `action` (absent for a\nhost call and for a reset), the gesture's own `target` and `geometry`, and the\n`state` after. An overlay or a data swap leaves no trace in the state, so the\nhost keeps that model itself.\n\n```ts\nconst valuesOf = (elements: readonly SemanticElement[]) => elements.map((element) => element.value);\n\n\u002F\u002F A panel follows the gesture live, else shows what the chart shows.\nsurface.onChange(({ phase, target, state }) => {\n  const hit = valuesOf(target?.elements ?? []);\n  panel.show(phase === 'preview' && hit.length > 0 ? hit : valuesOf(state.selected));\n});\n\n\u002F\u002F An agent context reads committed state only: values and ranges, never rows.\nsurface.onChange(({ phase, state, geometry }) => {\n  if (phase !== 'commit') return;\n  sendContext({ selected: valuesOf(state.selected), range: geometry?.domain, hidden: state.hidden, viewport: state.viewport });\n});\n```\n\nThe container also emits a bubbling DOM event, `flint-interaction`, for every\ncanvas interaction, before the chart reacts: `detail.chartId`,\n`detail.interactionId`, and `detail.event`, the same `CanvasInteractionEvent`\na handler gets. It fires for a definition whose `handle` returns `null`, so it\nis the path for a host that draws its own response to a gesture.\n\nThings the surface holds fixed: no preset changes the layout, so a zoom never\nrecomputes the bar step, the ticks, or the domain (filter the rows and mount\nagain for that); the mute level is set at mount; Escape reaches a chart after\na press on it, and only that chart; a preset's toggle (`select`,\n`click-highlight`) needs Shift, Ctrl, or Meta, and a plain second click\nreplaces; a press on an overlay path resolves to no mark, so a `click-none`\nreset on the definition that drew the overlay clears it at that press.\n\n## Link charts\n\n`linkCharts` links mounted charts on the fields they share. A selection on one\nchart (a click, a brush, a group focus, a hover) becomes one condition, a part\nper field; every chart emphasizes the marks that meet it, the source included,\nthrough one `linked-selection` layer; a reset on any chart clears it everywhere.\n\n```ts\nimport { linkCharts, mountChart, clickHighlight, brushX } from 'flint-chart\u002Finteractive';\n\nconst link = linkCharts({\n  bars: { input: barsInput, surface: mountChart(barsEl, barsInput, { backend: 'vegalite', interactions: [clickHighlight()] }) },\n  trend: { input: trendInput, surface: mountChart(trendEl, trendInput, { backend: 'vegalite', interactions: [brushX()] }) },\n}, { fields: ['region', 'month'] });\nfor (const [id, chart] of Object.entries(await link.links)) console.log(id, chart.notes); \u002F\u002F what each chart left out\nlink.onCondition(({ condition }) => table.filter(condition)); \u002F\u002F { region: { in: ['Asia'] }, month: { range: [start, end] } }\n\u002F\u002F link.clear(); link.destroy();\n```\n\n**A table, a filter or any control beside linked charts** joins the same\ncondition: `link.select(parts)` sets parts as if a chart had selected them, and\n`onCondition` gives the table what to show. One condition, so a row click, a\nregion picker and a brush combine (AND) and a reset on any chart clears them all.\n\n```ts\nrow.addEventListener('click', () => link.select({ country: { in: [row.dataset.country] } }));\nregionPicker.addEventListener('change', (e) => link.select({ region: e.target.value ? { in: [e.target.value] } : null })); \u002F\u002F null lets go\nlink.onCondition(({ condition }) => renderTable(rows.filter((r) => meets(r, condition))));\n```\n\n- `select` only takes fields in `fields`; a chart shows a part when its marks\n  carry the field (encode it, or put it on `detail`).\n- The link emphasizes the matching marks; to drop rows instead (a filter), give\n  the charts filtered data as below.\n\n```tsx\nimport { FlintChart, useLinkedCharts } from 'flint-chart\u002Freact';\n\nconst link = useLinkedCharts({ bars: barsInput, trend: trendInput }, { fields: ['region', 'month'] });\n\u003CFlintChart spec={barsInput} {...link.chart('bars')} \u002F>;\n\u002F\u002F link.select({ region: { in: [\"Asia\"] } }) from a table or control; link.clear()\n\u003CFlintChart spec={trendInput} {...link.chart('trend', { onChange: setPageState, updates: myUpdates })} \u002F>;\n\u002F\u002F the chart's own onChange, onRender and updates go through chart(id, own); a prop after the spread replaces the link's\n\u002F\u002F link.condition.condition is the same condition; filtered rows in `spec` keep it\n```\n\n- A chart receives a field when its marks carry it (encode it, on `detail` if\n  nothing else shows it), and sends through each selecting interaction whose\n  subject includes a connected field. `{ charts: { trend: { receive: false } } }`\n  keeps a chart as drawn; `send: false` keeps its selections to itself.\n- The link emphasizes; it never filters or re-aggregates. To filter, read the\n  condition (`onCondition`, `link.condition`) and give each chart its rows:\n  beside `linkCharts`, a `set-data` update (a new spec remounts the chart, and\n  the link holds the old surface); beside `useLinkedCharts`, a `spec` with the\n  filtered rows.\n- `setUpdates` replaces every layer, the link's included, until the condition\n  next changes; use `applyUpdate` and `clearUpdate` beside a link.\n- `linkCharts` checks the charts with the validator, loaded on first use; pass\n  `{ links }` from `validateLinks` (`flint-chart\u002Fvalidate`) to skip that load.\n\nFor a link `linkCharts` does not express (a payload each chart reads its own\nway), wire `onChange` to an `externalInteraction` on the other charts:\n\n```ts\nconst linked = externalInteraction\u003C{ countries: string[] }>({\n  id: 'linked',\n  handle: ({ countries }) => ({\n    id: 'linked',\n    ops: [{ op: 'set-style', targets: [{ select: { where: { Country: { in: countries } } } }], value: { state: 'emphasized' } }],\n  }),\n});\nsource.onChange(({ phase, interactionId, state }) => {\n  if (phase !== 'commit' || !interactionId) return;       \u002F\u002F host writes carry no id: no loop\n  void other.dispatch('linked', { countries: state.selected.map((element) => String(element.value.Country)) });\n});\n```\n\nLinked charts share one `theme_spec` so a value has one colour everywhere.\n\nThe same `dispatch` drives a chart over time: a playback loop dispatches one\nframe per animation frame, and the handler returns the whole layer for that\nframe.\n\n## Worked examples\n\nIn each example `data` is a placeholder; the host binds real rows.\n\n### Presets: a named gesture\n\nUser: \"An interactive chart that I can brush.\" A line chart of `sales` by\n`month`, one series.\n\n```json\n{ \"interactions\": [ { \"type\": \"brush-x\" } ] }\n```\n\nThe user named one job, a brush along the period axis, and the row for it is\n`brush-x`. Hover already reads the values.\n\n### Presets: a goal on a category chart\n\nUser: \"Which regions stand out? Let me look.\" A bar chart of `revenue` by\n`region`, no colour field.\n\nThe reader singles out a region and compares orders. `click-highlight` singles\none out; `drag-reorder` lets the reader rank by hand. No colour field is\nbound, so `legend-toggle` has nothing to work with. Both presets ask for a\ndifferent trigger, a click on a bar and a drag on an axis label.\n\n```json\n{\n  \"interactions\": [\n    { \"type\": \"click-highlight\" },\n    { \"type\": \"drag-reorder\" }\n  ]\n}\n```\n\n### Presets: a multi-series line chart in an explore session\n\nUser: \"Help me understand how these three regions moved.\" The chart:\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": { \"month\": \"YearMonth\", \"sales\": \"Amount\", \"region\": \"Region\" },\n  \"chart_spec\": { \"chartType\": \"Line Chart\", \"encodings\": { \"x\": { \"field\": \"month\" }, \"y\": { \"field\": \"sales\" }, \"color\": { \"field\": \"region\" } } }\n}\n```\n\nThe reader compares the series at one month, hides a region, and points at a\nperiod. `inspect-index` reads every series at one month; `legend-toggle` has a\ncolour field to work with; `brush-x` focuses a period. `inspect-index` with\n`show: \"all\"` asks for no legend click, so it sits beside `legend-toggle`.\n\n```json\n{\n  \"interactions\": [\n    { \"type\": \"inspect-index\" },\n    { \"type\": \"legend-toggle\" },\n    { \"type\": \"brush-x\" }\n  ]\n}\n```\n\n### Presets: a chart that drives the app\n\nUser: \"In my dashboard, a click selects the country and a double-click opens\nits page. Zooming the y axis would help too.\" A scatter of `gdp` by `life`\nwith `color: continent` and `detail: country`.\n\n`navigate` holds the double-click by default for its reset, and\n`double-activate` needs it; `reset: [\"escape\"]` frees it. `click-highlight`\ntakes `targets: [\"mark\"]` so the app's selection comes from marks only.\n\n```json\n{\n  \"interactions\": [\n    { \"type\": \"click-highlight\", \"options\": { \"targets\": [\"mark\"] } },\n    { \"type\": \"double-activate\" },\n    { \"type\": \"navigate\", \"options\": { \"axes\": \"y\", \"reset\": [\"escape\"] } }\n  ]\n}\n```\n\nThe app reads the selection from `onChange` on a `commit` whose `action` is\n`click-element`, and the activation from one whose `action` is\n`double-activate-element`.\n\n### ChartUpdate: an agent points out rows\n\nUser: \"Which Asian countries are past 80 years? Point them out.\" The chart is\nthe scatter above, with `navigate` on `y`, and the state says nothing is\nemphasized. The agent finds Japan and Singapore in its rows and answers:\n\n```json\n{\n  \"id\": \"agent\",\n  \"ops\": [\n    { \"op\": \"set-style\",\n      \"targets\": [\n        { \"select\": { \"key\": { \"country\": \"Japan\" } } },\n        { \"select\": { \"key\": { \"country\": \"Singapore\" } } }\n      ],\n      \"value\": { \"state\": \"emphasized\" } },\n    { \"op\": \"set-annotation\",\n      \"target\": { \"select\": { \"key\": { \"country\": \"Japan\" } } },\n      \"value\": { \"text\": \"Japan, 82.6 years\" } },\n    { \"op\": \"set-viewport\", \"axes\": \"y\", \"value\": { \"y\": [70, 86] } }\n  ]\n}\n```\n\n`country` resolves because it is encoded on `detail`. The result returns\n`applied` with two resolved targets; a key with `\"Country\"` would come back\nunresolved, and the agent fixes the spelling before it describes the chart.\n\n### Code: two linked charts\n\nTwo bar charts share a `Country` field: GDP per country, and population per\ncountry. A click on one emphasizes the same country on both.\n\n```ts\nimport { mountChart, clickHighlight, linkCharts } from 'flint-chart\u002Finteractive';\n\nconst mount = (el: HTMLElement, input: ChartAssemblyInput) =>\n  ({ input, surface: mountChart(el, input, { backend: 'vegalite', interactions: [clickHighlight({ targets: ['mark'] })] }) });\nconst link = linkCharts({ gdp: mount(gdpEl, gdpInput), population: mount(popEl, popInput) }, { fields: ['Country'] });\n```\n\nThe clicked chart drops its own emphasis once the link shows the condition, so\nboth charts show one selection. A `click-none`, Escape, or double-click on\neither chart clears it on both; the link's own writes are host changes, so no\nchart echoes them back.\n\n### Bespoke: a rule over the brushed box\n\nA line chart of `Series` by `Month`. The reader drags a box; the series whose\nevery point in that month range lies inside the y range stay emphasized. No\npreset applies a rule over series, so the definition is the `timebox` above:\n`rectangleTrigger('contain')`, a `plot` affordance with the `region` cursor,\na handler that acts on `select-region` at `commit`, and `reset:\n['click-none', 'escape']` so a click on empty plot clears it. It mounts beside\n`navigate({ pan: false })` when the chart also zooms, since both ask for the\nplot drag otherwise.\n\n## Validation checklist\n\nFor an `interaction_spec`:\n\n1. It parses as strict JSON and is a bare object with `interactions`.\n2. Every entry answers a job from Step 1: a gesture the user named, a\n   question their goal raises, or something the chart invites.\n3. Every `type` is in the chart type's list from `list_chart_types`, and every\n   option is under `options` with `id` on the entry.\n4. `groupBy` and `seriesBy` are present where required, and name bound fields.\n5. At most one entry owns the plot drag, the double-click, the legend click,\n   and the retained mark focus.\n6. `validate_chart` returns no warning.\n\nFor a `ChartUpdate`:\n\n1. Every `op` and `state` is one from the op table.\n2. Every selector key names encoded fields with values that exist in the rows.\n3. The update is the whole layer for its id; clearing uses `targets: []` with\n   `state: 'normal'`.\n4. Ops that need a mounted preset have it, or `unsupportedOps` has been read\n   and acted on.\n5. The result came back `applied` before you said what the chart shows.\n\nFor code:\n\n1. Every trigger, cursor, and field name is one from this skill or the\n   installed `flint-chart\u002Finteractive` types.\n2. Every definition declares `affordances` for the hits its trigger yields.\n3. No two definitions, and no definition and spec entry, share a trigger or\n   an `id`.\n4. The surface is destroyed on unmount, and every `applyUpdate` or `dispatch`\n   result is read.\n\n## Preset options\n\n`list_interaction_presets` returns every preset with what it needs from the\nchart, its reset gestures, and every option it accepts under `options`, with\neach option's type and note; `type` narrows it to one preset and `chartType`\nto the presets a chart type supports. In code, the same options are the\nfactory's parameter type in `flint-chart\u002Finteractive`. `id` is never an\noption: it sits on the entry. A `ResetGesture` is `click-none`,\n`double-click`, or `escape`. `dimOpacity` is the opacity of the muted marks,\n0.25 by default. A `guide` is `{ visible?, style? }`, or `false` to draw\nnothing while the gesture runs.\n","# flint-chart: interactions and updates on a chart\n\nA Flint chart is shared state. A reader's gesture becomes an event, a handler\nturns the event into an update `{ id, ops }`, updates compose as layers, and\nthe layers render into a state the host and the agent read back. An\napplication and an agent write the same kind of update directly. The full\nmodel is in the documentation:\nhttps:\u002F\u002Fmicrosoft.github.io\u002Fflint-chart\u002F#\u002Fdocumentation\u002Finteraction-introduction\n\nThe chart itself (`chart_spec`, `semantic_types`) is the `flint-chart-author`\nskill. Everything here runs on the Vega-Lite backend; a static render and the\nother backends leave `interaction_spec` and updates untouched.\n\n## What you produce (and what you do NOT)\n\n| The request says | You produce | Section |\n| --- | --- | --- |\n| A gesture or a behaviour on the chart: \"highlight on click\", \"zoom\", \"brush a period\", \"make it interactive\", \"add behaviour to this chart\". | One `interaction_spec`: `{ \"interactions\": [ ... ] }`, presets only. | Add interactions to the chart |\n| A change to a chart that is already on screen, from outside: \"point out Japan\", \"frame 2010 to 2020\", \"note this value\", \"reorder by value\", an agent answering a user with the chart. | One `ChartUpdate`: `{ \"id\", \"ops\": [ ... ] }`. | Update the chart |\n| Code around a chart: \"in my app\", \"on click open the record\", \"a panel that follows the hover\". | TypeScript against `flint-chart\u002Finteractive`: mount, presets as factories, `onChange`, `dispatch`. | Use Flint in an application |\n| Code across charts or over time: \"link these two charts\", \"a table that follows the selection\", \"play the years\". | `linkCharts` or `useLinkedCharts`; `dispatch` on a timer. On an MCP board, `link_spec` instead (board skill, \"Step 5 — connect them\"). | Link charts |\n| A gesture no preset gives: \"drag along the path\", \"draw a guess\", \"a rule for the box\", \"a lens the app draws\". | A `CanvasInteractionDef`: a trigger, affordances, a handler. | When no preset fits |\n\n- **DO** use only the names in this skill: preset types, options, ops, states,\n  triggers, cursors, event actions. Every name here is one the library ships.\n  When the request asks for a behaviour no preset gives, say which preset\n  comes closest and what it leaves out, then offer a bespoke definition if the\n  host runs code.\n- **DO** add behaviour only when the request or the session asks for it. A\n  static image or a bare spec gets no `interaction_spec`.\n\n## Which workflow the user is asking for\n\n- **Presets through the MCP tools:** put the object beside `chart_spec` as\n  `interaction_spec` in the chart input; `validate_chart` reports its warnings\n  without drawing, and `show` draws it with the same report. `list_interaction_presets`\n  with `chartType` lists the presets that chart type supports, what each\n  does, and its options.\n- **An update through the MCP tools:** there is no handle on the chart. Pass\n  the layers as the `updates` argument beside the input, to `validate_chart`\n  and `show`; see \"Step 4 — send the layer and read the result\".\n- **Code, only when the user asks for code:** mount with `mountChart`, or\n  `\u003CFlintChart>` in React; presets as factory calls, updates with\n  `applyUpdate`, state with `getState` and `onChange`. The programming API\n  is documented at\n  https:\u002F\u002Fmicrosoft.github.io\u002Fflint-chart\u002F#\u002Fdocumentation\u002Finteraction-api\n- **A bespoke interaction** needs a host that runs code. In the MCP view, the\n  answer is the closest preset and what it leaves out.\n\n## Add interactions to the chart\n\n### Step 1 — read the request and the chart\n\nThree things in the request decide the entries, read in this order:\n\n1. **What the user named.** A gesture or a preset in their words: \"brush a\n   period\", \"zoom\", \"toggle the legend\", \"highlight on click\". Each is one job.\n2. **The goal they stated.** \"Explore\", \"compare the regions\", \"find\n   outliers\", \"present this\", \"a control in my app\". A goal is the set of\n   questions the reader will ask of this chart; each question is one job.\n3. **Nothing named**, only \"interactive\", a live view, a dashboard. The chart\n   and the data say what the reader can do with it.\n\nThen read the chart spec: the chart type, the field on each channel, and the\nshape of the data. What a chart invites a reader to do:\n\n| The chart | Invites |\n| --- | --- |\n| A time series, one or many series | Comparing the series at one x; focusing a period; hiding a series when a colour field is bound; moving along the series when it is long. |\n| A scatter or a dense point chart | Focusing a cluster; moving closer; hiding a series when a colour field is bound. |\n| A bar, lollipop, or other category chart | Singling out a category; ranking by hand; hiding a series when a colour field is bound. |\n| A pie, donut, rose, or radar | Singling out a sector; focusing adjacent sectors. |\n| A heatmap or a calendar | Singling out a cell; reading a whole row or column. |\n| A map | Singling out a region; moving closer. |\n| A chart that is a control in an app | A click the app reads as a selection; a stronger gesture the app reads as an activation. |\n| A chart read without a pointer | Walking the chart from the keyboard. |\n\n### Step 2 — pick presets\n\nBefore any entry, a mounted chart already shows a tooltip with the mark's\nvalues on hover and nothing else. An entry adds one behaviour.\n\nTake one row of the map per job. A named gesture takes the row whose\n\"Gesture\" is that gesture. A question takes the row whose \"Answers\" is that\nquestion, on a chart whose \"Needs\" it offers; a broad goal takes two or three\nrows, the ones the chart invites most. When nothing is named, the rows the\nchart invites, two or three. A row that answers no job on the request stays\nout.\n\n| Preset | Gesture | Answers | Needs | Default reset |\n| --- | --- | --- | --- | --- |\n| **Single one thing out** | | | | |\n| `click-highlight` | Clicks a mark, a legend item, or an axis label; it stays emphasised and the rest mute. Shift-click adds. | Which mark, series, or category is this one? | elements | click-none, escape |\n| `click-group-focus` | Clicks a mark; every mark in its group stays emphasised. `groupBy` names the group field. | Which marks belong with this one? | elements | click-none, escape |\n| `hover-group-focus` | Hovers a mark; its group stands out until the pointer leaves. `groupBy` names the group field; `emphasis` is `\"auto\"` (a line widens and the rest stay as drawn; points, bars, and slices dim the rest), `\"highlight\"`, or `\"spotlight\"`. | Which marks belong with this one, at a glance? | elements | none |\n| `axis-highlight` | Clicks (or with `event: \"hover\"`, hovers) a discrete axis label; that category's marks emphasise. | Which marks share this row or column? | discrete axis | click-none, escape |\n| **Read values** | | | | |\n| `inspect` | Moves over the plot; a guide follows the pointer and the nearest mark on the chosen axes reads out. `mode` picks the axes and a comparison (`\"x>\"` reads every mark past the pointer). | What is at this x, this y, or beyond it? | elements | none |\n| `inspect-index` | Moves along the index axis; every series reads out at that position on one guide, each value labelled in its series colour and the position named at the axis. `show: \"single\"` reads one series and switches it through the legend; `seriesBy` names the series field when the chart draws one series at a time; `displayValue: false` keeps the guides and drops the labels. | What does each series hold at this point? | index axis | escape |\n| **Focus a region** | | | | |\n| `select` | Drags a rectangle; the marks inside emphasise. `mode: \"stateful\"` keeps the rectangle on screen to move and resize. | Which marks lie in this area? | elements, cartesian region | click-none, escape |\n| `lasso-select` | Draws a freehand region; the marks inside emphasise. | Which marks lie in this irregular cluster? | elements, cartesian region | click-none, escape |\n| `brush-x`, `brush-y` | Drags an interval along one axis; the marks in it emphasise. `mode: \"stateful\"` keeps the interval on screen to move and resize. On a polar chart the x brush is a sector. | Which marks fall in this period or this range? | elements, cartesian region | click-none, escape |\n| `brush-angle` | Drags an angular sector on a pie, donut, rose, or radar; the slices in it emphasise. | Which adjacent slices make up this share? | elements, angular region | click-none, escape |\n| `linked-brush` | Drags a region; the groups inside emphasise in every chart that mounts the same preset. `groupBy` names the shared field; `mode: 'stateful'` keeps the rectangle on screen to move or resize. | Where are these groups in the other charts? | elements, cartesian region | click-none, escape |\n| **Move the view** | | | | |\n| `navigate` | Drags to pan; wheels or pinches to zoom a continuous axis. `axes` limits it; `pan: false` leaves the plot drag free. On a map it moves the projection; on a globe (a world Choropleth with `projection: \"orthographic\"`) a drag turns the sphere and the zoom stays centred. On a map, `geometry.domain.focus` names the region at the centre. | What is in this part of the range, closer? | navigation | double-click |\n| `brush-zoom` | Drags a rectangle; the chart flies into it. | What is in this box, closer? | navigation | double-click, escape |\n| **Reduce or rearrange** | | | | |\n| `legend-toggle` | Clicks a legend item; its series hides or returns. Hidden series are a setting and survive every reset. | What does the chart look like without this series? | discrete legend | none |\n| `drag-reorder` | Drags a discrete axis label to a new place; the categories reorder. | How do these rank in the order I choose? | reorderable axis | none |\n| `filter-controls` | Picks values in controls beside the chart; the rows that fail are removed, or muted with `mode: \"highlight\"`. One control per listed field. | What does the chart show for this subset of a field it does not draw? | inline data | none |\n| **Mark for the reader** | | | | |\n| `click-annotate` | Clicks a mark; a note with its value pins to it. | What is this value, left on the chart? | elements | click-none, escape |\n| **Hand a gesture to the app** | | | | |\n| `double-activate` | Double-clicks a mark; the mark activates and the host reads it. | Which record should the app open? | elements | click-none, escape |\n| `long-press` | Holds a mark (500 ms); the mark activates. The touch spelling of the same activation. | Which record should the app open, on touch? | elements | click-none, escape |\n| `context-activate` | Right-clicks or long-presses a mark; the host receives a context target and draws its own menu. | What can the app do with this mark? | elements | none |\n| `context-menu` | The reader picks marks and a menu of the app's actions opens beside them; picking one emits `menu-select` with the item's id and the marks. `gesture` picks how: `rectangle` (default), `lasso`, `brush-x`, `brush-y`, `click`, `right-click`, `long-press`. `items` is required: `[{ \"id\", \"label\" }]`. One entry; it brings its own selection. | What can the app do with these marks? | elements, cartesian region | click-none, escape |\n| **Read without a pointer** | | | | |\n| `accessible-navigation` | Tabs into the chart and walks titles, axes, legends, facet headers, series, and marks with the arrow keys; Enter goes a level in, and inside a level Tab steps through it before moving on. Each step says what the element is and emphasises its data. Space on a focused element runs the click presets. | What is on this chart, from the keyboard or a screen reader? | elements | none |\n\nWithin a family the rows answer neighbouring questions, so one chart takes\none of them: `inspect` reads the marks near the pointer, `inspect-index` reads\nevery series at one index; `select` takes an area, `brush-x` a period, `lasso`\na shape; `navigate` moves continuously, `brush-zoom` jumps into a box;\n`double-activate`, `long-press`, and `context-activate` are three spellings of\n\"hand this mark to the app\" for a mouse, a finger, and a menu.\n\n\"Needs\" is what the chart type offers; `list_chart_types` has already applied\nit to the list it returns. The data confirms a few at mount, in Step 3. A\nviewport a gesture commits, and a reset, fly over 400 ms;\n`\"transition\": { \"duration\": 0 }` or `\"resetTransition\": { \"duration\": 0 }`\njumps. Panning and wheel zooming follow the pointer.\n\n**`filter-controls`.** It draws real controls beside the chart and changes the\nrows the chart shows, so it answers a request that names a field to filter by\n(\"filter by region\", \"one year at a time\", \"let me pick the group\"), or a data\ncolumn the chart does not encode whose values are versions of the same chart:\na year in a snapshot scatter, a population group, a scenario. A field the\nchart already draws has its own preset: the colour field `legend-toggle`, a\ncontinuous axis `brush-x` or `navigate`, a category on an axis\n`click-highlight`; and the measure the chart plots is the chart. List `fields`\nexplicitly, one or two, three at most: unlisted, the preset picks up to four\ncategorical and temporal fields itself. One entry per chart. A fixed subset\nbelongs in the data, and slices side by side are a facet in the\n`flint-chart-author` skill.\n\n### Step 3 — resolve conflicts\n\nEach trigger has one owner. At mount, admission gives a trigger to the first\nentry that asks for it; a later entry that asks for the same trigger yields\nit with an `info` warning when it can keep the rest, or is dropped with a\nwarning when it cannot. The chart still renders. Four triggers are asked for\nby more than one preset:\n\n| Trigger | Asked for by | Free it with |\n| --- | --- | --- |\n| The plot drag | `navigate` with pan on, every region preset, `brush-zoom`, `drag-reorder` | `\"pan\": false` on `navigate` beside one region preset |\n| The double-click | `double-activate`, and any entry whose `reset` holds `double-click` (`navigate` and `brush-zoom` by default) | `\"reset\": [\"escape\"]` on `navigate` or `brush-zoom` |\n| The legend click | `legend-toggle`, `click-highlight`, `inspect-index` with `show: \"single\"` | `\"targets\": [\"mark\"]` on `click-highlight` |\n| The retained mark focus | `click-highlight`, `click-group-focus` | one of them |\n\n`click-highlight` yields a legend or an axis click on its own, with an `info`\nwarning; `targets` keeps the spec silent. `accessible-navigation` takes no\npointer trigger, so it sits beside any entry.\n\nThe data confirms what the chart type cannot promise. An entry that fails one\nof these at mount is dropped with a warning:\n\n- `legend-toggle` needs a colour field bound to a discrete legend.\n- `navigate` and `brush-zoom` need a continuous, unfaceted axis.\n- `drag-reorder` needs a discrete axis in the bound encodings.\n- `inspect-index` needs an index axis, which it finds itself: the temporal\n  axis, else the discrete axis beside a measure, else x. A horizontal bar\n  chart reads along y with no option; `axis` overrides the choice. `seriesBy`\n  names the series field when the chart draws one series at a time.\n- `click-group-focus`, `hover-group-focus`, and `linked-brush` need `groupBy`,\n  a bound field.\n\n### Step 4 — write and validate\n\nThe object is `{ \"interactions\": [ ... ] }`, beside `chart_spec` as\n`interaction_spec`. Every entry is\n`{ \"type\": \u003Cpreset>, \"id\"?: \u003Cstring>, \"options\"?: { ... } }`:\n\n- `type` is a preset from the map, supported by the chart type per\n  `list_chart_types`.\n- `options` holds the preset's own options; `list_interaction_presets` lists\n  every one with its type (see \"Preset options\"). `reset` is one of them: omit it for the default list in the map, set it to\n  free a trigger. A preset that keeps nothing (`hover-group-focus`, `inspect`,\n  `context-activate`) accepts no `reset`.\n- `id` sits on the entry and is needed only when one preset appears twice.\n- In code, the same entry is a factory call with the same options:\n  `clickHighlight({ targets: ['mark'] })`, `brushX()`, `navigate({ axes: 'x' })`.\n\n```json\n{\n  \"interactions\": [\n    { \"type\": \"click-highlight\", \"options\": { \"targets\": [\"mark\"] } },\n    { \"type\": \"navigate\", \"options\": { \"axes\": \"x\" } },\n    { \"type\": \"legend-toggle\" }\n  ]\n}\n```\n\n`validate_chart` checks the object without drawing, and `show` runs the same\ncheck when it draws. A malformed entry\nis an error: an unknown `type`, an option beside `type`, an `id` inside\n`options`, a bare string entry, a missing required option, an unsupported\n`reset` gesture, a duplicate `id`. A dropped or yielded entry is a warning\nthat names the entry, what it needed, and the chart type:\n\n```\nInteraction \"legend-toggle\" requires a discrete legend; Bar Chart has none. The interaction was dropped.\nInteraction \"click-highlight\" yields legend clicks to \"legend-toggle\".\n```\n\nFix or drop what it warns about. Return the bare object, strict JSON, unless\nthe user asks for commentary.\n\n### When no preset fits\n\nA host that runs code mounts a `CanvasInteractionDef` in `options.interactions`\nbeside presets: a trigger, what it affords, and a handler that returns the\nlayer for its id.\n\n```ts\nimport { rectangleTrigger, type CanvasInteractionDef } from 'flint-chart\u002Finteractive';\n\nconst timebox: CanvasInteractionDef = {\n  id: 'timebox',\n  eventSource: rectangleTrigger('contain'),\n  affordances: { plot: { cursor: 'region' } },\n  reset: ['click-none', 'escape'],\n  handle(event) {\n    if (event.action !== 'select-region' || event.phase !== 'commit') return null;\n    const x = event.geometry.domain?.x;\n    const y = event.geometry.domain?.y;\n    if (x?.kind !== 'interval' || y?.kind !== 'interval') return null;\n    const keep = seriesInsideBox(x.start, x.end, Number(y.start), Number(y.end)); \u002F\u002F host logic over host rows\n    return {\n      id: 'timebox',\n      ops: [{ op: 'set-style',\n        targets: keep.map((series) => ({ select: { key: { Series: series } } })),\n        value: { state: 'emphasized' } }],\n    };\n  },\n};\n```\n\n| Field | Role |\n| --- | --- |\n| `id` | Names the interaction in events and the layer it retains. |\n| `eventSource` | The trigger: what to capture and how to read it. |\n| `affordances` | The kinds of hit the handler receives, each with its cursor and hover; a hit of a kind not listed never reaches it. Keys: `mark`, `legend-item`, `axis-label`, `plot`. Cursors: `activate`, `drag`, `region`, `navigate`, `inspect`, `draw`. Hovers: `target` (the mark), `cohort` (its group). |\n| `handle(event, context)` | Returns the whole layer for this id, or `null` for no change. |\n| `reset` | Gestures that clear the layer: `click-none`, `double-click`, `escape`. A reset reports as a commit with no `interactionId`. It suits a layer of styles the state shows; an overlay or a data layer is cleared by the host. |\n\n**Triggers.**\n\n| Trigger | The reader does | `action` | `geometry.plot` | `geometry.domain` | Affordance key |\n| --- | --- | --- | --- | --- | --- |\n| `clickTrigger` | Clicks a mark, legend item, or axis label. | `click-element`, `click-legend`, `click-axis` | `point` | — | `mark`, `legend-item`, `axis-label` |\n| `hoverTrigger` | Moves over a mark. | `hover-element`, `hover-legend`, `hover-axis` | `point` | — | same |\n| `dragTrigger(tolerance?)` | Presses a mark, or a projectable overlay path, within `tolerance` px (12) and drags. | `drag` | `drag { start, current, delta }` | the pointer's values; `geometry.projection` while over a projectable overlay | `mark` |\n| `rectangleTrigger(match?, guide?)` | Drags a rectangle. | `select-region` | `rect` | `x`, `y` intervals | `plot` |\n| `xBrushTrigger(match?, mode?, guide?)`, `yBrushTrigger(…)` | Drags an interval along one axis. | `brush-x`, `brush-y` | `rect` | that axis's interval | `plot` |\n| `lassoTrigger(match?, guide?)` | Draws a freehand region; previews begin at the third point. | `select-lasso` | `polygon` | `points`: the stroke so far, in data values | `plot` |\n| `angularBrushTrigger(match?, mode?, guide?)` | Drags a sector on a polar chart. | `brush-angle` | `angular-sector` | — | `plot` |\n| `inspectTrigger(mode?)` | Moves over the plot. | `inspect-x`, `inspect-y`, `inspect-xy` | `point` | `x`, `y` values | `plot` |\n| `inspectIndexTrigger(axis?, show?, seriesBy?)` | Moves along the index axis. | `inspect-x`, `inspect-y` | `point` | the index value | `plot` |\n| `navigationTrigger({ axes?, pan?, zoom?, reset? })` | Drags, wheels, pinches. | `pan-viewport`, `zoom-viewport`, `reset-viewport` | `viewport { delta, factor, anchor }` | the viewport after the move | `plot` |\n| `contextTrigger`, `longPressTrigger(ms?)`, `doubleActivateTrigger` | Right-clicks, holds (500 ms), double-clicks a mark. | `context-element`, `long-press-element`, `double-activate-element` | `point` | — | `mark` |\n\n`match` is `'intersect'` (default) or `'contain'` and decides which marks the\nregion's `target` lists. `guide` styles the drawn shape; `false` hides it. A\nregion is ephemeral: its guide and its preview end at the commit. A brush or\n`select` with `mode: 'stateful'` keeps its region on screen and editable, and\n`event.operation` reads `create`, `move`, `resize-leading`, `resize-trailing`,\nor `clear`. Spread a trigger to tune it:\n`{ ...hoverTrigger, defaultAssistDistance: 28 }` acquires the nearest mark\nwithin 28 px. A chart admits a trigger when it offers what the trigger needs,\nper the map's \"Needs\"; a code definition the chart cannot honour throws at\nmount with a message that names what it needed.\n\n**The event.**\n\n```ts\ninterface CanvasInteractionEvent {\n  action: string;                                     \u002F\u002F from the table\n  phase: 'start' | 'preview' | 'commit' | 'cancel';   \u002F\u002F clicks and hovers skip start\n  operation?: string;                                 \u002F\u002F a stateful brush's edit, or pan | zoom | reset\n  geometry: { plot?, domain?, projection? };\n  target: { visual: { kind, role }, elements: [{ value, records? }] } | null;\n  modifiers?: { shift, ctrl, meta };\n  description?: { kind, type, content, text };  \u002F\u002F on focus-element from accessible-navigation\n}\n```\n\n- `target.elements[i].value` holds the mark's encoded values, derived fields\n  included; `records` holds the source rows when the runtime can prove them. A\n  region target lists every mark inside. A legend hit's `value` is a\n  `LegendTargetValue`: `{ channel, field, domain: { kind: 'value', value } }`,\n  or `{ kind: 'interval', start, end }` on a continuous legend. An axis-label\n  hit's `value` is an `AxisTargetValue`: `{ axis, field, value }`.\n- `geometry.domain` is inverted through the plot's scales: a brush on a time\n  axis gives `domain.x = { kind: 'interval', start: Date, end: Date }`, a linear\n  axis gives numbers, a discrete axis gives the category.\n- `description` arrives on `focus-element` while `accessible-navigation` walks\n  the chart: what the focused element is (`type`, such as \"Bar\") and what it\n  represents (`content`), with the spoken `text`. The walk's emphasis reports\n  through `onChange` as a `preview`.\n- `geometry.projection.kind === 'path'` during a drag over an overlay with\n  `projectable: true`: `segment.start.value` and `segment.end.value` are the\n  overlay rows on each side of the pointer and `segment.t` the fraction between\n  them.\n\n**The types.** `flint-chart\u002Finteractive` exports these, and `flint-chart\u002Freact`\nre-exports them. `onChange` receives a `ChartChange`: the gesture's own\n`target` and `geometry`, and the chart's `state` after it. Generated from the\nsource by `npm run gen:reference`:\n\n\u003C!-- event-types:start -->\n```ts\ninterface ChartChange {\n  \u002F\u002F `preview` while a gesture runs, `commit` for a committed change, `cancel` when a gesture ends with none.\n  phase: ChartChangePhase;\n  \u002F\u002F `host` for `updates`, `applyUpdate`, `clearUpdate` and `dispatch`; always `commit`.\n  source: ChartChangeSource;\n  changed: ChartStateFacet[];\n  \u002F\u002F The interaction behind the change: a reader's gesture, or an external interaction run by `dispatch`.\n  interactionId?: string;\n  action?: CanvasInteractionAction;\n  \u002F\u002F The gesture's own hit: the hovered point, or the marks at an inspected index.\n  target?: SemanticTarget | null;\n  \u002F\u002F The gesture's geometry: a brushed range, an index value, or the viewport after a move.\n  geometry?: CanvasInteractionEvent['geometry'];\n  \u002F\u002F The reset gesture a reader made: a click on nothing, Escape, or a double-click. Reported even when the chart held nothing to reset, so a host that shows a selection of its own can let it go.\n  reset?: InteractionResetGesture;\n  state: ChartState;\n  previous: ChartState;\n}\n\ninterface ChartState {\n  chartType: string;\n  \u002F\u002F The marks the chart emphasizes or focuses now, previews included.\n  selected: SemanticElement[];\n  \u002F\u002F The emphasized marks of each update, by update id; `selected` is their union.\n  entries?: ReadonlyMap\u003Cstring, ChartStateEntry>;\n  \u002F\u002F The legend values a toggle hides now.\n  hidden?: ChartHiddenValue[];\n  \u002F\u002F The domain the plot shows now, when the chart navigates.\n  viewport?: DomainGeometry;\n  \u002F\u002F The category window of each rail.\n  windows?: Partial\u003CRecord\u003C'x' | 'y', ChartCategoryWindow>>;\n  categoryOrder?: unknown[];\n  \u002F\u002F The notes the chart holds now, previews included; `inView` tells which ones it draws.\n  annotations?: ChartAnnotation[];\n  \u002F\u002F The active filter of each field, set by filter controls; absent fields show every value.\n  filters?: Readonly\u003CRecord\u003Cstring, FilterValue>>;\n}\n\ninterface SemanticTarget {\n  visual: {\n    kind: 'mark' | 'path' | 'region' | 'widget' | 'handle' | 'legend' | 'axis' | 'chart' | 'title' | 'header';\n    role: string;\n  };\n  elements: SemanticElement[];\n}\n\ninterface SemanticElement {\n  \u002F\u002F Values represented by the mark's channels, or by a semantic control such as a legend item.\n  value: Record\u003Cstring, unknown>;\n  \u002F\u002F Contributing input records when provenance is available; zero or many may support one value.\n  records?: Record\u003Cstring, unknown>[];\n}\n\ninterface LegendTargetValue {\n  channel?: string;\n  field?: string;\n  \u002F\u002F The entry's value on a reader's hit; read `domain`, which every legend target carries.\n  value?: unknown;\n  domain: LegendDomain;\n}\n\ntype LegendDomain =\n  | { kind: 'value'; value: unknown }\n  | { kind: 'interval'; start?: number; end?: number };\n\ninterface AxisTargetValue {\n  axis: 'x' | 'y';\n  field: string;\n  value: unknown;\n}\n\ninterface DomainGeometry {\n  x?: DomainCoordinate;\n  y?: DomainCoordinate;\n  points?: DomainPoint[];\n  \u002F\u002F The detail level a multi-level projected chart draws for this viewport.\n  level?: string;\n  \u002F\u002F On a multi-level projected chart, the coarsest-level region under the plot centre: its feature id and properties plus the joined row fields.\n  focus?: Record\u003Cstring, unknown>;\n}\n\ntype DomainCoordinate =\n  | { kind: 'value'; value: DomainValue }\n  | { kind: 'interval'; start: DomainValue; end: DomainValue };\n\ntype DomainValue = number | Date | string;\n\ninterface DomainPoint {\n  x?: DomainValue;\n  y?: DomainValue;\n}\n\ninterface ChartHiddenValue {\n  channel: string;\n  value: unknown;\n}\n\ntype FilterValue =\n  | { in: unknown[] }\n  | { range: [unknown, unknown] };\n```\n\u003C!-- event-types:end -->\n- `context` is the chart state plus `available`, every drawn mark, for\n  building refs, and `selected`, the current emphasis, for a toggle rule.\n\n**The handler.**\n\n- It returns the complete layer for the id on every `preview` and `commit`;\n  the runtime drops the preview on `cancel`. A `null` on a `preview` shows\n  nothing until the commit, which is how a rule applies to the committed box\n  only. A region gesture keeps a preview layer under the interaction's id\n  while the drag runs, so a commit after previews restates the layer; a\n  `null` there lets the last preview stand.\n- It maps geometry to the host's model and the model to ops. Host side\n  effects (state, panels, overlays the host draws) come from\n  `flint-interaction` or `onChange`, which carry the same event; the handler\n  itself returns ops and leaves the DOM to the runtime.\n- Triggers have one owner for code definitions as for presets; two code\n  definitions on one trigger throw at mount. `navigate({ pan: false })` goes\n  beside a region trigger.\n- An element drag owns the press on the marks it affords. A press there is a\n  `drag` with phase `start`; `target` is the pressed mark through every phase.\n  The layer returned at `start` is kept as the gesture's preview, and a release\n  without movement commits that preview with no further call. No click\n  definition fires on those marks, so the definition that drags a mark also\n  answers its press.\n- A click definition that affords `hover` on marks receives `preview` events\n  for the hover cue. Return `null` or an emphasis for them, and the layer on\n  `commit`.\n\n**Patterns.**\n\n| Pattern | Flow | Pieces |\n| --- | --- | --- |\n| Gesture rule | Flint in, Flint out | A region trigger reads `geometry.domain`; the host rule picks rows; `set-style` by key (the timebox above). |\n| Host-drawn detail | Flint in, custom out | `hoverTrigger` or `inspectTrigger` with a `null` handle; the host listens to `flint-interaction` for the resolved element and draws a lens, a cursor, or a badge over the mount. |\n| Application drives the chart | external in, Flint out | `externalInteraction` plus `dispatch` per frame: a playback year, a reveal progress. |\n| Drag along a path | press, then drag | One `dragTrigger()` definition and one layer. At `start` on a mark it returns the mark's path as a `set-overlay` `line` with `interactive: true, projectable: true` and `order`, plus `set-data` for the current position. On `preview` and `commit` with `geometry.projection` it returns the same two ops for the pointer's position. `intrinsicDomain` on the x and y fields holds the axes still across the swaps. |\n| Freehand stroke | draw | The rows to guess form their own series, and a mount update sets `opacity: 0` on that series, which keeps the axis. `lassoTrigger('contain', false)` with `plot: { cursor: 'draw' }` is the pen: `geometry.domain.points` become the rows of a `set-overlay` line. An `externalInteraction` reveals the truth as an overlay, then the host clears the hide. |\n| Chart-level zoom | wheel | When the layout itself must change (bar step, ticks, domain), filter the rows and `mountChart` again in a hidden layer; swap on `ready`. No preset re-lays out. |\n| Semantic zoom on a map | navigate | A Choropleth with `level: 'auto'` (US states, then counties) or with your own `levels: [{ value, url, feature?, enter?, exit? }]` (one GeoJSON or TopoJSON object per level, rows joined on the feature `id` or `featureKey`, `baseMapUrl` drawn beneath as land without data), or a Map with `levelField` and `levels`; `navigate()`, and `set-viewport { region: { key } }` with a `transition` to fly into a region. Navigation events carry `geometry.domain.level` and `focus` (the coarsest region under the centre). A hover reaches the host through a hover affordance (`hover: 'target'`); a handler that returns `null` leaves the map as drawn. |\n\nHost-drawn detail, the shape of it:\n\n```ts\nconst probe: CanvasInteractionDef = {\n  id: 'probe',\n  eventSource: { ...hoverTrigger, defaultAssistDistance: 28 },\n  affordances: { mark: { hover: 'target' } },\n  handle() { return null; },\n};\ncontainer.addEventListener('flint-interaction', (e) => {\n  const { interactionId, event } = (e as CustomEvent\u003CFlintInteractionEventDetail>).detail;\n  if (interactionId !== 'probe' || event.phase !== 'preview') return;\n  const row = event.target?.elements[0]?.records?.[0];\n  if (row) lens.show(row);                 \u002F\u002F the host draws; the chart is unchanged\n});\n```\n\n## Update the chart\n\n### Step 1 — read the state\n\nA host hands the model the chart input and the state, as text or as data. The\nMCP chart view does this after every build (first mount, new `updates`, a\npanel edit) and every committed gesture, and each send replaces the last:\n\n```\nChart \"Life expectancy\": Line Chart, theme economist.\nEdited in panel: theme default → economist.\nEncodings: x = year, y = life, color = continent.\nInteractions: legend-toggle, navigate.\nAgent updates (yours):\n- asia:\n  ✓ emphasizes 3 marks where continent = Asia\n- oceania:\n  ✗ emphasizes marks where continent = Oceania: no row has continent = Oceania.\nUser interactions:\n- Hidden: Africa.\nThe y axis shows life from 70 to 86.\n```\n\nThe state is in field terms: what the chart is after the reader's panel edits,\neach op of your updates as applied (✓) or not and why (✗), what the reader\nselected, hid or filtered, and the viewport on view when the chart navigates.\n`validate_chart` and `show` return the same report before the\nreader touches the chart, with `User interactions: none.` From it, decide what the\nreader should see next: which rows stand out, what one note says, which range\nframes them. The rows you were given are the source of every value; the chart\nhands over no copy of the data, and nothing is computed that the chart cannot\nverify.\n\n### Step 2 — choose ops\n\nEvery change from outside is one update, `{ id, ops }`. Each op does one kind\nof thing:\n\n| Op | It is for | Effect | Shape | Needs |\n| --- | --- | --- | --- | --- |\n| `set-style` | The marks the chart already draws: emphasize some, fade some, hide some, recolour some. A highlight is this op with `state: 'emphasized'`. | Emphasize the targets and mute the rest; recolour; fade; hide. | `{ op, targets: UpdateTarget[], value: StyleSpec }` | marks |\n| `set-annotation` | One mark that should carry a note. | Pin a note on one mark; `null` clears it. | `{ op, target: UpdateTarget, value: { text, anchor?: 'segment' \\| 'point' } \\| null }` | one resolved element |\n| `set-viewport` | The range or region the chart should frame. | Frame a continuous domain. | `{ op, axes: 'x' \\| 'y' \\| 'xy', value: { x?: [lo, hi], y?: [lo, hi] } }`; on a multi-level map `value: { region: { key } }` | `navigate` or `brush-zoom` mounted on that axis |\n| `set-order` | The order of the categories on a discrete axis. | Reorder the axis; list every category in the order wanted. | `{ op, scope: 'category', field, values: unknown[] }` | `drag-reorder` mounted on that axis |\n| `set-overlay` | Marks the chart does not draw: a reference line, a threshold band, a forecast, a sketch of rows. | Draw new rows through the plot's own scales. | `{ op, name, value: { mark, data: { values }, encodings: { x?, y?, x2?, y2?, order?, color?, text? }, role, interactive?, projectable?, style? } \\| null }` | x and y scales |\n| `set-freeform-overlay` | A custom shape with no data behind it. | Draw SVG, or a clone of marks, over the plot. | `{ op, name, value: { coordinateSpace: 'plot' \\| 'renderer', body: [{ type: 'svg', content } \\| { type: 'clone', targets, transform?, opacity? }] } \\| null }` | — |\n| `set-data` | The rows the chart draws, for playback or a swap. | Replace every row; the rows carry every encoded field. | `{ op, source: 'main', value: { rows } }` | inline data |\n\n`StyleSpec` is `{ state?, opacity?, visible?, fill?, stroke?, strokeWidth?,\nmutedOpacity? }`. Two states matter from outside: `emphasized` keeps the\ntargets at full ink and mutes every other mark; `normal` with `targets: []`\nclears the layer. `opacity` on the targets fades a subset and leaves the rest\nalone; `opacity: 0` hides marks and keeps the axes, where `visible: false`\nremoves the rows and shrinks the scales. The mute level is one per chart,\nfixed at mount from the lowest `mutedOpacity` the mounted interactions carry\n(0.25 when none).\n\nOverlay marks are `line`, `point`, `rule`, `rect`, `text`. `encodings` name\nfields of the overlay's own rows, projected through the chart's x, y, and\ncolour scales. `style` takes `stroke`, `strokeWidth`, `strokeDash`, `fill`,\n`fillOpacity`, `opacity`, `pointRadius`, `fontSize`, `fontWeight`, `textAlign`\n(`start`, `middle`, `end`), `dx`, `dy`. A `line` takes its stroke and a `text`\nits fill from the `color` encoding; a `point` takes `style.fill`. A `text` sits on\nthe data, so it draws a halo in the chart's background; `stroke` sets another\ncolour, `strokeWidth` its width, and `stroke: 'none'` drops it, as a faint\nwatermark wants.\n\nTwo ops need a preset the chart mounts: `set-viewport` needs `navigate` or\n`brush-zoom` on that axis, `set-order` needs `drag-reorder`. The host says\nwhich are mounted, or the state shows `viewport` and `categoryOrder`. On a\nchart that mounts neither, say so and offer an emphasis; `set-data` replaces\nwhat the chart shows and is not a frame. Legend values belong to\n`legend-toggle`: no update hides or restores one, and a series fades with\n`opacity` instead.\n\n### Step 3 — name targets and values\n\nA target is one of three forms:\n\n```ts\n{ select: { key: { Country: 'Japan' } } }                 \u002F\u002F marks with these values\n{ select: { where: { Region: { in: ['Asia', 'Europe'] }, Month: { range: ['2025-03', '2025-06'] } } } }  \u002F\u002F a set of marks\n{ visual: { kind: 'mark', role: 'mark' }, elements }      \u002F\u002F elements from an event or the state\n```\n\n- A `where` describes a set, in the words filters use: each field `in` a list\n  or within a `range` (ends included; `null` leaves an end open), and every\n  field must hold. Use it for a period, a band of values, or several categories\n  at once, where a key would need one target per row. Only `set-style` takes a\n  `where`; a note or an order names one mark, so it takes a key.\n\n- A selector key names fields the chart encodes (`x`, `y`, `color`, `detail`,\n  `size`, …) and matches rows by equality, so `'2007'` and `2007` differ. One\n  key may cover many marks (`{ Continent: 'Africa' }` takes every African\n  mark); several fields narrow it; an annotation's key covers one. On a line\n  or area chart, a key on the series field covers the whole line and a key on\n  the x field covers the segment that starts at that row, so a run of segments\n  is one target per row. The last row of a line starts no segment; its key\n  takes the segment that ends there. A note on an x key marks the segment, for\n  a change from one row to the next (\"2019 → 2020: +4%\"); `anchor: \"point\"`\n  marks the row's own point, for its value (\"Dec 2022: $0.87B\"). A note on\n  the last row is always at its point.\n- A field the chart does not encode resolves to nothing. Encode the identity\n  field (`detail` on a scatter), or pass elements from `event.target`,\n  `state.selected`, or `context.available` through as `{ visual, elements }`,\n  as they are: an element's `value` carries derived fields on a bar, pie,\n  heatmap, or waterfall mark (a stack start, a sort index), so the element\n  resolves where a key copied from it does not.\n- A target that matches nothing is listed in the result as unresolved, and\n  nothing nearby is taken in its place.\n\nA key value, an overlay row, and a viewport bound hold what the parsed row\nholds: a CSV `120` is the number `120`. A temporal field (`Year`,\n`YearMonth`, `Date`, …) takes a date written the way the data writes it\n(`\"2014-01\"`, `2014` on a `Year` field) or UTC epoch milliseconds. On a\ndiscrete axis the value is the category. Overlay rows need the fields their\nmark reads: `line`, `point`, `text` read `x` and `y`. A `rule` with only `x`\nis a vertical line across the plot, and one with only `y` a horizontal\nthreshold; a `rect` with only `x` and `x2` is a full-height band, and one with\nonly `y` and `y2` a full-width band. With all four channels, `rule` and `rect`\nrun from (`x`, `y`) to (`x2`, `y2`). An overlay whose rows do not project is\nreported as unsupported.\n\n```json\n{ \"id\": \"agent\", \"ops\": [\n  { \"op\": \"set-style\", \"value\": { \"state\": \"emphasized\" },\n    \"targets\": [{ \"select\": { \"key\": { \"month\": \"2014-01\" } } }, { \"select\": { \"key\": { \"month\": \"2014-02\" } } }] },\n  { \"op\": \"set-overlay\", \"name\": \"threshold\", \"value\": { \"mark\": \"rule\", \"role\": \"reference\",\n    \"data\": { \"values\": [{ \"y\": 1e9 }] }, \"encodings\": { \"y\": { \"field\": \"y\" } } } },\n  { \"op\": \"set-overlay\", \"name\": \"period\", \"value\": { \"mark\": \"rect\", \"role\": \"band\",\n    \"data\": { \"values\": [{ \"start\": \"2022-11\", \"end\": \"2026-08\" }] },\n    \"encodings\": { \"x\": { \"field\": \"start\" }, \"x2\": { \"field\": \"end\" } }, \"style\": { \"fillOpacity\": 0.12 } } }\n] }\n```\n\n### Step 4 — send the layer and read the result\n\n**Ids are layers.** One id holds one concern: the agent's update, a story\nstep, a linked selection. Use one stable id, such as `agent`, and send the\nwhole layer each time:\n\n- An update with an id already retained replaces that layer, and carries only\n  what it lists: an omitted viewport flies home, an omitted order returns to\n  the input order, an omitted annotation disappears. The ops that should hold\n  are repeated; the ops the request replaces are dropped.\n- Layers compose in insertion order, and a replaced layer keeps its place:\n  styles accumulate across layers; for an annotation, an order, a data swap,\n  or an overlay name, the layer inserted last wins. One kind of op lives in\n  one layer: a drag and a playback that both swap the rows write the same id.\n- A canvas interaction retains its layer under its own `id`; a host may write\n  or clear that layer by the id, as a Reset button does.\n- A gesture reset (`click-none`, `escape`, `double-click`) clears preset\n  layers only. A host layer stays until the host clears it, or until a layer\n  with `set-style`, `targets: []`, `state: 'normal'` replaces it.\n- After `set-data` the marks are new; a style layer is sent again with the\n  data.\n\n**The result.** `applyUpdate`, `setUpdates`, and `dispatch` return a\n`ChartUpdateResult`: `{ status: 'applied' | 'partially-applied' |\n'unsupported', resolvedTargets, unresolvedTargets, unsupportedOps }`. An\nunresolved target has a wrong field or value; an unsupported op needs a\npreset the chart does not mount. What the chart shows is what the result says\nit shows: fix an unresolved key or drop an unsupported op before describing\nthe chart.\n\n**From the MCP view.** There is no `applyUpdate` to call. Pass the same layers\nas the `updates` argument beside the chart input, not inside it, to\n`validate_chart` and then to `show` as part of `chart`:\n\n```json\n{\n  \"data\": { \"values\": [ ... ] },\n  \"chart_spec\": { \"chartType\": \"Bar Chart\", \"encodings\": { \"x\": \"region\", \"y\": \"revenue\" } },\n  \"updates\": [\n    { \"id\": \"agent\", \"ops\": [\n      { \"op\": \"set-style\", \"targets\": [{ \"select\": { \"key\": { \"region\": \"East\" } } }], \"value\": { \"state\": \"emphasized\" } },\n      { \"op\": \"set-annotation\", \"target\": { \"select\": { \"key\": { \"region\": \"West\" } } }, \"value\": { \"text\": \"Lowest in Q3\" } }\n    ] }\n  ]\n}\n```\n\n`show` opens the chart with the layers applied, with the same look as a\nuser action. To show the chart in a new state, call `show` again with the same\ninput and the layers wanted now. Each `show` opens a new card with\nexactly those layers; earlier cards keep what they showed. The argument is state, and `interaction_spec` is behaviour, so\neach stays where it is. The reader's own brush or selection does not survive\nthe new view; the context message reports it in field terms, and it is\ncarried as a layer when it still serves the reader.\n\nCheck the input and `updates` with `validate_chart` first. A malformed layer\nor an invalid spec is an error, and `show` draws nothing for it. Otherwise the\nresult is the report above, one line per op, led by a count when an op did not\napply. The chart draws without a ✗ op: fix it in the layer (a key no row has,\nan overlay encoding written as `\"x\": \"start\"` instead of `{ \"field\": \"start\" }`,\na viewport with no navigation preset) before you call `show`, or tell the\nreader what is missing. The\nreader's later brushes and clicks reach you with their next message, not in\nthe same turn.\n\n## Use Flint in an application\n\n### Mount\n\n```bash\nnpm install flint-chart vega vega-lite vega-tooltip\n```\n\n```ts\nimport { mountChart, navigate, legendToggle } from 'flint-chart\u002Finteractive';\n\nconst surface = mountChart(container, input, {\n  backend: 'vegalite',\n  renderer: 'svg',\n  interactions: [navigate({ axes: 'x' }), legendToggle()],\n  chartId: 'sales',\n});\nawait surface.ready;\nconst warnings = await surface.warnings; \u002F\u002F interactions the chart could not honour\n\u002F\u002F …\nsurface.destroy();\n```\n\n| Member | What it does |\n| --- | --- |\n| `ready`, `warnings` | Promises: the first render; the dropped or yielded interactions. Every call below waits for `ready` itself. |\n| `applyUpdate(update, options?)` | Retains one update by id; replaces the layer with the same id. `options.transition.duration` animates a viewport. |\n| `setUpdates(updates)` | Replaces every retained update. |\n| `clearUpdate(id)` | Removes one layer. |\n| `dispatch(id, payload)` | Runs an `externalInteraction` handler and applies what it returns. |\n| `getState()` | What the chart shows now; `undefined` before `ready`. |\n| `onChange(callback)` | Every change to that state, after the render; returns the unsubscribe. |\n| `layout` | Once ready: `plot`, the plot box `{ width, height }` on a chart that draws one plot; `views`, each drawn view as `{ role, box: { x, y, width, height } }` (role `plot`, `text`, `tile` or `rule`; facet panels row by row); `block`, the box the views take with their axes and headers, without title and keys. Boxes are in pixels from the drawn chart's top left, read from what it drew, so they follow filters and resizes. `undefined` on charts with a category rail and on non-Vega-Lite backends. |\n| `refresh()` | Re-projects overlays after the host rescales the chart. |\n| `element`, `chartId` | The mount's root and the stable chart id. |\n| `destroy()` | Removes listeners, the renderer, and the DOM. |\n\n- `input` is a `ChartAssemblyInput`. Presets go in `input.interaction_spec` as\n  JSON or in `options.interactions` as factory calls. Both may appear; spec\n  entries mount first, and a code definition with a spec entry's `id`\n  replaces it with an `interaction_overridden` info warning.\n- A malformed `interaction_spec`, a repeated code `id`, or a code definition\n  the chart cannot honour fails the mount with an error that names it. A spec\n  entry the chart cannot honour is dropped with a warning instead.\n- `options.availableSize: { width?, height? }` is the room the host gives the\n  chart. It becomes the `canvasSize` ceiling on the sides given, so the chart\n  lays out for that room rather than being scaled into it.\n- `options.updates` holds retained updates present at the first render, for a\n  chart that starts with a part hidden or framed.\n- `renderer: 'svg'` for charts whose marks a host reads from the DOM, or under\n  a few thousand marks; `'canvas'` for maps and dense charts.\n- A `Year` or `Date` field makes a temporal axis whose rows hold `Date`s or\n  ISO strings; a bare number such as `2013` reads as milliseconds after 1970.\n  A `semantic_types` entry may be an object,\n  `{ semanticType, intrinsicDomain: [lo, hi] }`; the intrinsic domain holds an\n  axis still while the rows change.\n- `options: { addTooltips: false }` on the input is the switch for the\n  tooltip, for a chart whose hidden values it would give away.\n- Build with a bundler. The interactive entry reaches every backend through\n  lazy imports with no file extension; when `echarts`, `chart.js`, and\n  `plotly.js-dist-min` are not installed, mark them external (esbuild\n  `--splitting --external:echarts --external:chart.js --external:plotly.js-dist-min`;\n  Vite `build.rollupOptions.external`).\n- In React, use `\u003CFlintChart>` from `flint-chart\u002Freact` rather than mounting\n  in an effect; see \"In React\" below.\n\n### In React\n\n```tsx\nimport { FlintChart, type FlintChartHandle } from 'flint-chart\u002Freact';\nimport { clickHighlight } from 'flint-chart\u002Finteractive';\n\nconst chartRef = useRef\u003CFlintChartHandle>(null);\n\n\u003CFlintChart\n  ref={chartRef}\n  spec={input}\n  interactions={[clickHighlight({ targets: ['mark'] })]}\n  width=\"100%\"\n  fit=\"relayout\"\n  onChange={(change) => setSelected(change.state.selected)}\n  onError={(error) => report(error)}\n\u002F>;\n```\n\n| Prop | What it does |\n| --- | --- |\n| `spec` | The `ChartAssemblyInput`, `interaction_spec` included. A change of content remounts; rows compare by reference. |\n| `interactions` | Factory calls added to the spec's entries, or `(fromSpec) => definitions` for the full list. A factory rebuilt with the same id and options keeps the chart; a changed option remounts it. |\n| `updates` | Host updates, diffed by id. Passing even `[]` mounts the runtime on a chart with no interactions. |\n| `width`, `height`, `fit` | The box. `fit` is `scale-down` (default; never scales up), `crop` (natural size, clipped), or `relayout` (the box becomes the layout room; a resize remounts once it settles, so a selection does not survive it). |\n| `onChange`, `onInteraction` | The state after a change, and the raw gesture record. |\n| `onRender`, `onWarnings`, `onError` | Each mount's render and warnings; a failed mount, shown in the box as a muted error. |\n| `fallback` | Shown until the chart mounts. |\n| `ariaLabel`, `chartId` | `ariaLabel` applies in place; a new `chartId` remounts. |\n\nThe ref's `FlintChartHandle` holds `surface` (the mounted surface or `null`),\n`applyUpdate`, `clearUpdate`, `dispatch`, `getState`, and `refresh`. A remount\nkeeps the old chart on screen until the new one is ready.\n\n### Read the chart\n\n| `getState()` field | Holds |\n| --- | --- |\n| `selected` | The marks the chart emphasizes now, previews included. |\n| `entries` | The same marks per update id, with `layer: 'retained' \\| 'preview'`. |\n| `hidden` | Legend values a `legend-toggle` hides, as `{ channel, value }`. |\n| `viewport` | The domain on view after a pan or zoom; absent on a chart that does not navigate. |\n| `categoryOrder` | The order of the reorderable axis now. |\n| `windows` | Category rail windows, `{ start, count, total }` per channel. |\n\nEach element's `value` is the mark in field terms: a bar's category and\nmeasure, a legend item's `{ channel, field, domain }`, a histogram bar's\n`{ field, range }`. The host owns the rows and queries them from the value and\nthe geometry.\n\n`onChange` fires after every render with a `ChartChange`: `phase` (`preview`\nwhile a gesture runs, `commit` for a committed change or a host call, `cancel`\nwhen a gesture ends with nothing), `interactionId` and `action` (absent for a\nhost call and for a reset), the gesture's own `target` and `geometry`, and the\n`state` after. An overlay or a data swap leaves no trace in the state, so the\nhost keeps that model itself.\n\n```ts\nconst valuesOf = (elements: readonly SemanticElement[]) => elements.map((element) => element.value);\n\n\u002F\u002F A panel follows the gesture live, else shows what the chart shows.\nsurface.onChange(({ phase, target, state }) => {\n  const hit = valuesOf(target?.elements ?? []);\n  panel.show(phase === 'preview' && hit.length > 0 ? hit : valuesOf(state.selected));\n});\n\n\u002F\u002F An agent context reads committed state only: values and ranges, never rows.\nsurface.onChange(({ phase, state, geometry }) => {\n  if (phase !== 'commit') return;\n  sendContext({ selected: valuesOf(state.selected), range: geometry?.domain, hidden: state.hidden, viewport: state.viewport });\n});\n```\n\nThe container also emits a bubbling DOM event, `flint-interaction`, for every\ncanvas interaction, before the chart reacts: `detail.chartId`,\n`detail.interactionId`, and `detail.event`, the same `CanvasInteractionEvent`\na handler gets. It fires for a definition whose `handle` returns `null`, so it\nis the path for a host that draws its own response to a gesture.\n\nThings the surface holds fixed: no preset changes the layout, so a zoom never\nrecomputes the bar step, the ticks, or the domain (filter the rows and mount\nagain for that); the mute level is set at mount; Escape reaches a chart after\na press on it, and only that chart; a preset's toggle (`select`,\n`click-highlight`) needs Shift, Ctrl, or Meta, and a plain second click\nreplaces; a press on an overlay path resolves to no mark, so a `click-none`\nreset on the definition that drew the overlay clears it at that press.\n\n## Link charts\n\n`linkCharts` links mounted charts on the fields they share. A selection on one\nchart (a click, a brush, a group focus, a hover) becomes one condition, a part\nper field; every chart emphasizes the marks that meet it, the source included,\nthrough one `linked-selection` layer; a reset on any chart clears it everywhere.\n\n```ts\nimport { linkCharts, mountChart, clickHighlight, brushX } from 'flint-chart\u002Finteractive';\n\nconst link = linkCharts({\n  bars: { input: barsInput, surface: mountChart(barsEl, barsInput, { backend: 'vegalite', interactions: [clickHighlight()] }) },\n  trend: { input: trendInput, surface: mountChart(trendEl, trendInput, { backend: 'vegalite', interactions: [brushX()] }) },\n}, { fields: ['region', 'month'] });\nfor (const [id, chart] of Object.entries(await link.links)) console.log(id, chart.notes); \u002F\u002F what each chart left out\nlink.onCondition(({ condition }) => table.filter(condition)); \u002F\u002F { region: { in: ['Asia'] }, month: { range: [start, end] } }\n\u002F\u002F link.clear(); link.destroy();\n```\n\n**A table, a filter or any control beside linked charts** joins the same\ncondition: `link.select(parts)` sets parts as if a chart had selected them, and\n`onCondition` gives the table what to show. One condition, so a row click, a\nregion picker and a brush combine (AND) and a reset on any chart clears them all.\n\n```ts\nrow.addEventListener('click', () => link.select({ country: { in: [row.dataset.country] } }));\nregionPicker.addEventListener('change', (e) => link.select({ region: e.target.value ? { in: [e.target.value] } : null })); \u002F\u002F null lets go\nlink.onCondition(({ condition }) => renderTable(rows.filter((r) => meets(r, condition))));\n```\n\n- `select` only takes fields in `fields`; a chart shows a part when its marks\n  carry the field (encode it, or put it on `detail`).\n- The link emphasizes the matching marks; to drop rows instead (a filter), give\n  the charts filtered data as below.\n\n```tsx\nimport { FlintChart, useLinkedCharts } from 'flint-chart\u002Freact';\n\nconst link = useLinkedCharts({ bars: barsInput, trend: trendInput }, { fields: ['region', 'month'] });\n\u003CFlintChart spec={barsInput} {...link.chart('bars')} \u002F>;\n\u002F\u002F link.select({ region: { in: [\"Asia\"] } }) from a table or control; link.clear()\n\u003CFlintChart spec={trendInput} {...link.chart('trend', { onChange: setPageState, updates: myUpdates })} \u002F>;\n\u002F\u002F the chart's own onChange, onRender and updates go through chart(id, own); a prop after the spread replaces the link's\n\u002F\u002F link.condition.condition is the same condition; filtered rows in `spec` keep it\n```\n\n- A chart receives a field when its marks carry it (encode it, on `detail` if\n  nothing else shows it), and sends through each selecting interaction whose\n  subject includes a connected field. `{ charts: { trend: { receive: false } } }`\n  keeps a chart as drawn; `send: false` keeps its selections to itself.\n- The link emphasizes; it never filters or re-aggregates. To filter, read the\n  condition (`onCondition`, `link.condition`) and give each chart its rows:\n  beside `linkCharts`, a `set-data` update (a new spec remounts the chart, and\n  the link holds the old surface); beside `useLinkedCharts`, a `spec` with the\n  filtered rows.\n- `setUpdates` replaces every layer, the link's included, until the condition\n  next changes; use `applyUpdate` and `clearUpdate` beside a link.\n- `linkCharts` checks the charts with the validator, loaded on first use; pass\n  `{ links }` from `validateLinks` (`flint-chart\u002Fvalidate`) to skip that load.\n\nFor a link `linkCharts` does not express (a payload each chart reads its own\nway), wire `onChange` to an `externalInteraction` on the other charts:\n\n```ts\nconst linked = externalInteraction\u003C{ countries: string[] }>({\n  id: 'linked',\n  handle: ({ countries }) => ({\n    id: 'linked',\n    ops: [{ op: 'set-style', targets: [{ select: { where: { Country: { in: countries } } } }], value: { state: 'emphasized' } }],\n  }),\n});\nsource.onChange(({ phase, interactionId, state }) => {\n  if (phase !== 'commit' || !interactionId) return;       \u002F\u002F host writes carry no id: no loop\n  void other.dispatch('linked', { countries: state.selected.map((element) => String(element.value.Country)) });\n});\n```\n\nLinked charts share one `theme_spec` so a value has one colour everywhere.\n\nThe same `dispatch` drives a chart over time: a playback loop dispatches one\nframe per animation frame, and the handler returns the whole layer for that\nframe.\n\n## Worked examples\n\nIn each example `data` is a placeholder; the host binds real rows.\n\n### Presets: a named gesture\n\nUser: \"An interactive chart that I can brush.\" A line chart of `sales` by\n`month`, one series.\n\n```json\n{ \"interactions\": [ { \"type\": \"brush-x\" } ] }\n```\n\nThe user named one job, a brush along the period axis, and the row for it is\n`brush-x`. Hover already reads the values.\n\n### Presets: a goal on a category chart\n\nUser: \"Which regions stand out? Let me look.\" A bar chart of `revenue` by\n`region`, no colour field.\n\nThe reader singles out a region and compares orders. `click-highlight` singles\none out; `drag-reorder` lets the reader rank by hand. No colour field is\nbound, so `legend-toggle` has nothing to work with. Both presets ask for a\ndifferent trigger, a click on a bar and a drag on an axis label.\n\n```json\n{\n  \"interactions\": [\n    { \"type\": \"click-highlight\" },\n    { \"type\": \"drag-reorder\" }\n  ]\n}\n```\n\n### Presets: a multi-series line chart in an explore session\n\nUser: \"Help me understand how these three regions moved.\" The chart:\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": { \"month\": \"YearMonth\", \"sales\": \"Amount\", \"region\": \"Region\" },\n  \"chart_spec\": { \"chartType\": \"Line Chart\", \"encodings\": { \"x\": { \"field\": \"month\" }, \"y\": { \"field\": \"sales\" }, \"color\": { \"field\": \"region\" } } }\n}\n```\n\nThe reader compares the series at one month, hides a region, and points at a\nperiod. `inspect-index` reads every series at one month; `legend-toggle` has a\ncolour field to work with; `brush-x` focuses a period. `inspect-index` with\n`show: \"all\"` asks for no legend click, so it sits beside `legend-toggle`.\n\n```json\n{\n  \"interactions\": [\n    { \"type\": \"inspect-index\" },\n    { \"type\": \"legend-toggle\" },\n    { \"type\": \"brush-x\" }\n  ]\n}\n```\n\n### Presets: a chart that drives the app\n\nUser: \"In my dashboard, a click selects the country and a double-click opens\nits page. Zooming the y axis would help too.\" A scatter of `gdp` by `life`\nwith `color: continent` and `detail: country`.\n\n`navigate` holds the double-click by default for its reset, and\n`double-activate` needs it; `reset: [\"escape\"]` frees it. `click-highlight`\ntakes `targets: [\"mark\"]` so the app's selection comes from marks only.\n\n```json\n{\n  \"interactions\": [\n    { \"type\": \"click-highlight\", \"options\": { \"targets\": [\"mark\"] } },\n    { \"type\": \"double-activate\" },\n    { \"type\": \"navigate\", \"options\": { \"axes\": \"y\", \"reset\": [\"escape\"] } }\n  ]\n}\n```\n\nThe app reads the selection from `onChange` on a `commit` whose `action` is\n`click-element`, and the activation from one whose `action` is\n`double-activate-element`.\n\n### ChartUpdate: an agent points out rows\n\nUser: \"Which Asian countries are past 80 years? Point them out.\" The chart is\nthe scatter above, with `navigate` on `y`, and the state says nothing is\nemphasized. The agent finds Japan and Singapore in its rows and answers:\n\n```json\n{\n  \"id\": \"agent\",\n  \"ops\": [\n    { \"op\": \"set-style\",\n      \"targets\": [\n        { \"select\": { \"key\": { \"country\": \"Japan\" } } },\n        { \"select\": { \"key\": { \"country\": \"Singapore\" } } }\n      ],\n      \"value\": { \"state\": \"emphasized\" } },\n    { \"op\": \"set-annotation\",\n      \"target\": { \"select\": { \"key\": { \"country\": \"Japan\" } } },\n      \"value\": { \"text\": \"Japan, 82.6 years\" } },\n    { \"op\": \"set-viewport\", \"axes\": \"y\", \"value\": { \"y\": [70, 86] } }\n  ]\n}\n```\n\n`country` resolves because it is encoded on `detail`. The result returns\n`applied` with two resolved targets; a key with `\"Country\"` would come back\nunresolved, and the agent fixes the spelling before it describes the chart.\n\n### Code: two linked charts\n\nTwo bar charts share a `Country` field: GDP per country, and population per\ncountry. A click on one emphasizes the same country on both.\n\n```ts\nimport { mountChart, clickHighlight, linkCharts } from 'flint-chart\u002Finteractive';\n\nconst mount = (el: HTMLElement, input: ChartAssemblyInput) =>\n  ({ input, surface: mountChart(el, input, { backend: 'vegalite', interactions: [clickHighlight({ targets: ['mark'] })] }) });\nconst link = linkCharts({ gdp: mount(gdpEl, gdpInput), population: mount(popEl, popInput) }, { fields: ['Country'] });\n```\n\nThe clicked chart drops its own emphasis once the link shows the condition, so\nboth charts show one selection. A `click-none`, Escape, or double-click on\neither chart clears it on both; the link's own writes are host changes, so no\nchart echoes them back.\n\n### Bespoke: a rule over the brushed box\n\nA line chart of `Series` by `Month`. The reader drags a box; the series whose\nevery point in that month range lies inside the y range stay emphasized. No\npreset applies a rule over series, so the definition is the `timebox` above:\n`rectangleTrigger('contain')`, a `plot` affordance with the `region` cursor,\na handler that acts on `select-region` at `commit`, and `reset:\n['click-none', 'escape']` so a click on empty plot clears it. It mounts beside\n`navigate({ pan: false })` when the chart also zooms, since both ask for the\nplot drag otherwise.\n\n## Validation checklist\n\nFor an `interaction_spec`:\n\n1. It parses as strict JSON and is a bare object with `interactions`.\n2. Every entry answers a job from Step 1: a gesture the user named, a\n   question their goal raises, or something the chart invites.\n3. Every `type` is in the chart type's list from `list_chart_types`, and every\n   option is under `options` with `id` on the entry.\n4. `groupBy` and `seriesBy` are present where required, and name bound fields.\n5. At most one entry owns the plot drag, the double-click, the legend click,\n   and the retained mark focus.\n6. `validate_chart` returns no warning.\n\nFor a `ChartUpdate`:\n\n1. Every `op` and `state` is one from the op table.\n2. Every selector key names encoded fields with values that exist in the rows.\n3. The update is the whole layer for its id; clearing uses `targets: []` with\n   `state: 'normal'`.\n4. Ops that need a mounted preset have it, or `unsupportedOps` has been read\n   and acted on.\n5. The result came back `applied` before you said what the chart shows.\n\nFor code:\n\n1. Every trigger, cursor, and field name is one from this skill or the\n   installed `flint-chart\u002Finteractive` types.\n2. Every definition declares `affordances` for the hits its trigger yields.\n3. No two definitions, and no definition and spec entry, share a trigger or\n   an `id`.\n4. The surface is destroyed on unmount, and every `applyUpdate` or `dispatch`\n   result is read.\n\n## Preset options\n\n`list_interaction_presets` returns every preset with what it needs from the\nchart, its reset gestures, and every option it accepts under `options`, with\neach option's type and note; `type` narrows it to one preset and `chartType`\nto the presets a chart type supports. In code, the same options are the\nfactory's parameter type in `flint-chart\u002Finteractive`. `id` is never an\noption: it sits on the entry. A `ResetGesture` is `click-none`,\n`double-click`, or `escape`. `dimOpacity` is the opacity of the muted marks,\n0.25 by default. A `guide` is `{ visible?, style? }`, or `false` to draw\nnothing while the gesture runs.\n",{"name":5,"description":7},"main","a794557384adcc3293a77043f6b9aff1c33d152c","agent-skills\u002Fflint-interaction-author",268,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart","https:\u002F\u002Fmicrosoft.github.io\u002Fflint-chart\u002F","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-10 13:52:42",{"source":8,"skillName":5,"commands":47,"prompt":57,"zipAvailable":58},[48,51,54],{"tool":49,"command":50},"npx","npx skills add microsoft\u002Fflint-chart --skill flint-interaction-author",{"tool":52,"command":53},"bunx","bunx skills add microsoft\u002Fflint-chart --skill flint-interaction-author",{"tool":55,"command":56},"pnpm","pnpm dlx skills add microsoft\u002Fflint-chart --skill flint-interaction-author","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002Fagent-skills\u002Fflint-interaction-author\u002FSKILL.md",true,[60,66,70,74,78,82,86,90,94,98,102,106,110,114,118,122,126,130,133,137,141,145,149,153,159,163,167,171,175,179,183,187,191,195,199,203,207,211,215,219,223,227,231,235,239,243,247,251,255,259,262,265,269,273,277,281,285,289,293,297,301,305,309,313,317,321,325,329,333,337,341,345,349,353,357,361,365,369,373,377,381,385,389,393,397,401,405,409,413,417,421,425,429,433,437,441,444,448,452,456,460,464,468,472,476,480,484,488,492,496,500,504,508,512,516,520,524,528,532,536,540,544,548,552,556,560,564,568,572,576,580,584,588,592,596,600,604,608,612,616,620,624,628,632],{"path":61,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":64,"sourceHtmlUrl":65},"copilot-studio\u002Fagent.sync.yaml","REFERENCE","参考文档",174,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fagent.sync.yaml",{"path":67,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":68,"sourceHtmlUrl":69},"copilot-studio\u002Fbehaviors\u002Faibast_account-setup.mcs.yml",1337,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fbehaviors\u002Faibast_account-setup.mcs.yml",{"path":71,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":72,"sourceHtmlUrl":73},"copilot-studio\u002Fbehaviors\u002Faibast_beneficial-ownership.mcs.yml",1262,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fbehaviors\u002Faibast_beneficial-ownership.mcs.yml",{"path":75,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":76,"sourceHtmlUrl":77},"copilot-studio\u002Fbehaviors\u002Faibast_document-checklist.mcs.yml",2144,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fbehaviors\u002Faibast_document-checklist.mcs.yml",{"path":79,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":80,"sourceHtmlUrl":81},"copilot-studio\u002Fbehaviors\u002Faibast_initiate-onboarding.mcs.yml",1568,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fbehaviors\u002Faibast_initiate-onboarding.mcs.yml",{"path":83,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":84,"sourceHtmlUrl":85},"copilot-studio\u002Fbehaviors\u002Faibast_kyc-verification.mcs.yml",1926,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fbehaviors\u002Faibast_kyc-verification.mcs.yml",{"path":87,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":88,"sourceHtmlUrl":89},"copilot-studio\u002Fbehaviors\u002Faibast_onboarding-status.mcs.yml",1292,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fbehaviors\u002Faibast_onboarding-status.mcs.yml",{"path":91,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":92,"sourceHtmlUrl":93},"copilot-studio\u002Fbehaviors\u002Faibast_onboarding-timeline.mcs.yml",1221,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fbehaviors\u002Faibast_onboarding-timeline.mcs.yml",{"path":95,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":96,"sourceHtmlUrl":97},"copilot-studio\u002Fbehaviors\u002Faibast_risk-assessment.mcs.yml",1232,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fbehaviors\u002Faibast_risk-assessment.mcs.yml",{"path":99,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":100,"sourceHtmlUrl":101},"copilot-studio\u002Fbehaviors\u002Faibast_status-summary.mcs.yml",1132,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fbehaviors\u002Faibast_status-summary.mcs.yml",{"path":103,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":104,"sourceHtmlUrl":105},"copilot-studio\u002Fcapabilities\u002Fknowledge\u002Ffiles\u002Faibast_fs-customer-onboarding-controls-and-review.md",41331,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fcapabilities\u002Fknowledge\u002Ffiles\u002Faibast_fs-customer-onboarding-controls-and-review.md",{"path":107,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":108,"sourceHtmlUrl":109},"copilot-studio\u002Fcapabilities\u002Fknowledge\u002Ffiles\u002Faibast_fs-customer-onboarding-controls-and-review.md.mcs.yml",208,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fcapabilities\u002Fknowledge\u002Ffiles\u002Faibast_fs-customer-onboarding-controls-and-review.md.mcs.yml",{"path":111,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":112,"sourceHtmlUrl":113},"copilot-studio\u002Fcapabilities\u002Fknowledge\u002Ffiles\u002Faibast_fs-customer-onboarding-synthetic-records.md",27683,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fcapabilities\u002Fknowledge\u002Ffiles\u002Faibast_fs-customer-onboarding-synthetic-records.md",{"path":115,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":116,"sourceHtmlUrl":117},"copilot-studio\u002Fcapabilities\u002Fknowledge\u002Ffiles\u002Faibast_fs-customer-onboarding-synthetic-records.md.mcs.yml",204,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fcapabilities\u002Fknowledge\u002Ffiles\u002Faibast_fs-customer-onboarding-synthetic-records.md.mcs.yml",{"path":119,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":120,"sourceHtmlUrl":121},"copilot-studio\u002Fsettings.mcs.yml",6524,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fcopilot-studio\u002Fsettings.mcs.yml",{"path":123,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":124,"sourceHtmlUrl":125},"deployment.json",3702,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fdeployment.json",{"path":127,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":128,"sourceHtmlUrl":129},"EASY-MODE-COPILOT-CHAT.md",229,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002FEASY-MODE-COPILOT-CHAT.md",{"path":131,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":128,"sourceHtmlUrl":132},"EASY-MODE-PERSONLESS.md","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002FEASY-MODE-PERSONLESS.md",{"path":134,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":135,"sourceHtmlUrl":136},"EASY-MODE.md",2720,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002FEASY-MODE.md",{"path":138,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":139,"sourceHtmlUrl":140},"evals\u002Fcopilot-studio-preview-evidence.json",3263,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fcopilot-studio-preview-evidence.json",{"path":142,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":143,"sourceHtmlUrl":144},"evals\u002Fdataverse-draft-evidence.json",1687,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fdataverse-draft-evidence.json",{"path":146,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":147,"sourceHtmlUrl":148},"evals\u002Fhistory\u002F2026-09-10-upstream\u002FGLOBAL-INSTRUCTIONS.md",3115,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002FGLOBAL-INSTRUCTIONS.md",{"path":150,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":151,"sourceHtmlUrl":152},"evals\u002Fhistory\u002F2026-09-10-upstream\u002Findex.json",24344,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002Findex.json",{"path":154,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":157,"sourceHtmlUrl":158},"evals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F05-remove-web-search.png","ASSET","资产文件",401637,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F05-remove-web-search.png",{"path":160,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":161,"sourceHtmlUrl":162},"evals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F06-add-aibast-fs-customer-onboarding-controls-and-review.png",409011,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F06-add-aibast-fs-customer-onboarding-controls-and-review.png",{"path":164,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":165,"sourceHtmlUrl":166},"evals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F07-add-aibast-fs-customer-onboarding-synthetic-records.png",422298,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F07-add-aibast-fs-customer-onboarding-synthetic-records.png",{"path":168,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":169,"sourceHtmlUrl":170},"evals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F08-add-aibast-account-setup-02.png",425722,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F08-add-aibast-account-setup-02.png",{"path":172,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":173,"sourceHtmlUrl":174},"evals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F09-add-aibast-document-checklist-03.png",433032,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F09-add-aibast-document-checklist-03.png",{"path":176,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":177,"sourceHtmlUrl":178},"evals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F10-add-aibast-kyc-verification-01.png",438761,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F10-add-aibast-kyc-verification-01.png",{"path":180,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":181,"sourceHtmlUrl":182},"evals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F11-add-aibast-onboarding-status-04.png",445060,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F11-add-aibast-onboarding-status-04.png",{"path":184,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":185,"sourceHtmlUrl":186},"evals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F12-review-inventory.png",463094,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F12-review-inventory.png",{"path":188,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":189,"sourceHtmlUrl":190},"evals\u002Fhistory\u002F2026-09-10-upstream\u002Fvisual-checkpoints.json",19205,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fhistory\u002F2026-09-10-upstream\u002Fvisual-checkpoints.json",{"path":192,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":193,"sourceHtmlUrl":194},"evals\u002Fmanual-build-evidence.json",5481,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fmanual-build-evidence.json",{"path":196,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":197,"sourceHtmlUrl":198},"evals\u002Fmanual-pilot-review.json",7076,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fmanual-pilot-review.json",{"path":200,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":201,"sourceHtmlUrl":202},"evals\u002Fonepager-map.json",2809,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fonepager-map.json",{"path":204,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":205,"sourceHtmlUrl":206},"evals\u002Fsource-audit.json",2179,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fsource-audit.json",{"path":208,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":209,"sourceHtmlUrl":210},"evals\u002Ftranscripts.json",25538,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Ftranscripts.json",{"path":212,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":213,"sourceHtmlUrl":214},"evals\u002Fvisual-checkpoints.json",37319,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevals\u002Fvisual-checkpoints.json",{"path":216,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":217,"sourceHtmlUrl":218},"evidence-report.html",35648,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fevidence-report.html",{"path":220,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":221,"sourceHtmlUrl":222},"export-manifest.json",69187,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fexport-manifest.json",{"path":224,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":225,"sourceHtmlUrl":226},"exports\u002Ffs-customer-onboarding-copilot-studio-solution.zip",36746,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fexports\u002Ffs-customer-onboarding-copilot-studio-solution.zip",{"path":228,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":229,"sourceHtmlUrl":230},"exports\u002Ffs-customer-onboarding-deployment-settings.json",207,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fexports\u002Ffs-customer-onboarding-deployment-settings.json",{"path":232,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":233,"sourceHtmlUrl":234},"exports\u002Ffs-customer-onboarding-solution-export.json",1120,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fexports\u002Ffs-customer-onboarding-solution-export.json",{"path":236,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":237,"sourceHtmlUrl":238},"exports\u002FREADME.md",1592,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fexports\u002FREADME.md",{"path":240,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":241,"sourceHtmlUrl":242},"field-guide.html",40144,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Ffield-guide.html",{"path":244,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":245,"sourceHtmlUrl":246},"FIELD-GUIDE.md",15049,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002FFIELD-GUIDE.md",{"path":248,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":249,"sourceHtmlUrl":250},"FLOW.md",1109,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002FFLOW.md",{"path":252,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":253,"sourceHtmlUrl":254},"manual-tutorial.html",110902,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fmanual-tutorial.html",{"path":256,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":257,"sourceHtmlUrl":258},"manual\u002FGLOBAL-INSTRUCTIONS.md",5225,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fmanual\u002FGLOBAL-INSTRUCTIONS.md",{"path":260,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":104,"sourceHtmlUrl":261},"manual\u002Fknowledge\u002Faibast_fs-customer-onboarding-controls-and-review.md","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fmanual\u002Fknowledge\u002Faibast_fs-customer-onboarding-controls-and-review.md",{"path":263,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":112,"sourceHtmlUrl":264},"manual\u002Fknowledge\u002Faibast_fs-customer-onboarding-synthetic-records.md","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fmanual\u002Fknowledge\u002Faibast_fs-customer-onboarding-synthetic-records.md",{"path":266,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":267,"sourceHtmlUrl":268},"quest.html",172874,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fquest.html",{"path":270,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":271,"sourceHtmlUrl":272},"README.md",4634,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002FREADME.md",{"path":274,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":275,"sourceHtmlUrl":276},"screenshots\u002Fassisted\u002F01-review-build.jpg",106148,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F01-review-build.jpg",{"path":278,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":279,"sourceHtmlUrl":280},"screenshots\u002Fassisted\u002F02-fco-01.jpg",80196,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F02-fco-01.jpg",{"path":282,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":283,"sourceHtmlUrl":284},"screenshots\u002Fassisted\u002F03-fco-02.jpg",98825,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F03-fco-02.jpg",{"path":286,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":287,"sourceHtmlUrl":288},"screenshots\u002Fassisted\u002F04-fco-03.jpg",99432,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F04-fco-03.jpg",{"path":290,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":291,"sourceHtmlUrl":292},"screenshots\u002Fassisted\u002F05-fco-04.jpg",150902,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F05-fco-04.jpg",{"path":294,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":295,"sourceHtmlUrl":296},"screenshots\u002Fassisted\u002F06-fco-05.jpg",162696,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F06-fco-05.jpg",{"path":298,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":299,"sourceHtmlUrl":300},"screenshots\u002Fassisted\u002F07-fco-06.jpg",110298,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F07-fco-06.jpg",{"path":302,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":303,"sourceHtmlUrl":304},"screenshots\u002Fassisted\u002F08-fco-07.jpg",125405,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F08-fco-07.jpg",{"path":306,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":307,"sourceHtmlUrl":308},"screenshots\u002Fassisted\u002F09-fco-08.jpg",127665,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F09-fco-08.jpg",{"path":310,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":311,"sourceHtmlUrl":312},"screenshots\u002Fassisted\u002F10-fco-09.jpg",106947,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F10-fco-09.jpg",{"path":314,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":315,"sourceHtmlUrl":316},"screenshots\u002Fassisted\u002F11-confirm-draft.jpg",29474,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002F11-confirm-draft.jpg",{"path":318,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":319,"sourceHtmlUrl":320},"screenshots\u002Fassisted\u002Fannotated\u002F01-review-build.png",115067,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F01-review-build.png",{"path":322,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":323,"sourceHtmlUrl":324},"screenshots\u002Fassisted\u002Fannotated\u002F02-fco-01.png",84116,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F02-fco-01.png",{"path":326,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":327,"sourceHtmlUrl":328},"screenshots\u002Fassisted\u002Fannotated\u002F03-fco-02.png",100935,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F03-fco-02.png",{"path":330,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":331,"sourceHtmlUrl":332},"screenshots\u002Fassisted\u002Fannotated\u002F04-fco-03.png",102933,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F04-fco-03.png",{"path":334,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":335,"sourceHtmlUrl":336},"screenshots\u002Fassisted\u002Fannotated\u002F05-fco-04.png",147999,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F05-fco-04.png",{"path":338,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":339,"sourceHtmlUrl":340},"screenshots\u002Fassisted\u002Fannotated\u002F06-fco-05.png",161052,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F06-fco-05.png",{"path":342,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":343,"sourceHtmlUrl":344},"screenshots\u002Fassisted\u002Fannotated\u002F07-fco-06.png",114241,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F07-fco-06.png",{"path":346,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":347,"sourceHtmlUrl":348},"screenshots\u002Fassisted\u002Fannotated\u002F08-fco-07.png",128332,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F08-fco-07.png",{"path":350,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":351,"sourceHtmlUrl":352},"screenshots\u002Fassisted\u002Fannotated\u002F09-fco-08.png",135767,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F09-fco-08.png",{"path":354,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":355,"sourceHtmlUrl":356},"screenshots\u002Fassisted\u002Fannotated\u002F10-fco-09.png",105759,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F10-fco-09.png",{"path":358,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":359,"sourceHtmlUrl":360},"screenshots\u002Fassisted\u002Fannotated\u002F11-confirm-draft.png",29875,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fannotated\u002F11-confirm-draft.png",{"path":362,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":363,"sourceHtmlUrl":364},"screenshots\u002Fassisted\u002Fbrowserfilm.json",2115,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fbrowserfilm.json",{"path":366,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":367,"sourceHtmlUrl":368},"screenshots\u002Fassisted\u002Fcopilot-assisted-contact-sheet.jpg",287370,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fcopilot-assisted-contact-sheet.jpg",{"path":370,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":371,"sourceHtmlUrl":372},"screenshots\u002Fassisted\u002Fcopilot-assisted-walkthrough.gif",1651885,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002Fcopilot-assisted-walkthrough.gif",{"path":374,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":375,"sourceHtmlUrl":376},"screenshots\u002Fassisted\u002FREADME.md",468,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fassisted\u002FREADME.md",{"path":378,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":379,"sourceHtmlUrl":380},"screenshots\u002Fmanual\u002F01-create-blank-agent.jpg",59079,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F01-create-blank-agent.jpg",{"path":382,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":383,"sourceHtmlUrl":384},"screenshots\u002Fmanual\u002F02-name-agent.jpg",59370,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F02-name-agent.jpg",{"path":386,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":387,"sourceHtmlUrl":388},"screenshots\u002Fmanual\u002F03-enter-instructions.jpg",129834,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F03-enter-instructions.jpg",{"path":390,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":391,"sourceHtmlUrl":392},"screenshots\u002Fmanual\u002F04-save-instructions.jpg",132830,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F04-save-instructions.jpg",{"path":394,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":395,"sourceHtmlUrl":396},"screenshots\u002Fmanual\u002F05-remove-web-search.jpg",129434,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F05-remove-web-search.jpg",{"path":398,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":399,"sourceHtmlUrl":400},"screenshots\u002Fmanual\u002F06-add-aibast-fs-customer-onboarding-controls-and-review.jpg",131688,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F06-add-aibast-fs-customer-onboarding-controls-and-review.jpg",{"path":402,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":403,"sourceHtmlUrl":404},"screenshots\u002Fmanual\u002F07-add-aibast-fs-customer-onboarding-synthetic-records.jpg",134633,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F07-add-aibast-fs-customer-onboarding-synthetic-records.jpg",{"path":406,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":407,"sourceHtmlUrl":408},"screenshots\u002Fmanual\u002F08-add-aibast-account-setup-02.jpg",137352,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F08-add-aibast-account-setup-02.jpg",{"path":410,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":411,"sourceHtmlUrl":412},"screenshots\u002Fmanual\u002F09-add-aibast-beneficial-ownership-06.jpg",139016,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F09-add-aibast-beneficial-ownership-06.jpg",{"path":414,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":415,"sourceHtmlUrl":416},"screenshots\u002Fmanual\u002F10-add-aibast-document-checklist-03.jpg",140746,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F10-add-aibast-document-checklist-03.jpg",{"path":418,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":419,"sourceHtmlUrl":420},"screenshots\u002Fmanual\u002F11-add-aibast-initiate-onboarding-05.jpg",142347,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F11-add-aibast-initiate-onboarding-05.jpg",{"path":422,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":423,"sourceHtmlUrl":424},"screenshots\u002Fmanual\u002F12-add-aibast-kyc-verification-01.jpg",143661,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F12-add-aibast-kyc-verification-01.jpg",{"path":426,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":427,"sourceHtmlUrl":428},"screenshots\u002Fmanual\u002F13-add-aibast-onboarding-status-04.jpg",144406,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F13-add-aibast-onboarding-status-04.jpg",{"path":430,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":431,"sourceHtmlUrl":432},"screenshots\u002Fmanual\u002F14-add-aibast-onboarding-timeline-07.jpg",144436,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F14-add-aibast-onboarding-timeline-07.jpg",{"path":434,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":435,"sourceHtmlUrl":436},"screenshots\u002Fmanual\u002F15-add-aibast-risk-assessment-08.jpg",144074,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F15-add-aibast-risk-assessment-08.jpg",{"path":438,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":439,"sourceHtmlUrl":440},"screenshots\u002Fmanual\u002F16-add-aibast-status-summary-09.jpg",144094,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F16-add-aibast-status-summary-09.jpg",{"path":442,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":439,"sourceHtmlUrl":443},"screenshots\u002Fmanual\u002F17-review-inventory.jpg","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F17-review-inventory.jpg",{"path":445,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":446,"sourceHtmlUrl":447},"screenshots\u002Fmanual\u002F18-review-skills.jpg",135025,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F18-review-skills.jpg",{"path":449,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":450,"sourceHtmlUrl":451},"screenshots\u002Fmanual\u002F19-review-skills-more.jpg",139769,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F19-review-skills-more.jpg",{"path":453,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":454,"sourceHtmlUrl":455},"screenshots\u002Fmanual\u002F20-open-preview.jpg",33565,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F20-open-preview.jpg",{"path":457,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":458,"sourceHtmlUrl":459},"screenshots\u002Fmanual\u002F21-fco-01.jpg",79346,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F21-fco-01.jpg",{"path":461,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":462,"sourceHtmlUrl":463},"screenshots\u002Fmanual\u002F22-fco-02.jpg",115372,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F22-fco-02.jpg",{"path":465,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":466,"sourceHtmlUrl":467},"screenshots\u002Fmanual\u002F23-fco-03.jpg",96892,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F23-fco-03.jpg",{"path":469,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":470,"sourceHtmlUrl":471},"screenshots\u002Fmanual\u002F24-fco-04.jpg",145271,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F24-fco-04.jpg",{"path":473,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":474,"sourceHtmlUrl":475},"screenshots\u002Fmanual\u002F25-fco-05.jpg",152478,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F25-fco-05.jpg",{"path":477,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":478,"sourceHtmlUrl":479},"screenshots\u002Fmanual\u002F26-fco-06.jpg",145366,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F26-fco-06.jpg",{"path":481,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":482,"sourceHtmlUrl":483},"screenshots\u002Fmanual\u002F27-fco-07.jpg",135350,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F27-fco-07.jpg",{"path":485,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":486,"sourceHtmlUrl":487},"screenshots\u002Fmanual\u002F28-fco-08.jpg",103799,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F28-fco-08.jpg",{"path":489,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":490,"sourceHtmlUrl":491},"screenshots\u002Fmanual\u002F29-fco-09.jpg",133774,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F29-fco-09.jpg",{"path":493,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":494,"sourceHtmlUrl":495},"screenshots\u002Fmanual\u002F30-confirm-draft.jpg",29562,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002F30-confirm-draft.jpg",{"path":497,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":498,"sourceHtmlUrl":499},"screenshots\u002Fmanual\u002Fannotated\u002F01-create-blank-agent.png",64183,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F01-create-blank-agent.png",{"path":501,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":502,"sourceHtmlUrl":503},"screenshots\u002Fmanual\u002Fannotated\u002F02-name-agent.png",65686,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F02-name-agent.png",{"path":505,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":506,"sourceHtmlUrl":507},"screenshots\u002Fmanual\u002Fannotated\u002F03-enter-instructions.png",131289,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F03-enter-instructions.png",{"path":509,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":510,"sourceHtmlUrl":511},"screenshots\u002Fmanual\u002Fannotated\u002F04-save-instructions.png",129868,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F04-save-instructions.png",{"path":513,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":514,"sourceHtmlUrl":515},"screenshots\u002Fmanual\u002Fannotated\u002F05-remove-web-search.png",130708,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F05-remove-web-search.png",{"path":517,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":518,"sourceHtmlUrl":519},"screenshots\u002Fmanual\u002Fannotated\u002F06-add-aibast-fs-customer-onboarding-controls-and-review.png",134591,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F06-add-aibast-fs-customer-onboarding-controls-and-review.png",{"path":521,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":522,"sourceHtmlUrl":523},"screenshots\u002Fmanual\u002Fannotated\u002F07-add-aibast-fs-customer-onboarding-synthetic-records.png",137039,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F07-add-aibast-fs-customer-onboarding-synthetic-records.png",{"path":525,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":526,"sourceHtmlUrl":527},"screenshots\u002Fmanual\u002Fannotated\u002F08-add-aibast-account-setup-02.png",136484,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F08-add-aibast-account-setup-02.png",{"path":529,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":530,"sourceHtmlUrl":531},"screenshots\u002Fmanual\u002Fannotated\u002F09-add-aibast-beneficial-ownership-06.png",138594,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F09-add-aibast-beneficial-ownership-06.png",{"path":533,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":534,"sourceHtmlUrl":535},"screenshots\u002Fmanual\u002Fannotated\u002F10-add-aibast-document-checklist-03.png",139739,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F10-add-aibast-document-checklist-03.png",{"path":537,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":538,"sourceHtmlUrl":539},"screenshots\u002Fmanual\u002Fannotated\u002F11-add-aibast-initiate-onboarding-05.png",141285,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F11-add-aibast-initiate-onboarding-05.png",{"path":541,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":542,"sourceHtmlUrl":543},"screenshots\u002Fmanual\u002Fannotated\u002F12-add-aibast-kyc-verification-01.png",142410,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F12-add-aibast-kyc-verification-01.png",{"path":545,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":546,"sourceHtmlUrl":547},"screenshots\u002Fmanual\u002Fannotated\u002F13-add-aibast-onboarding-status-04.png",143135,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F13-add-aibast-onboarding-status-04.png",{"path":549,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":550,"sourceHtmlUrl":551},"screenshots\u002Fmanual\u002Fannotated\u002F14-add-aibast-onboarding-timeline-07.png",143131,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F14-add-aibast-onboarding-timeline-07.png",{"path":553,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":554,"sourceHtmlUrl":555},"screenshots\u002Fmanual\u002Fannotated\u002F15-add-aibast-risk-assessment-08.png",142727,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F15-add-aibast-risk-assessment-08.png",{"path":557,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":558,"sourceHtmlUrl":559},"screenshots\u002Fmanual\u002Fannotated\u002F16-add-aibast-status-summary-09.png",142767,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F16-add-aibast-status-summary-09.png",{"path":561,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":562,"sourceHtmlUrl":563},"screenshots\u002Fmanual\u002Fannotated\u002F17-review-inventory.png",147233,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F17-review-inventory.png",{"path":565,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":566,"sourceHtmlUrl":567},"screenshots\u002Fmanual\u002Fannotated\u002F18-review-skills.png",147990,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F18-review-skills.png",{"path":569,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":570,"sourceHtmlUrl":571},"screenshots\u002Fmanual\u002Fannotated\u002F19-review-skills-more.png",159127,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F19-review-skills-more.png",{"path":573,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":574,"sourceHtmlUrl":575},"screenshots\u002Fmanual\u002Fannotated\u002F20-open-preview.png",32974,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F20-open-preview.png",{"path":577,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":578,"sourceHtmlUrl":579},"screenshots\u002Fmanual\u002Fannotated\u002F21-fco-01.png",85577,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F21-fco-01.png",{"path":581,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":582,"sourceHtmlUrl":583},"screenshots\u002Fmanual\u002Fannotated\u002F22-fco-02.png",121075,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F22-fco-02.png",{"path":585,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":586,"sourceHtmlUrl":587},"screenshots\u002Fmanual\u002Fannotated\u002F23-fco-03.png",103257,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F23-fco-03.png",{"path":589,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":590,"sourceHtmlUrl":591},"screenshots\u002Fmanual\u002Fannotated\u002F24-fco-04.png",146030,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F24-fco-04.png",{"path":593,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":594,"sourceHtmlUrl":595},"screenshots\u002Fmanual\u002Fannotated\u002F25-fco-05.png",157863,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F25-fco-05.png",{"path":597,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":598,"sourceHtmlUrl":599},"screenshots\u002Fmanual\u002Fannotated\u002F26-fco-06.png",148970,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F26-fco-06.png",{"path":601,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":602,"sourceHtmlUrl":603},"screenshots\u002Fmanual\u002Fannotated\u002F27-fco-07.png",140601,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F27-fco-07.png",{"path":605,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":606,"sourceHtmlUrl":607},"screenshots\u002Fmanual\u002Fannotated\u002F28-fco-08.png",108539,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F28-fco-08.png",{"path":609,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":610,"sourceHtmlUrl":611},"screenshots\u002Fmanual\u002Fannotated\u002F29-fco-09.png",134131,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F29-fco-09.png",{"path":613,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":614,"sourceHtmlUrl":615},"screenshots\u002Fmanual\u002Fannotated\u002F30-confirm-draft.png",30475,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fannotated\u002F30-confirm-draft.png",{"path":617,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":618,"sourceHtmlUrl":619},"screenshots\u002Fmanual\u002Fbrowserfilm.json",5290,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fbrowserfilm.json",{"path":621,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":622,"sourceHtmlUrl":623},"screenshots\u002Fmanual\u002Fmanual-build-contact-sheet.jpg",1078030,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fmanual-build-contact-sheet.jpg",{"path":625,"resourceType":155,"resourceTypeDesc":156,"sizeBytes":626,"sourceHtmlUrl":627},"screenshots\u002Fmanual\u002Fmanual-build-walkthrough.gif",7069168,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002Fmanual-build-walkthrough.gif",{"path":629,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":630,"sourceHtmlUrl":631},"screenshots\u002Fmanual\u002FREADME.md",461,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002Fmanual\u002FREADME.md",{"path":633,"resourceType":62,"resourceTypeDesc":63,"sizeBytes":634,"sourceHtmlUrl":635},"screenshots\u002FREADME.md",522,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ffs-customer-onboarding\u002Fscreenshots\u002FREADME.md",[637,656,675,687],{"skillNo":638,"name":639,"nameZh":6,"description":640,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":641,"categories":642,"keywords":651,"publishedAt":31},"01M69AXSRQVDB9W7S83SRE0ZSP","flint-theme-author","Use when: creating, translating, refining, reviewing, or validating a custom Flint ThemeSpec from brand guidelines, websites, slide decks, publication references, design tokens, or an existing visual identity. Produce a reusable ThemeSpec JSON object for Flint Theme Lab without inventing fields or changing chart semantics.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002Fagent-skills\u002Fflint-theme-author\u002FSKILL.md",[643,646],{"categoryNo":644,"categoryName":645,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":647,"categoryName":648,"taxonomyType":649,"taxonomyTypeDesc":650},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[652,653,654,655],"flint theme author","design","brand guidelines","identity",{"skillNo":657,"name":658,"nameZh":6,"description":659,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":660,"categories":661,"keywords":670,"publishedAt":31},"01M69AXMC64Z7NCCRZKFENZ1AE","flint-chart-author","Use when: the user asks to make or render charts with flint-chart, visualize tabular data, generate a ChartAssemblyInput, validate\u002Frender through MCP, or add Flint to a JS\u002FTS project. Author the semantic spec, transform data before Flint when needed, install\u002Fimport Flint only when executable code is needed, and reserve backend-specific style tweaks for after compiling from Flint.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002Fagent-skills\u002Fflint-chart-author\u002FSKILL.md",[662,665,666,667],{"categoryNo":663,"categoryName":664,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":668,"categoryName":669,"taxonomyType":649,"taxonomyTypeDesc":650},"01JBSEEDCAT000000000000033","数据分析师",[671,672,28,673,674],"flint chart author","MCP","visualize","validate",{"skillNo":676,"name":677,"nameZh":6,"description":678,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":679,"categories":680,"keywords":683,"publishedAt":31},"01M69AXHW6ENKKHP24X54B9KT4","flint-board-author","Use when: several Flint charts should be read together in one view: a dashboard, KPI tiles over a trend, a side-by-side comparison, an overview with its detail; or a board on screen should change (add, retitle, resize, reconnect a chart). Produce one board spec whose headline, chart choice, layout and connected fields are decided, check and refine it with validate_board, then put it on screen once with show.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002Fagent-skills\u002Fflint-board-author\u002FSKILL.md",[681,682],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":668,"categoryName":669,"taxonomyType":649,"taxonomyTypeDesc":650},[684,28,685,686,674],"flint board author","dashboard","change",{"skillNo":688,"name":658,"nameZh":6,"description":659,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":689,"categories":690,"keywords":695,"publishedAt":31},"01M69AXEZX3YWNKXJ5Y5P76B0W","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002F.github\u002Fextensions\u002Fflint-chart\u002Fskills\u002Fflint-chart-author\u002FSKILL.md",[691,692,693,694],{"categoryNo":663,"categoryName":664,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":668,"categoryName":669,"taxonomyType":649,"taxonomyTypeDesc":650},[671,674,672,673,28]]