mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-21 07:41:28 +02:00
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
180 lines
4.7 KiB
Python
180 lines
4.7 KiB
Python
"""A report theme is data: tokens a user can write, upload and share, never code."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from enum import StrEnum
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
MAX_THEME_BYTES = 200_000
|
|
MAX_THEME_CSS = 60_000
|
|
THEME_SLUG_LENGTH = 48
|
|
|
|
|
|
class ThemeOrigin(StrEnum):
|
|
BUILTIN = "builtin"
|
|
CUSTOM = "custom"
|
|
|
|
|
|
THEME_ORIGIN_LABELS: dict[str, str] = {
|
|
ThemeOrigin.BUILTIN.value: "Shipped themes",
|
|
ThemeOrigin.CUSTOM.value: "Custom themes",
|
|
}
|
|
|
|
|
|
class CoverLayout(StrEnum):
|
|
BAND = "band"
|
|
RULE = "rule"
|
|
FULL = "full"
|
|
SPLIT = "split"
|
|
MINIMAL = "minimal"
|
|
|
|
|
|
COVER_LAYOUT_LABELS: dict[str, str] = {
|
|
CoverLayout.BAND.value: "Colour band",
|
|
CoverLayout.RULE.value: "Rule and title",
|
|
CoverLayout.FULL.value: "Full bleed",
|
|
CoverLayout.SPLIT.value: "Split panel",
|
|
CoverLayout.MINIMAL.value: "Minimal",
|
|
}
|
|
|
|
|
|
class CoverArt(StrEnum):
|
|
NONE = "none"
|
|
GRID = "grid"
|
|
TOPO = "topo"
|
|
MESH = "mesh"
|
|
SCAN = "scan"
|
|
RINGS = "rings"
|
|
|
|
|
|
COVER_ART_LABELS: dict[str, str] = {
|
|
CoverArt.NONE.value: "None",
|
|
CoverArt.GRID.value: "Grid",
|
|
CoverArt.TOPO.value: "Contours",
|
|
CoverArt.MESH.value: "Mesh",
|
|
CoverArt.SCAN.value: "Scan lines",
|
|
CoverArt.RINGS.value: "Rings",
|
|
}
|
|
|
|
|
|
class TableStyle(StrEnum):
|
|
HAIRLINE = "hairline"
|
|
ZEBRA = "zebra"
|
|
BOXED = "boxed"
|
|
OPEN = "open"
|
|
|
|
|
|
TABLE_STYLE_LABELS: dict[str, str] = {
|
|
TableStyle.HAIRLINE.value: "Hairline",
|
|
TableStyle.ZEBRA.value: "Zebra",
|
|
TableStyle.BOXED.value: "Boxed",
|
|
TableStyle.OPEN.value: "Open",
|
|
}
|
|
|
|
|
|
class FindingStyle(StrEnum):
|
|
RAIL = "rail"
|
|
CARD = "card"
|
|
PLAIN = "plain"
|
|
BANNER = "banner"
|
|
|
|
|
|
FINDING_STYLE_LABELS: dict[str, str] = {
|
|
FindingStyle.RAIL.value: "Severity rail",
|
|
FindingStyle.CARD.value: "Card",
|
|
FindingStyle.PLAIN.value: "Plain",
|
|
FindingStyle.BANNER.value: "Severity banner",
|
|
}
|
|
|
|
|
|
class HeadingStyle(StrEnum):
|
|
NUMBERED = "numbered"
|
|
RULE = "rule"
|
|
PLAIN = "plain"
|
|
KICKER = "kicker"
|
|
|
|
|
|
HEADING_STYLE_LABELS: dict[str, str] = {
|
|
HeadingStyle.NUMBERED.value: "Numbered",
|
|
HeadingStyle.RULE.value: "Rule above",
|
|
HeadingStyle.PLAIN.value: "Plain",
|
|
HeadingStyle.KICKER.value: "Kicker label",
|
|
}
|
|
|
|
|
|
class ColorTokens(BaseModel):
|
|
model_config = ConfigDict(extra="ignore")
|
|
|
|
page: str = "#ffffff"
|
|
ink: str = "#16181d"
|
|
ink_soft: str = "#4a4f5a"
|
|
ink_faint: str = "#82889a"
|
|
rule: str = "#e3e5ea"
|
|
rule_strong: str = "#c8ccd4"
|
|
surface: str = "#f6f7f9"
|
|
surface_soft: str = "#fafbfc"
|
|
accent: str = "#4f46e5"
|
|
accent_soft: str = "#eef0fe"
|
|
accent_ink: str = "#ffffff"
|
|
link: str = ""
|
|
severity: dict[str, str] = Field(default_factory=dict)
|
|
chart: list[str] = Field(default_factory=list, max_length=8)
|
|
|
|
|
|
class TypeTokens(BaseModel):
|
|
model_config = ConfigDict(extra="ignore")
|
|
|
|
heading: str = "inter"
|
|
body: str = "inter"
|
|
mono: str = "jetbrains-mono"
|
|
base_size: float = Field(default=9.5, ge=6, le=16)
|
|
scale: float = Field(default=1.22, ge=1.05, le=1.5)
|
|
line_height: float = Field(default=1.55, ge=1.0, le=2.4)
|
|
heading_weight: int = Field(default=650, ge=300, le=900)
|
|
heading_tracking: float = Field(default=-0.01, ge=-0.08, le=0.2)
|
|
label_tracking: float = Field(default=0.08, ge=-0.05, le=0.4)
|
|
uppercase_labels: bool = True
|
|
numeric_tabular: bool = True
|
|
|
|
|
|
class LayoutTokens(BaseModel):
|
|
model_config = ConfigDict(extra="ignore")
|
|
|
|
rule_width: float = Field(default=0.5, ge=0.2, le=2.0)
|
|
radius: float = Field(default=3.0, ge=0, le=12)
|
|
pill: bool = True
|
|
block_gap: float = Field(default=1.0, ge=0.4, le=2.5)
|
|
table: str = TableStyle.HAIRLINE.value
|
|
finding: str = FindingStyle.RAIL.value
|
|
heading: str = HeadingStyle.NUMBERED.value
|
|
chart_stroke: float = Field(default=1.0, ge=0.4, le=3.0)
|
|
|
|
|
|
class CoverTokens(BaseModel):
|
|
model_config = ConfigDict(extra="ignore")
|
|
|
|
layout: str = CoverLayout.BAND.value
|
|
art: str = CoverArt.NONE.value
|
|
ink: str = "light"
|
|
background: str = ""
|
|
accent_bar: bool = True
|
|
|
|
|
|
class ThemeTokens(BaseModel):
|
|
"""The whole look of a document, as a file a person can write."""
|
|
|
|
model_config = ConfigDict(extra="ignore")
|
|
|
|
key: str = Field(default="", max_length=THEME_SLUG_LENGTH)
|
|
name: str = Field(default="", max_length=120)
|
|
description: str = Field(default="", max_length=400)
|
|
author: str = Field(default="", max_length=120)
|
|
version: str = Field(default="1", max_length=20)
|
|
color: ColorTokens = Field(default_factory=ColorTokens)
|
|
dark: ColorTokens | None = None
|
|
type: TypeTokens = Field(default_factory=TypeTokens)
|
|
layout: LayoutTokens = Field(default_factory=LayoutTokens)
|
|
cover: CoverTokens = Field(default_factory=CoverTokens)
|
|
css: str = Field(default="", max_length=MAX_THEME_CSS)
|