← Cycles
Design · c128

Reports

activecreated 2026-11-03· last activity 2026-11-05· mode: assemblage. Reports are made from rollups the operator pages already compute, plus c78's report chassis. The window picker is the one new piece.

A time-windowed Reports section for qdash. It gives C-level people, auditors and others who see data but get no controls what the dashboard already shows, as documents they can hand on. The two categories are Security (Posture) and IT (Inventory, Deployment status). Every report reads one window: rolling, calendar or custom from–to, with a comparison period.

New in this cycle: The Compliance report

Why

qdash's /reports is a placeholder (DesignInProgress). The people who need to know how AI risk and rollout are trending don't work in the operator pages, and those pages don't agree on time:

  • Findings and DLP follow the 7d pill.
  • Posture, compliance and alerts are all-time.

Nothing offers a calendar period or a custom range. The problem registry's strongest reporting problem is "I can't hand my board or my auditor a report", recurring at Moto and Tiled.

Objective

Three things, mocked in the phase-4 lab, reviewed one at a time, then ported to qdash /reports:

  • A Reports catalog.
  • Three reports (Security · Posture, IT · Inventory, IT · Deployment status), each with one window in its header, and every section labelled in window, over window or as of.
  • A reusable window picker: rolling, calendar and custom presets, compare, data-range edges, and the window in the URL.

Done also means:

  • Print/PDF, CSV and JSON exports.
  • The picker extracted to the layer.
  • The backend gaps handed to Tyler: from/to, history beyond 90 days, snapshots, sensor version and health.

Scope

  • Cycle docs: cycles/c128.reports/ (plan, data inventory, thread files)
  • Lab mock:
    • app/pages/pages/phase4/mock/reports/ (catalog and three reports)
    • the window picker's demo page
    • a history extension to the lab world
  • c78 pieces carried forward: app/components/report/, app/data/report-fixtures.ts
  • Layer: the window picker (working name UxTimeWindow), plus the report header if it stabilises
  • qdash: pages/reports.vue and a composables/reports/ domain on feat/new-pages, ported after review
  • Out:
    • scheduled delivery and retained snapshots (recorded as follow-ons)
    • c78's Board report
    • framework evidence pages (c123)

