Commit Graph
5 Commits
Author SHA1 Message Date
Yogesh OjhaandClaude Fable 5.1 0a69f3201c copy: state the fact, delete the explanation
Repo-wide sweep of every user-facing string: frontend components, routes,
config and stores; API details and route docs; shared definitions and
models; stage config help; report prose; MCP, toolbox, interest and
connector text; notification and worker messages.

Descriptions that explained, reassured, justified or restated their title
are deleted. Confirm dialogs name what is removed and stop. Failed actions
read "<Noun> not <verbed>", successes "<Noun> <verbed>". No reader address,
no product-as-subject, no em-dashes or parentheticals in prose.

Mirrors updated on both sides: EPSS band help, compare refusal reasons,
interest labels. Four tests asserting on old wording follow the new text.
The rules are condensed into CLAUDE.md under Writing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SK5o8xR96cbSnAfjYSTPSe
2026-09-12 11:35:07 +05:30
Yogesh OjhaandClaude Opus 5 2b52fa0d0a refactor(ui): the interface explained itself to the reader instead of naming things
Page subtitles, panel descriptions and field labels had drifted into prose
that narrates a feature rather than naming it. A subtitle read "Connect a
language model to draft report narratives. Everything reNgine computes works
without it; a model only changes how the findings are written up." where the
older pages already say "Instance-wide configuration".

A subtitle and a panel description are noun phrases with no trailing period.
Four patterns went out:

- Second person. "Your keywords", "Your themes", "Your templates", "Your
  company" and three "Yours" origin badges become "Keywords", "Custom themes",
  "Custom templates", "Company", "Custom" — moved in the backend SSOT so the
  frontend mirrors follow. "Checks you uploaded" is "Custom templates";
  "Upload your own in the Tools Arsenal" is "Custom lists are uploaded in the
  Tools Arsenal". The user's own credentials keep "your".

- Rhetorical What/Where/Why headings. "What is listening" is "Service
  classes", "Where it concentrates" is "Findings by target", "What changed"
  is "Changes", "Worth acting on first" is "Priority queue", "Why this is
  interesting" is "Signals". Nine query-grammar field descriptions follow the
  rule already on record: "How many parameters the endpoint accepts" is
  "Number of parameters the endpoint accepts".

- The reassuring second clause that explains the first away. Cut from the AI
  page, the reports empty state and the schedules panel. The AI alert states
  what is sent and what is not, then stops.

- "Create your first X" as a heading, now "No scan engines" / "Create a
  project".

Two delete dialogs were missing the standard "This action cannot be undone."
tail. schedule-list-card used a native title= for a hover hint, which the
project forbids; it is a Hint now.

Copy in shared/, stages/, reports/ and interest/ renders straight into the UI
and gets the same pass. MCP tool descriptions are left alone: they are prompts
a model plans against, not UI copy, even though /mcp displays them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016AeRG6G2FdSyWjG4AXPhBA
2026-09-06 19:50:21 +05:30
Yogesh OjhaandClaude Opus 5 724ebd42bb feat(reports): a section declares its role, its order and what it asks at generate time
Choosing what a report contains required editing a template. That is the wrong
place for a decision made per report, and the scan launch flow already
established the alternative: a saved configuration seeds the run, the run may
depart from it, and the saved configuration is never touched.

Three declarations put sections on the same footing as stages. SectionRole
separates the content a reader asked for from the furniture around it, so a
picker can offer twenty-one choices and include the cover, the contents and the
reference matter without being asked. Section.order fixes the reading order,
which until now was however the directories happened to sort — the executive
summary came after attack paths because "a" precedes "e". Section.launch_fields
names the settings worth surfacing when the report is generated rather than
only in the builder, mirroring Stage.launch_fields; the findings section
declares its severities, its detail depth, its roll-up and its asset cap.

The estimate had to become honest for any of this to be worth showing. It
counted every finding in the scan whatever the section filtered to, so dropping
three severities moved nothing. It now counts through the same severity filter
the section applies and prices each tier separately, from the real per-severity
issue counts rather than an assumed split: 28 pages against 26 rendered for a
full template, 10 against 9 for critical and high alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AW5pitnosX8LEFEYxH8gFS
2026-09-06 17:14:01 +05:30
Yogesh OjhaandClaude Opus 5 2c9d922bc8 feat(reports): a lighter document, and a running head that is not against the paper's edge
The first cut was rejected outright, and fairly. Four things were wrong.

