Sentinel Style Guide
"The Sentinel's rigour in code must extend to every pixel the user sees."
This document codifies the Sentinel Visual Language — the binding rules for all Zenzic documentation pages. Every contributor must follow these rules. Reviewers must reject PRs that violate them.
Directive: ZRT-DOC-002
1. Card Rule (High-Density UX)
Navigation cards orient. They do not replace the sidebar.
Structure
Every card in a <div class="grid cards" markdown> block must have exactly:
- An icon (
<Icon name="..." />— see §3). - A bold title.
- A description of at most two lines.
- A single action link using the arrow prefix.
Canonical example
- <Icon name="play" /> **User Guide**
Everything you need to install, configure, and integrate Zenzic into
your CI/CD workflow.
[<Icon name="arrow-right" /> Explore the Guide](../../../how-to/install.mdx)
Forbidden patterns
| Pattern | Why |
|---|---|
Horizontal link chains (·-separated) | Creates a wall of text; impossible to scan |
Nested <li> lists inside a card | Breaks card height uniformity |
--- separators inside a card | Adds visual noise without information gain |
| Cards with zero action links | Dead-end; the user has nowhere to go |
Exception
Presentation cards (e.g., homepage "Sentinel in Action" demos) may omit the action link because their purpose is visual demonstration, not navigation. They must still receive the card CSS (border, hover, transition).
2. Admonition Taxonomy
Each admonition type has one — and only one — semantic role.
| Type | Role | When to use |
|---|---|---|
:::tip | Quick Win | One-liner commands the reader can run immediately |
:::info | Sentinel Output | CLI output blocks and Sentinel report samples |
:::danger | Security Gate | Exit Code 2 (credentials) and Exit Code 3 (path traversal) only |
:::warning | Design Constraint | Architectural rules, contributor policies, "use sparingly" caveats |
:::note | Clarification | Engine-specific facts, contributor onboarding, multi-step guidance |
:::info | Cross-Reference Bridge | Links from the current section to the next actionable step |
:::info | Community CTA | Engagement calls ("Help us grow", "Join the discussion") |
:::note | Philosophy | Project vision, design manifesto, Sentinel creed |
Enforcement
If a block does not fit any category above, rewrite it as prose. Admonitions are not decoration.
3. Iconography Law (ZRT-DOC-003)
The <Icon /> Component
Every icon in the documentation must be rendered with:
<Icon name="icon-name" />
Optional size override (default is 1.15em, inherits from surrounding text):
<Icon name="shield-check" size={20} />
All icon names follow the Lucide icon set naming convention (lowercase, hyphen-separated).
Hierarchy
| Priority | Set | Syntax | Notes |
|---|---|---|---|
| 1 | Lucide | <Icon name="play" /> | All UI and navigation icons |
Rules
-
Semantic consistency: if an icon represents "Contribute" on one page, it
must be the same icon on every page.
-
Uniform syntax: every icon in a card grid uses
<Icon name="..." />.No mixing of syntaxes or icon sets.
-
Tree-shaking contract: before using a new icon name, add it to the
explicit
iconsMapinsrc/components/Icon.tsx. Unregistered names render a red placeholder and emit aconsole.warn.
4. Anchor ID Protocol (ZRT-DOC-004)
When to add explicit IDs
Add {#id} to a heading when it satisfies both of:
- It is an H2 or H3 heading (never H1 — Docusaurus auto-generates H1 IDs from
sidebar_label). - It is referenced by a cross-page link (
[text](page.mdx#anchor)).
i18n Invariant
The canonical ID is always the English slug. Italian (and any future
language) pages must use the same {#id} value:
{/* EN */}
## Getting Started \{#getting-started}
{/* IT */}
## Inizia Ora \{#getting-started}
This ensures the VSM resolver and cross-language links never break due to translation-dependent slug generation.
Heading format
## Section Title \{#section-title}
Do not add IDs to headings that are never linked to externally. Every explicit ID is a maintenance contract.
5. Code Block Rule
Every opening fence must carry a language tag:
| Fence | Verdict |
|---|---|
```python | ✓ |
```bash | ✓ |
```toml | ✓ |
```text | ✓ (plain output) |
``` | ✗ FORBIDDEN |
Use text for output that has no syntax highlighting. Naked fences hurt
accessibility tools and syntax highlighters.
Gutter specificity: for CLI output shown inside :::info blocks,
always use the text tag to prevent the syntax highlighter from generating
random colours on log strings or file paths.
6. SPDX Header
Every .md file must begin with:
{/* SPDX-License-Identifier: Apache-2.0 */}
Files with YAML frontmatter place the SPDX block immediately after the
closing ---.
7. Visual Consistency Checklist
Before submitting a PR, verify:
- Every card grid follows §1 (single action link).
- Every admonition matches its §2 role.
- All icons use
<Icon name="..." />— no:lucide-*:,:octicons-*:, or:material-*:shortcodes remain (§3). - Any new icon name is registered in
src/components/Icon.tsx(§3). - Cross-referenced H2/H3 headings have explicit
{#id}(§4). No anchors on H1. - No naked code fences exist (§5).
- SPDX header is present (§6).
- Italian mirror is structurally identical to English.
- No hex literal (
#rrggbb) insrc/outsideSentinelPalette._*(§9). - All colour references use
SentinelPalette.*— no removed flat constants (§9).
8. SentinelUI Gateway
All branded terminal output in Zenzic flows through a single object: SentinelUI in
src/zenzic/ui.py. Command modules must never instantiate Console or SentinelUI
directly — they must call get_ui() and get_console() from zenzic.cli._shared.
Core methods
| Method | When to use |
|---|---|
print_header(version) | The top-of-output Forge Frame banner — once per command invocation |
make_panel(content, *, title, border_style) | Styled Rich Panel — for structured output blocks |
print_exception_alert(message, *, context, title, border_style) | Error panels for ZenzicError and PluginContractError |
Usage pattern
# In any _check.py / _clean.py / _standalone.py command
from . import _shared
# Print the Zenzic banner header
_shared.get_ui().print_header(__version__)
# Print a styled panel
panel = _shared.get_ui().make_panel(
"Content here",
title="Panel Title",
border_style="bold cyan",
)
_shared.get_console().print(panel)
Why the gateway matters
The --no-color and --force-color CLI flags call configure_console(), which atomically
replaces the module-level console and _ui singletons. Any locally-created Console or
SentinelUI instance will be frozen before the flag takes effect, silently ignoring the
user's color preference.
The force_terminal parameter must always be None (auto-detect) in the module-level
Console, never False. Explicit False disables color system detection entirely —
resulting in no ANSI styling even in truecolor terminals. This is the most common source of
visual regressions in the Zenzic CLI layer.
Checklist addition
Add to your PR checklist:
- No
Console(...)orSentinelUI(...)instantiation in command modules. - All banner output uses
get_ui().print_header(), not a locally-created UI instance. -
force_terminalon any newConsolecall isNoneor conditional (True if ... else None), neverFalse.
9. SentinelPalette — Zero Hex Law
SentinelPalette in src/zenzic/ui.py is the sole authorised source of colour values
in the entire Zenzic codebase. This is the Zero Hex Law.
The Law
:::warning Design Constraint
No hex colour string (e.g. #4f46e5) and no raw Rich colour name (e.g. "red", "cyan")
may appear anywhere in src/ except inside SentinelPalette._* private class attributes.
Every other file must address only the semantic public attributes shown below.
:::
Semantic palette
| Attribute | Hex | Meaning |
|---|---|---|
SentinelPalette.BRAND | #4f46e5 | Zenzic primary / brand accent (Indigo) |
SentinelPalette.SUCCESS | #10b981 | OK · clean · pass (Emerald) |
SentinelPalette.WARNING | #f59e0b | Caution · advisory (Amber) |
SentinelPalette.ERROR | #f43f5e | Failure · broken links (Rose) |
SentinelPalette.DIM | #64748b | Muted · secondary text (Slate) |
SentinelPalette.FATAL | #8b0000 | Security breach · path traversal (Blood) |
Pre-composed style strings
For the most common combinations, use a STYLE_* constant instead of constructing
f"bold {X}" inline:
| Constant | Expands to |
|---|---|
SentinelPalette.STYLE_BRAND | "bold #4f46e5" |
SentinelPalette.STYLE_OK | "bold #10b981" |
SentinelPalette.STYLE_WARN | "bold #f59e0b" |
SentinelPalette.STYLE_ERR | "bold #f43f5e" |
SentinelPalette.STYLE_DIM | "#64748b" |
Usage pattern
# CORRECT — semantic alias via SentinelPalette
from zenzic.ui import SentinelPalette
table = Table(border_style=SentinelPalette.DIM, header_style=SentinelPalette.STYLE_BRAND)
text = Text.from_markup(f"[{SentinelPalette.BRAND}]Zenzic[/]")
panel = Panel("...", border_style=SentinelPalette.STYLE_ERR)
# FORBIDDEN — hex literal outside SentinelPalette
text = Text.from_markup("[#4f46e5]Zenzic[/]") # ✗
# FORBIDDEN — flat constant import (removed in v0.7.0)
from zenzic.ui import INDIGO, EMERALD # ✗
# FORBIDDEN — inline alias
P = SentinelPalette # ✗ use full qualification
Updating the palette
To change a colour, edit only the corresponding _PRIVATE hex attribute inside
SentinelPalette in src/zenzic/ui.py. All semantic aliases and pre-composed style
strings derive from those private attributes — the entire codebase updates automatically.
Checklist addition
Add to your PR checklist:
- No hex literal (
#rrggbb) anywhere insrc/outsideSentinelPalette._*. - No raw Rich colour names (
"red","cyan") for brand-palette usage — useSentinelPalette.*. - No local alias
P = SentinelPalette— always use the full class name. - No
from zenzic.ui import INDIGO(or any removed flat constant).