Key Changes

  • kick-off (2026-11-03):
    • Cycle created from Mark's prompt.
    • Two read passes:
      • every qdash page's data and time handling
      • the layer and design pieces for time, history and export, prior report cycles, and the problems and personas in market-analyst
    • data-inventory.md maps each candidate section to its source, time basis and gaps.
    • plan.md sets out rules R1–R6, decisions D1–D10, four clarifying questions and Phases 0–7.
    • Waiting on Mark's rulings before Phase 1.
    • Uncommitted.
  • answers (2026-11-03): Mark answered the four clarifying questions. Details are in plan.md under Clarifying questions.
    • "Non-operator" is not a role. It means C-level and other people who see data but get no controls. Reports therefore hold no controls and must make sense on their own.
    • Findings/SLA is a section of Posture.
    • The Board report folds into the /reports catalog's top band: one sentence for the window, one highlight per report, and the trend in words. Its composite score is dropped.
    • History of 3–12 months comes when the backend is connected. The mock is designed for 12 months.
  • rulings (2026-11-03): Mark ruled D1–D10. Details are in plan.md under Decisions.
    • Defaults kept:
      • the mock goes in the phase-4 lab
      • rolling, calendar and custom windows, defaulting to the last 30 days
      • compare with the previous period
      • scope is the org, with an optional Team
      • print/PDF, CSV and JSON this cycle
      • c78 closes into c128
      • the org's timezone
    • D5 overturned: the window is shared with the global when: pill, not separate. The picker becomes the pill's panel in the layer Filter and in qdash useGlobalFilter, and every operator page has to take any window. The backend needs from/to for every page.
    • Shared default: 30 days everywhere (Mark). qdash DEFAULT_PERIOD moves from 7d to 30d at the port.
  • downloads move to Reports (2026-11-03): Mark ruled that downloads and reports are handled in Reports now.
    • The "Forensic why-trail & evidence export — preview" panel is gone from the lab's Compliance page (phase4/mock/compliance.vue), along with the subtitle's evidence-export sentence. GovernanceLiveComplianceEvidenceExport stays in the tree, unused, as the source for Phase 4.
    • At Mark's word, the panel is also out of qdash /compliance (crates/qdash/ui/app/pages/compliance.vue on feat/new-pages), along with the same subtitle sentence and the unused download binding.
      • Checks: npm run check is clean, and vitest passes 593/593. :3030/compliance renders with no console errors and no export panel.
      • components/compliance/EvidenceExport.vue and useCompliance().download stay in the tree, unused, as the source for the Reports port.
      • Until Reports ships, findings JSON/CSV can still be downloaded from the findings export card on /integrations.
      • qdash is uncommitted.
    • The old phase-0 mock /pages/compliance.vue is left as an archive.
    • c123's X4 (merging the two evidence exports) now lands in Reports.
  • Phase 1, window picker (2026-11-03): the picker is built design-local, and its demo is at /pages/phase4/time-window (a new Reports section on the Phase 4 index).
    • Model: app/utils/time-window.ts is pure, with no Vue. Spec → resolved civil dates in the org timezone, plus compare, coverage, labels and URL encoding.
      • The defaults (30d, previous period) are left out of the URL, so the old ?period=7d still reads.
      • scripts/c128-time-window-check.mts holds 9 checks: rolling, calendar, to-date compare, the month-end clip, coverage, the year compare, custom ranges, the year boundary, the URL round trip and the org-timezone anchor. All pass.
    • Parts: app/components/time-window/ holds Calendar, Panel, Popover, Pill (the when: pill, with FilterItem's chrome) and Line (the report header line). app/composables/useTimeWindow.ts keeps the window in the URL or locally.
    • Demo:
      • One URL-synced window drives both the pill and a report line.
      • The panel is laid flat across seven worlds: 90 days, 12 months kept, 16 months kept, fresh install, before the data, across the new year, month to date.
      • A table shows every preset resolved.
    • Calls made:
      • Last 24 hours and All time stay in the presets, because the window is shared and operators had them.
      • Presets apply in one click; a custom range and a compare change wait for Apply.
      • A to-date period compares with the same days of the period before (Nov 1–2 vs Oct 1–2).
      • A year compare the data can't support falls back to the previous period.
      • The compare button reads "A year earlier".
    • Finding for Tyler: with exactly 12 months of retention, same-period-last-year is never available. The year before any window starts before the data. Comparing Q3 with the Q3 before needs about 16 months. This pulls against Mark's 3–12 months.
    • Checked in the browser:
      • presets, the custom pick, Apply, URL sync, and every scenario's text
      • dark and light themes
      • phone width: the panel fits at 343px, and the calendar shows the month the window ends in
      • no console errors

      The design site's own nav still overflows at 375px. That was already true before this cycle, and it widens the emulated layout.
    • Gotcha: functions imported from ~/utils/time-window and used only in a template were dropped from the compiled <script setup> ("$setup.isBakeable is not a function"). They are now read through local bindings (fmtDay, bakeable).
    • Uncommitted. Mark's review is next, then Phase 2 (chassis and catalog).
  • Phase 2, chassis and catalog (2026-11-03, started on Mark's word): Reports now has pages in the lab shell: the catalog at /pages/phase4/mock/reports and the three reports at /reports/posture, /reports/inventory and /reports/deployment.
    • The report world (app/data/reports-c128.ts) holds twelve months of daily history (Oct 30, 2025 to Oct 29, 2026) that ends on the lab's present, so a report agrees with the page behind it (R3).
      • The last 30 days of spend are the cost tour's ledger to the dollar: $5,865, Home's window.
      • The fleet ends at Home's 100 hosts: 97 fully monitored, 3 partial, 0 unmonitored.
      • The harnesses and models come from the ledger; the MCP servers are the lab's listing.
      • Before that the history is generated and seeded, so it's the same on every load: a rollout from 8 to 100 hosts, posture 53 → 78 with a March dip and the Oct 21 eval-run dip, 571 findings with fix times by severity (SLA targets are c78's placeholders: 7, 30, 90 and 180 days), and first_seen dates for inventory.
      • Readers implement R2: flows sum in the window, states read at its end, trends span its days. Days before history begins come back null (R6).
    • Specs and readers (app/data/reports-c128-specs.ts):
      • Each report's sections carry a question, a time basis, a qdash source and a status: live, staged or missing.
      • The readers produce the lede sentence, the lead metric with its change, the spark, the trend in words, the worst line and the facts.
      • The catalog card, the Board band and the report verdict all read the same reader.
    • Chassis parts (app/components/reports/):
      • BasisTag: "in window", "over the window" or "as of Oct 29". It reads "current state" in amber when the window ends before the data does, since there are no snapshots yet.
      • Section: title, question, tag, staged or "not in qdash yet", and the source line.
      • Verdict: sentence, then number and change, spark, trend words and facts. Sparks are scaled to the window's own range.
      • Footer: window, data through, timezone, generated, and the read-only note.
      • Card: c78's mini-doc briefing, its provisional lean.
      • SummaryBand: the Board fold-in. One derived sentence, three highlights, and the trend in words; no composite score.
    • The catalog has the shared when: pill, the Board band, then Security (Posture) and IT (Inventory, Deployment status). Links carry the window.
    • A report page has the breadcrumb title, Print/PDF and CSV·JSON stubs (Phase 4), the window line, the verdict, every section with a few real figures where the reader has them ("Phase 3 draws this section"), and the footer. A window with no data shows the window line's reason and the report's outline, with no sections.
    • Checked in the browser:
      • the catalog, all three reports, Q3 on Posture (76 ▲ 4 vs Q2, "current state" tags), year to date on Deployment (85 → 100%, no year-earlier data so no change), and a window before the data
      • the spend matches Home
      • light and dark themes
      • no console errors
    • Lab-wide, not changed here: the lab shell's rail doesn't collapse at phone width, and it highlights only exact paths, so Reports isn't lit on a report page.
    • Uncommitted. Next is Phase 3: Posture first, one report at a time, preview → build → Mark's review.
  • Phase 3 · Posture (2026-11-03, on Mark's word): all eight Posture sections are drawn, in app/components/reports/posture/Body.vue. It is mounted from /reports/posture, while the other two reports keep the Phase 2 outline.
    • Posture score: ReportsLineChart draws one point a day with the comparison window as a dashed ghost aligned day for day (the c103 F6 idea). Days before the data are hatched. Start, end, low and high are shown with their dates.
    • What lowers the score: points lost by factor. They sum to 100 − 78, the state today.
    • Critical and at-risk harnesses: installs under 80 with person, endpoint and top factor.
    • Findings and SLA:
      • the auditor's table by severity: target, opened (with its change), resolved, open at end, past target
      • opened and resolved per day
      • a past-target list on the window's last day, worst severity first, then oldest
    • Data protection: sensitive opens and secrets in content against the comparison, split by kind.
    • Egress: allowed, observed only and blocked. Blocks begin in July, when enforcement began, and a sentence explains the split. Risky destinations are listed.
    • Policies in force: counts by mode and the mode changes in the window, read from the lab's governance world, so they match /policies.
    • Framework evidence: counts by state for the tracked frameworks, with no % (c123). Read from the lab world, linking to Compliance.
    • World extension (reports-c128.ts, on its own seed so the Phase 2 numbers didn't move): POSTURE_FACTORS, AT_RISK, GUARD_DAYS (DLP and egress per day), splits, finding titles and harnesses, pastTargetAt, findingsPerDay.
    • Checked in the browser: last 30 days, Q3, all time, 24h with no comparison, and a custom range that starts before the data (hatched, comparison shown as —). No console errors.
    • Uncommitted. Mark's review of Posture is next, then Deployment status, then Inventory.
  • deploy (2026-11-03): deployed live at Mark's word ("it's all our lab").
    • What shipped: npm run deploy → deploy d39dd67b12, built from design c400dd2afe with 23 uncommitted paths included: all of c128 plus other sessions' lab edits. Layer 0.9.37. 6,258 routes prerendered in 1,023 s.
    • The first attempt collided with another session's deploy over .nuxt-deploy and aborted at cleanup. That other deploy never published.
    • Checked live on design-f39.pages.dev: /pages/phase4/mock/reports/ and the posture, deployment and inventory reports, plus /pages/phase4/time-window/, all return 200. Posture on ?period=last-quarter renders (76 ▲ 4).
    • The slow build came from the prerender crawler following about 19k query-string lab URLs. c129 fixed that (build 17.5 → about 4 min) and deployed again as bd48264c3e, built from 0a2cb25691 plus the same uncommitted paths, so c128 stays live.
  • histories (2026-11-04): at Mark's word, the histories caught up with the deploy.
    • c128 has a story and pickup (app/data/cycle-stories-survey/c128.json). c78's story now reads closed into c128, with its pickup removed.
    • Archived cycles no longer show the c124 audit's "ready to close" note, on the story page or in the explorer (StoryView.vue, cycle-stories-mock.ts). The audit is dated, so anything closed after it was still being told to close.
    • c128 stays out of HISTORY.md because it's still active. c78's entry is there. Both are uncommitted.
    • c78 closed into c128: its Outcome is written, it has a HISTORY entry, and it's off CYCLES.md. It has no captures.
    • Uncommitted.
  • intents PR 5 and PR 6 (2026-11-05): c123's thread intents-pr5 evaluated both against Reports: evaluation, plan, Phase 6. PR 5 (merged) changes the Framework evidence section. PR 6 (open) would replace the three live reports with four generated PDFs and overturn three 2026-11-03 rulings, so that part waits on Mark. PR 6 merged the same day, and Mark ruled: PR 6's four reports (Inventory, Findings, Compliance, Executive Summary) replace our three, as living pages rather than generated PDFs. That reverses this cycle's rulings that Findings is a section of Posture and that the Board folds into the catalog. The work is in the plan's Phase 6, framework section first.
  • Four living reports (2026-11-05): built from c123's thread intents-pr5, on PR 6's questions. Inventory (reports/inventory/Body.vue), Findings, Compliance and Executive Summary replace Posture, Inventory and Deployment status; the old URLs read the report their content moved to. R1–R6 hold.
    • Findings read the lab's own 22 (data/reports-c128-findings.ts; Mark, 2026-11-05), with their real team, policy, outcome, reopens and reasons, so a report agrees with the pages (R3). The seeded 571 are gone. SLA goes, replaced by mean time to resolve on PR 6's definition.
    • Data protection and Egress stay, inside Findings (Mark, beyond PR 6). Their data is still seeded.
    • Compliance reads c123's enforcementPosture, exceptionsRead and postureHistory, and never adds frameworks together. Compliance Score stays out.
    • Enforce-pilot candidates (reports/Pilots.vue) are rule-based and show their evidence, or say why there are none.
    • A Team filter on every report, and Download PDF as a print of the page. CSV, JSON and the evidence pack are out for v1.
    • The Board band left the catalog for the Executive Summary. Posture's own body and the summary band are deleted.
    • Open calls are in c123's intents-pr5-review.md.

Visual record

10 captures — heroes, page slices, and full-page renders. Click to view.

After: Findings, Compliance, Inventory and Executive Summary, by reader
After: Findings, Compliance, Inventory and Executive Summary, by reader open page →
After: Enforcement Posture per tracked framework, how it changed, and the evidence
After: Enforcement Posture per tracked framework, how it changed, and the evidence open page →
After: open now, resolved, backlog, time to resolve, and Enforce-pilot candidates
After: open now, resolved, backlog, time to resolve, and Enforce-pilot candidates open page →history · 3 moments →
After: the known surface, what isn't monitored, coverage over time, where to focus
After: the known surface, what isn't monitored, coverage over time, where to focus open page →
After: one aggregate account, with no combined number and no people or devices
After: one aggregate account, with no combined number and no people or devices open page →
c128 full
open page →
c128 full
open page →
c128 full
open page →history · 3 moments →
c128 full
open page →
c128 full
open page →

Artifacts

Qpoint Brand Style Guide