← AIMS360 Fashion & Consumer ERPCONTENT HISTORY

Update to AIMS360 Fashion & Consumer ERP

Snapshot Sep 30, 2026 · 22:56 UTC · version 2.0.1

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "aims360-dashboard",
  "description": "Data contract for rendering AIMS360 connector results visually — what every dashboard, report or chart built from orders/invoices data must state, qualify or suppress, plus the embedded AIMS360 brand palette, type and motifs in assets/aims360-brand.css. Defers to dataviz for chart craft. Use whenever visualising, charting, reporting, or dashboarding ANY AIMS360 orders/invoices output — even if the user doesn't say \"dashboard\".",
  "included_files": [
    {
      "relative_path": "assets/aims360-brand.css",
      "size_in_bytes": 79854
    }
  ],
  "skill_md_contents": "---\nname: aims360-dashboard\ndescription: Data contract for rendering AIMS360 connector results visually — what every dashboard, report or chart built from orders/invoices data must state, qualify or suppress, plus the embedded AIMS360 brand palette, type and motifs in assets/aims360-brand.css. Defers to dataviz for chart craft. Use whenever visualising, charting, reporting, or dashboarding ANY AIMS360 orders/invoices output — even if the user doesn't say \"dashboard\".\n---\n\n# AIMS360 data contract\n\nThis skill does not prescribe **layout** — composition and hierarchy are a design decision. Palette,\ntype and the brand motifs are not: they ship in `assets/aims360-brand.css` as constants (see **The\nbrand asset**), because they are not judgement calls.\n\nIt also sets **ambition** — see *Aim for a showpiece*. Layout stays free; look and reach do not.\n\nWhat no design brief can know: **which AIMS360 numbers are trustworthy, and what must travel\nalongside them.** Every rule below was learned from a live wrong answer.\n\n## The governing rule\n\nA dashboard makes numbers look authoritative — that is its job, and its hazard. The connector already\nqualifies its output; a visual layer must carry those qualifications, not quietly drop them for a\ncleaner composition.\n\n**If a figure arrives qualified and the design has nowhere to put the qualification, the design is\nwrong. Change the design, never the figure.**\n\n## Read the right fields\n\n- Totals, coverage and scope come from `structuredContent.resultSummary` and the answer text.\n- **`previewRows` is a sample, not the population** — 25 rows for a grouped result, 10 for a raw\n  list (`buildPreviewRows`, `result-formatter.ts`). Never sum it, never rank from it, never infer a\n  total from it. If you need a number that isn't in `resultSummary`, ask the connector again.\n- A metric's label comes from `totalBookedAmountLabel`, not from the metric key you asked for — the\n  server may have resolved it to a different one and it says so.\n\n## What must appear\n\n**Scope.** Population, window *with its date field*, and coverage — `complete`, `top N of M`, or\n`partial`. A ranking's subtotal is not the population: \"4,417\" was the parent count of fifteen\ngroups where the filter matched 4,908.\n\n**Exclusions.** If anything was removed — `LOOPOFFSET`, a bulk style, a DTC channel — say so. Silent\nexclusion is non-negotiable here.\n\n**Both sides of a comparison.** `+37.9%` alone is unreadable. Show current, prior, and the change,\nwith both periods labelled.\n\n## What must be qualified\n\nRender the value, then a marker resolving to a plain-English note — never a bare confident number,\nnever silent omission.\n\nKnown-bad right now:\n\n- **Any 2026 margin or cost figure.** Cost data is largely absent — one style's `totalCost` fell from\n  $62,344 to $804 on comparable volume. Margin is overstated and not comparable year over year.\n- **`fill_rate`.** Held upstream pending `pickQuantity` at a totalable grain.\n- **Anything described as \"wholesale\".** No such field exists — `retailInvoice` catches Shopify but\n  not Global-E (DTC, flagged `false`). Don't invent a definition unasked.\n- **Margin %** is measured over *costed* revenue, not all revenue. Say so wherever it appears.\n\nTwo rules this generalises to: **a ratio across two different cohorts is not a conversion rate** —\ninvoiced ÷ booked mixes orders entered this period with invoices billed this period; call it\nthroughput, not a funnel. And **a known-missing metric earns a visible tile, not only a sentence** —\nshow a dash in the KPI row with \"not exposed,\" so the gap is seen, not scrolled past. **That KPI row\nis the page's default view** — not a secondary tab. Observed live: fulfilment time and discounts were\nboth explicitly asked for, both correctly declined, and both landed only in a \"Data Quality\" tab\nnobody sees by default. The honest answer existed and was still effectively hidden.\n\n## What must be suppressed\n\nA ratio the connector returned as `null` is **undefined or impossible**, not zero — render an em dash\nwith the reason reachable. Zero is a measurement; `—` is the absence of one.\n\nBoth states occur live: a group with no cost data at all (undefined), and a $1 net sale carrying\n−$43 of margin (impossible — a share cannot exceed the whole it is a share of).\n\n## A bar's length is a claim about its number\n\nA number and the mark beside it are two renderings of the same fact — when they disagree, the reader\nbelieves the mark.\n\n**Length maps to the label's scale.** A row reading 73.6% fills 73.6% of its track — normalising to\nthe row maximum makes the largest full-width by construction, a rank chart that must not carry\npercentage labels. Observed live: a row labelled 73.6% rendered at width 100%.\n\n**One page, one rule — a chart done right does not exempt its neighbour.** Observed live, on a page\nbuilt under this very rule: a status chart correctly matched width to label; an order-source chart\nfive lines below it, on the same page, independently re-derived width from its own row maximum and\nreproduced the identical bug. Getting it right once is not the same as making it structural — use one\nshared computation for every bar chart on the page, not a bespoke one per section.\n\n**A row with two numbers still gets one bar.** Observed live: a growth chart's bar tracked dollar\nmagnitude while its label printed percentage change — a +1108% row rendered shorter than a +49% row.\nThe code's own comment reasoned \"the bar matches what these rows rank by, not what's printed\" — that\njustification *is* the bug. Pick one number to be both the bar's basis and the label; if both matter,\nprint both as text and let neither alone drive a bar it only half explains.\n\n**Zero draws nothing.** A minimum-width floor fabricates a mark for an empty row. Observed live: `$0`\nand `$1,182` both rendered at 0.60%. If the row must stay visible, the label carries the zero; the\ntrack stays empty.\n\n`—` and `0` both draw no bar — the label still tells them apart: one is a measurement, the other is\nthe absence of one.\n\n## Never invent a visual that reads as data\n\nA colour swatch, a fabric thumbnail or a category icon derived from a *code* is a guess rendered as\nfact. AIMS360 carries colour **codes and descriptions** — `SAM` / \"Sage Multi\" — and **no hex\nvalues**. A merchandiser reading a sage-green chip will believe that is the fabric.\n\nObserved live: a dashboard mapped `Sage #A8B49A`, `Midnight #232C46`, `Powder Blue #B7CBDD` from\nnothing but the code names. Names read as data; colour felt like design. It is not.\n\nShow the code and description, or derive tints from the brand palette.\n\nThe same applies to any visual implying a measurement the data doesn't contain — a trend arrow on\ntwo points, a target line with no target behind it, a forecast band.\n\n## Beware a trend drawn from a handful of units\n\nSmall absolute numbers make dramatic percentages — \"Amazon fell from 237 units in May to 108 in\nJuly\" is a 54% decline and also plain noise. State the absolute figures beside any rate, and flag a\nmovement too small to read.\n\n## What the payload licenses\n\nInteraction is bounded by what the connector returned, not by what looks fetchable —\n`answer.shape` says which:\n\n| `answer.shape` | rows carried | interaction licensed |\n|---|---|---|\n| `single_total` | none | hero figure only — no control; nothing behind it |\n| `rollup_by_time`, `all_groups` (≤30 groups) | complete | full client-side sort / filter / drill |\n| `top_n_with_tail` (>30 groups) | top 30 only | a re-sort re-orders 30, not the catalogue — render `tail` as \"+N more\" |\n| `raw_list`, `line_items` | bounded by `limit` | the richest surface: sort, multi-facet filter, type-ahead |\n\n**Row budget:** the host's ~150,000-character tool result is the real ceiling — roughly 200–400\nbytes/row, so ≈300 rows/section across the page.\n\n`answer.rows` is the only interactive source; `previewRows` stays forbidden regardless of row count.\n\n## A control is a claim\n\nA control is a claim about what this page can do, like a swatch is a claim about the data — the\nfailure is never that it exists, but that its claim isn't true.\n\n- **A re-sort claims a re-ranking** — ranked server-side on one metric, re-sorted to another, the\n  toggle re-orders *that subset*. Say it plainly: *\"ranks are server-side by units — switching to\n  Net $ re-sorts these 25, not the full catalog.\"*\n- **A filter claims a search** — over an embedded subset it searches what was fetched; name what it\n  covers.\n- **A refresh claims new data** — let it re-query (see *The page should answer back*). A \"Refresh\n  live\" button that re-renders the same JSON stays wrong.\n- **Every state-changing interaction has a way back** — a filter, sort or drill-in needs a visible\n  undo.\n- **Styling something to look interactive is also a claim.** A `:hover` state or a caption promising\n  \"click to sort\" with no listener behind it lies in CSS instead of prose. Observed live: three\n  sentences described sortable columns; the script wired zero `<th>` listeners.\n\nA control that would lie needs a control that tells the truth, not a page without controls. A page\nnobody can interrogate has not passed this test.\n\n## The brand asset\n\n`assets/aims360-brand.css` ships beside this file, embedded as `data:` URIs — Montserrat, the\nwordmark, colour tokens, dark-ground lifts, banded surfaces, the AIMS360 motifs, and the\nmeasure-to-colour assignment. **Inline its contents**; never link it or re-fetch the font or logo.\n\n**Draw the wordmark itself somewhere — typed text is not a substitute for it.** `--aims-logo` is the\nreal embedded PNG, not a colour or a font; defining the token and never consuming it (`var(--aims-logo)`\nmust appear at least once, typically as the nav's background-image) means the actual asset never\nrenders. Observed live: a build declared the token, then hand-typed \"AIMS360\" in Montserrat as the nav\nmark instead — the true wordmark sat inlined and unused the entire time.\n\n**A motif is a constant; a composition is a decision.** The hero gradient, dot diamond, eyebrow\nsignature and pill button are brand identity — use as shipped.\n\n- **Colour from a token, never a hex you chose** — `--aims-white`, `--aims-charcoal`,\n  `--aims-purple`, `--aims-pink`, `--aims-black`, `--aims-platinum`.\n- **`--aims-logo-grey` is not `--aims-platinum`** — 26 apart; substituting collapses the logo into\n  the chrome.\n- **On a dark ground, substitute `--aims-purple-on-dark` / `--aims-pink-on-dark`** — book values\n  measure 1.44:1 / 3.62:1 on `#151920`; purple's lift is mandatory, pink's a choice. Both defined.\n- **No positive/negative pair, no ramp past three.** Pink is the accent — \"bad\" misreads. A fourth\n  series needs a decision, not an invented tint.\n- **Artifacts block requests, not code.** A font CDN link, script, remote image or third-party\n  `fetch()` is silently dropped — no error. (Google Fonts was observed blocked here 2026-08-03 —\n  keep embedding.) Inline `<script>` runs; `claude.use(...)` is not third-party.\n\n## Chart craft comes from `dataviz`\n\nDo not re-derive chart craft here — the built-in **`dataviz`** skill governs form, marks, colour and\ntooltips; read it first (loaded in Claude Code; UNKNOWN on plain claude.ai — the clause below covers\nthat gap).\n\n- **Palette is ours** — `assets/aims360-brand.css`, dark-ground lifts included; three series, no\n  positive/negative pair.\n- **A tooltip may not carry a qualification alone** — `dataviz` says tooltips enhance, never gate;\n  binds hardest on caveats.\n- **A filter over an embedded subset scopes the fetch, not the catalogue** — layered on `dataviz`'s\n  layout rule.\n- **Refetch holds the previous render at reduced opacity**, scope line included.\n- **Chart form, compressed:** ranking → bars; composition → stacked or ring; movement → area with an\n  emphasised endpoint.\n\n`dataviz` decides how a chart is drawn; this file decides what a number is allowed to claim.\n\n## Aim for a showpiece\n\nBeing **trusted** and being **worth looking at** are separate jobs. Treat an AIMS360 dashboard as\n**editorial work** — someone will present it — but none of the honesty rules bend.\n\nLevers, in rough order of effect — reach for what the data supports:\n\n- **One hero moment** — a figure at a scale nothing else competes with, the most consequential thing\n  in the data. Four equal tiles is no argument.\n- **Use the whole palette** — six values plus the dark-ground lifts. Three reads as unfinished.\n- **The emblem is a ring** — a power button, four dots, already a gauge: draw coverage and\n  share-of-total in the brand's own geometry, not a generic donut.\n- **Compose densely** — tight grids, aligned baselines, real contrast between a headline figure and\n  its label.\n- **Consider the dark ground** — the lifted pink and purple on `--aims-surface-inverse` read more\n  striking than white, and are brand-legitimate.\n- **The nav is its own decision, not an extension of the hero.** Committing the hero to\n  `--aims-surface-inverse` doesn't obligate the nav to match it — a light nav over a dark hero reads\n  as considered contrast, not inconsistency.\n\n**If a qualification has nowhere to sit, the design is wrong** — a constraint on the composition, not\na ceiling on it: the next section is about giving qualifications more places to sit.\n\n## The page should answer back\n\nThese readers are AIMS360's customers — brand owners and merchandisers, not analysts handed a\nreport. A page that can't answer sends them back to chat.\n\nAn HTML page is interactive by default: the hover layer is part of the deliverable, not an upgrade.\nInline `<script>` runs — the CSP blocks *requests*, not code. Four levers, ascending cost:\n\n- **Explore what's shown** — every mark answers pointer and keyboard focus alike (covers touch too).\n  `dataviz` covers the rest: crosshair, per-mark tooltip, ≥24px hit targets, `textContent` never\n  `innerHTML`.\n- **Move around the page** — sticky nav, `scroll-behavior: smooth`, native `<details>`. Pure CSS.\n  Give it room to breathe: a cramped logo against the links reads as an afterthought, not a decision\n  — size the bar to the wordmark, not the other way round. **One sticky layer, not two.** A page's\n  own sticky nav is enough; do not also make table headers sticky. Observed live: a\n  `thead th{position:sticky}` offset to clear the nav still visually sliced through the first body row\n  as it scrolled past — the row was one of the tallest on the page, and no offset reliably reserves\n  clearance for whatever row is mid-transition. This can't be verified before publishing, so don't\n  stack it. Two sticky bars of page CHROME (a nav, then a filter toolbar) are fine — neither competes\n  with scrolling rows. The risk is specifically a second sticky layer that has to coordinate with\n  per-row CONTENT scrolling past it, not stacking sticky bars in general.\n- **Change the question** — `claude.use(\"mcp\")`, then `watchTool` for anything displayed, `callTool`\n  for a one-shot (read **`artifact-capabilities`** first; `null` is normal). **The scope line derives\n  from the response, never typed in** — a stale caption over live numbers is the worst bug here.\n- **Move a little** — 150ms transitions on hover/open; the emblem ring drawing once on load. Honour\n  `prefers-reduced-motion`.\n\nA disclosure is *somewhere to put a qualification* — a still page has the least room of any page you\ncan build. **Scope, coverage, exclusions and known-bad notes stay visible while any disclosure is\ncollapsed**; methodology or an em-dash's reason may live inside `<details>`, since the visible\nsummary — or the dash — is the marker. A qualification only reachable on hover has been dropped, not\nplaced.\n\n## At scale, organize — don't just stack\n\nMany parallel cuts (top / growing / declining / new / lapsed accounts, a dozen breakdowns, this-year\nand comparison views) don't have to become one long scroll.\n\n- **Group parallel cohorts as tabs, not stacked cards.** State the TRUE population even when a subset\n  renders — \"131 accounts qualify; top 12 shown,\" not just \"top 12.\"\n- **One filter rail can scope several already-fetched visuals** — a tier between filtering one\n  embedded subset and re-querying the connector. Show a live count: \"N of M rows · K filters on.\" A\n  filter wired to nothing is the sortable-header lie again, just harder to notice.\n- **One consolidated coverage section, alongside per-section captions, not instead of them.** Seven\n  scattered notes get missed; state coverage once, completely — available / partial / not exposed.\n  Any claim in it is computed, not typed — a hand-written \"these lists are complete\" is a bug the\n  moment one row of the underlying data disagrees; derive it from the same total the per-cut captions\n  already compute, never assert it separately.\n\nA tab hiding a caveat, or a filter count that lies, is the governing rule broken at a larger scale.\n\n## Before publishing\n\n- [ ] Scope stated — population, window and date field, coverage — from the response, never typed\n      in.\n- [ ] Exclusions named.\n- [ ] Every known-bad figure is qualified, readable without a pointer; scope stays visible while any\n      disclosure is collapsed.\n- [ ] No null rendered as zero, no zero drawing a mark.\n- [ ] Every bar, on every chart on the page, agrees length with its printed number — checked per\n      chart, not assumed from the first one.\n- [ ] Comparisons show both periods.\n- [ ] Nothing recomputed from `previewRows`; interactive data comes from `answer.rows`.\n- [ ] Every control's claim is true; a re-sorting toggle says what it re-sorts.\n- [ ] Every chart with marks answers hover and keyboard focus alike.\n- [ ] The page still argues as a static screenshot before any interaction.\n- [ ] Every headline figure carries its unit and period.\n- [ ] No invented swatch, thumbnail or icon standing in for data.\n- [ ] Every font, image and script embedded — **no external request anywhere**.\n- [ ] A subtotal is never sitting where a reader expects a total.\n- [ ] One thing is unmistakably the headline, more than three brand colours work, and the page\n      responds when pointed at — else revisit *Aim for a showpiece*.\n- [ ] Every claimed control (sortable header, filter, toggle) has a real listener behind it — a hover\n      style and confident copy are not enough.\n- [ ] Every \"top N of M\" / \"X of Y\" sentence — and any claim like \"these lists are complete\" —\n      matches the actual data, not a hand-typed guess.\n- [ ] Every explicitly-asked-for, known-missing metric has a dash tile in the DEFAULT-visible KPI\n      row — not only a sentence in a secondary tab.\n"
}

SHA-256: 48cf6984a54a96531b48a129b9aaec0a1896a57c9daf8328c3945e5461386c5f