The running head and foot sat 1.4mm from the physical sheet: vertical-align:top
on the @top-* margin boxes pins text to the paper, not to the text block. They
now hang off the block with RUNNING_CLEAR of air on the inside edge.

Severity was five saturated hues, restated by every theme and slightly
different in each. It is now one reserved ramp defined once, light rather than
heavy, with lightness carrying the rank so it survives deuteranopia; it clears
the CVD and normal-vision floors all-pairs on white paper. A dark page picks
up the dark restep automatically, so a dark theme carries no severity block at
all. shared/utils/color.py converts through OKLCH, so a theme names one colour
and the document derives the wash a badge sits on and the ink that sits on the
wash — every badge clears 7:1, including one from a theme a user uploads.
Badges are pills, never saturated slabs, and never inline-flex, which
WeasyPrint lays out as a block so the badge stretched its whole column.

Mono is for machine values now — hostnames, evidence, hashes. Labels, table
headers and the running head are the body face, and uppercase_labels finally
does something: the CSS had hard-coded uppercase and ignored the token.

Prose fills the text column, justified and hyphenated, instead of sitting in a
narrow measure with dead space beside it. Chapters still start a new page, but
a document may now ask them to run on, and the page estimate knows the
difference: 52 pages against 52, and 48 against 48.

Two defects the redesign surfaced: the risk summary never passed the variables
its own template reads, so that severity table had always printed empty; and
the standalone HTML export had no margins at all, because @page geometry does
not apply on screen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AW5pitnosX8LEFEYxH8gFS
2026-09-06 16:26:51 +05:30
Yogesh OjhaandClaude Opus 5 424e6d6e23 feat(reports): a document engine, not an export
reNgine 2 could produce a PDF. This produces a deliverable, and it is built
the way scan engines are built: sections are plug-and-play directories under
reports/, each with a pydantic config model that IS the source of truth. That
model feeds GET /reports/catalog, the builder renders its own form from it, and
the API validates against it. Adding a section is a directory. Scan Engine is to
stages what Report Template is to sections.

WeasyPrint 69 renders it, chosen over Chromium and Typst after measuring what
each can actually do. Only real CSS Paged Media gives a table of contents with
true page numbers, running headers, a PDF outline and footnotes; SysReptor
reaches the same verdict and pipes Chromium's HTML into WeasyPrint for exactly
that reason. reNgine drops the Chromium hop because it templates in Jinja and
draws its charts as SVG in Python, so a browser buys nothing and costs 400 MB
per report. Typst was rejected because a user-uploadable theme would then have
to be Typst code rather than CSS.

A theme is data. shared/definitions/report_theme.py is the schema, six themes
ship as YAML, and a user uploads their own; there is no markup in a theme, so
there is no injection surface. Fonts are vendored so an offline instance still
renders in Latin, Greek and Cyrillic.

Everything the document claims is computed once, in reports/analysis/engine.py:
a posture score whose every deduction is named, weaknesses ranked by the signals
behind the rank rather than severity alone, attack paths that chain observations
into a route, a remediation plan ordered by risk removed, per-dimension change
against the previous run, and caveats that state what was never assessed. Two
narrators read that one brief. Without AI the report still argues its case; with
AI the prose is better and the model never sees a row, only the brief, capped and
cached per check. An AI failure falls back and the report still completes.

Generation is a job. POST /reports queues, the worker walks progress over SSE,
and four formats come out of one render: PDF, self-contained HTML, Markdown and
JSON. The estimate reads each section's own limits, so the page count it promises
is close to the one it delivers.

AI moves out of Settings into its own top-level page: connection, a model per
tier, what it may be used for, what it has cost, and the narrative cache.

Verified on a 1,379-host gov.cy scan: 124 pages in 19.5s across 19 sections,
all six themes, and a broken key still producing a complete report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017f2Mf2HGwFaTRnui4UxpRd
2026-09-06 11:45:51 +05:30