Semantics
Concept of the skill
Semantics in software is meaning encoding: every name, status code, version number, commit type, token, telemetry attribute, tool definition, and typed value is a sign that points at a referent under a convention.
Coverage
Cross-domain semantic thinking for all naming and meaning decisions:
- Naming in code — the fundamental what + why principle, verb-prefix return/side-effect contracts (
get/find/fetch/parse/ensure/assert), naming smells (Peter Hilton's seven categories), machine-reader naming smells, DDD ubiquitous language, scalar/count/collection/boolean rules
- Semantic versioning — SemVer 2.0.0 (MAJOR.MINOR.PATCH), the declared-public-API precondition, what counts as breaking, the
0.y.z initial-development rule, deprecation-as-MINOR, precedence, Hyrum's Law, SemVer vs CalVer
- Semantic commit messages — Conventional Commits format, type catalog (
feat, fix, docs, style, refactor, perf, test, build, ci, chore), the spec-vs-tooling SemVer mapping, breaking-change syntax, the SemVer-from-commits automation chain
- Semantic CSS — purpose-not-appearance class names, BEM (
.block__element--modifier), three-layer design-token architecture (primitive → semantic → component), the DTCG stable interchange format ($value/$type/$description/$deprecated, .tokens), cascade-layer (@layer) names and order as an explicit priority signal
- Semantic data modeling — column-naming rules (unit suffixes, boolean prefixes, timestamp/instant/date/range distinctions), semantic types beyond primitives, branded TypeScript types (string-tag and
unique symbol), smart constructors, parse-don't-validate
- Semantic UI / UX — Don Norman affordances and signifiers, semantic HTML element choice (
<button> vs <div onclick>, <time datetime>, lists), semantic color (with the never-color-alone rule), signifier checks, microcopy semantics
- Semantic APIs — REST resource naming (nouns + HTTP verbs, plural collections, kebab-case, max-2-nesting), HTTP status codes as semantic signals (the never-200-with-error-body rule + within-family disambiguation), RFC 9457 Problem Details for machine-readable error payloads, GraphQL naming and schema semantics (field/enum/nullability as wire contract,
@deprecated, @oneOf, the media-type-dependent transport-status rule)
- Semantic tool, telemetry & schema contracts for machine readers — LLM tool/function names, descriptions as routing prompts, argument enums and units, destructive-operation legibility, result-field contracts, OpenTelemetry observability-attribute conventions, prompt-artifact boundary names, and versioning machine-read artifacts
- Universal anti-patterns — generic names, semantic drift, misleading names, appearance-based names, abbreviation ambiguity, cargo-cult naming, machine-reader drift, unit drift, enum reuse
Philosophy of the skill
Every name is a micro-decision that compounds across the codebase. A function called process(data) forces every future reader to open the implementation to understand its purpose. A design token called --light-blue breaks the moment someone adds dark mode. An API returning HTTP 200 with an error body confuses every consumer. These are not style preferences — they are semantic failures that create real debugging time and real misunderstandings.
This skill exists because naming quality degrades silently. No test catches handleRefresh() being reused for too many unrelated actions. No linter flags --big-spacing. No CI gate rejects employee2. Without explicit semantic rules loaded at decision time, agents default to the first name that compiles rather than the name that communicates. The discipline is to make meaning a first-class artifact, not a residue.
The audience for names has widened. Names are no longer read only by humans and compilers — LLM-backed routers, agents, and tool-selection layers now read tool names, function names, and field names to decide what to invoke; dashboards and alerts read telemetry attribute names no human wrote at emit time; design-token transformers and GraphQL introspection clients read names as wire contracts. An ambiguous provider or a vague process degrades automated tool selection the same way it degrades a human reader, so semantic precision is now also a machine-routing concern, not only a readability one. (For the design of the tool/function interface itself, defer to agent-engineering and tool-call-flow; semantics owns only whether the chosen words encode the right meaning.)
The mental model: a Sign (the name) acquires meaning from its Referent (what it points to) and from Convention (the shared agreement). Affordance (what the name implies you can do) is the bridge between sign and user expectation. Semantic drift happens when referents change but signs do not — a silent lie in the code, the kind no test catches.
Semantics is to code what road signage is to driving — the sign isn't the road, but bad signage causes crashes even when the road is fine.
Semantic Audit Workflow (Sign · Referent · Convention)
When auditing any name, status, version, token, schema field, tool, or signal, run the same repeatable check — it operationalizes the mental model and works across every domain in this skill:
- Name the Sign. What is the literal signal? (
process(data), 200, --light-blue, feat:, OrderId, get_user, payment.status.)
- Find the Referent. What does it actually point at now? Read the implementation/behavior, not the name's promise. (Does
process reconcile revenue? Did the request actually fail? Does the token resolve to a brand color? Can the tool delete?)
- Check the Convention. Under the shared agreement for this artifact kind (SemVer spec, Conventional Commits, RFC 9110, DTCG, OpenTelemetry conventions, DDD ubiquitous language, the local API style), does the Sign correctly encode the Referent?
- Predict the inference. Would a competent reader, client, design tool, router, or LLM infer the actual referent from the sign without opening implementation details?
- Repair the mismatch. Fix the Sign, split the Referent, or update the Convention — in the same change as the behavior change so the drift never ships. A comment explaining the lie is not a semantic repair.
A mismatch at step 3/4 is semantic drift — the defect to fix.
| Failure |
Symptom |
Repair |
| Referent changed, sign stayed |
handleRefresh() now syncs, imports, and reconciles |
Rename to the current operation or split the behavior |
| Convention changed, sign stayed |
--light-blue now maps to a dark-theme accent |
Move appearance to a primitive token and purpose to a semantic token |
| Sign overclaims certainty |
getOrder() returns undefined |
Rename to findOrder() or change behavior to getOrderOrThrow() |
| Sign hides unit |
revenue stores integer cents |
Rename to revenue_cents or wrap in Money |
| Sign hides protocol outcome |
HTTP 200 with { "error": ... } |
Use the correct 4xx/5xx family plus a typed error body |
| Sign hides tool capability |
run can delete records |
Rename to the actual destructive action and make the boundary legible |
When to Use
- Choosing semantically truthful names for variables, functions, classes, files, database columns, events, and types.
- Auditing whether a name still matches the behavior, domain concept, unit, or compatibility promise it now represents.
- Deciding whether one API name, status code, error code, field, or problem type tells the truth (route whole-surface design to api-design and protocol detail to http-semantics).
- Choosing semantic CSS classes, design-token names, or token layers that survive rebrand, dark mode, and component reuse.
- Choosing Conventional Commit type/scope or SemVer bump based on the meaning of a change.
- Modeling domain language with branded/refined types or parse-don't-validate boundaries.
- Auditing whether an LLM tool/function name, telemetry attribute, or prompt-artifact slot truthfully encodes its referent.
- Reviewing UI labels, colors, affordances, and status indicators for meaning alignment before microcopy or a11y checks.
- Reviewing code for naming quality when the problem is the meaning encoded by a name, not the mechanical rename.
Boundary Routing
| User need |
Use |
Why |
| Word form, compound order, abbreviation policy, audience register, or blame-free phrasing |
linguistics |
Linguistics owns language form and register; semantics owns the meaning encoded by a signal. |
| Casing format, prefix/suffix convention, or rename mechanics across call sites |
naming-conventions or refactor |
Naming conventions and refactor own mechanical consistency; semantics decides whether the words are truthful. |
| Relation type between two concepts |
semantic-relations |
Semantic-relations owns IS-A, PART-OF, causal, thematic, synonymy, polysemy, and graph-edge typing. |
| Multi-channel visual sign systems (icons, badges, shapes, color metaphors) |
semiotics |
Semiotics owns visual/sign-system coherence; semantics owns the meaning of one textual identifier or signal. |
| Functional UI text pattern |
microcopy |
Microcopy owns button labels, empty states, dialogs, tooltips, validation, and toast text. |
| Full API surface: resources, schemas, pagination, idempotency, auth, error-envelope design |
api-design |
Api-design owns the whole surface; semantics owns whether one name/status/field tells the truth. |
| RFC-level method/status/header/cache protocol detail |
http-semantics |
Http-semantics owns the protocol contract; semantics owns meaning alignment when a signal contradicts its outcome. |
| Type soundness, narrowing, exhaustiveness, validator choice |
type-safety |
Type-safety owns the guarantees; semantics owns when a type/brand/unit name encodes the wrong meaning. |
| Telemetry strategy, span/metric/signal selection, dashboards, SLOs |
observability-modeling |
Observability-modeling owns instrumentation strategy; semantics owns whether one attribute/metric name tells the truth. |
| The LLM tool surface itself (which tools, granularity, statefulness, orchestration) |
agent-engineering or tool-call-flow |
Those own the interface design; semantics owns whether the chosen tool names/args/fields encode the right meaning. |
| Classification structure, facets, or category assignment rules |
taxonomy-design |
Taxonomy owns the structure; semantics owns the names and signals inside it. |
| Accessibility compliance or assistive-technology behavior |
a11y |
Semantics can detect a missing non-color signal or non-semantic element; a11y verifies the accessible contract. |
| Branching, rebasing, commit boundaries, release tags, or history shape |
version-control |
Version-control owns repository history; semantics owns version/commit meaning. |
1. Semantic Naming in Code
The Fundamental Principle
A name must encode what something is, why it exists, and what contract it exposes to readers or tools. The reader should not need to open the implementation to distinguish the domain concept, unit, side effect, lifecycle state, or compatibility signal.
Rules
| Rule |
Bad |
Good |
Why |
| Use full words |
calc(), mgr, btn |
calculateProfitAndMargin(), manager, button |
Abbreviations are ambiguous (auth = authentication or authorization?) |
| Encode intent |
data, info, result |
unpaidInvoices, customerProfile, validationErrors |
Generic names carry zero information |
| Use domain language |
UserRequestsProduct |
customerPlacesOrder |
Match stakeholder vocabulary (DDD ubiquitous language) |
| Name the distinction |
employee, employee2 |
manager, recentHire |
Numeric suffixes hide the actual difference |
| Scalars need qualifiers |
store = "text" |
storeName = "text" |
store implies an object, not a string |
| Counts need suffixes |
products = 0 |
productCount = 0 |
products implies a collection |
| Collections use plurals |
orderList |
orders |
The data structure already implies "list" |
| Booleans read as questions |
active |
isActive, hasOrders, canEdit |
Reads naturally in conditionals |
Verb Prefixes Encode a Return/Side-Effect Contract
A function's leading verb is a promise about its return shape, cost, certainty, and side effects; readers and callers (human and LLM) route on it. Keep the verb honest:
| Prefix / shape |
Implied contract |
Violated when |
get / read |
Cheap, synchronous, always returns the value (or throws if absence is exceptional); no I/O surprise |
getUser() makes a network call or silently returns null |
find / query |
Lookup where absence is normal; may return nothing (`T |
null` / list) |
fetch |
Async, does I/O, transport can fail |
fetchConfig() reads an in-memory constant |
load |
Reads and initializes or caches |
loadX() is a pure synchronous accessor |
list |
Returns a collection (possibly empty), never a scalar |
listUser() returns one record |
is / has / can / should |
Returns a boolean now, positive polarity, no side effects |
isReady() returns a promise, mutates state, or is inverted |
create / update / delete |
Mutates; not idempotent for create/delete |
createSession() returns a cached existing session |
ensure / upsert |
Idempotent; safe to call repeatedly |
ensureDir() throws if the dir exists |
compute / calculate / build |
Pure derivation from inputs, no I/O |
calculateTotal() writes to the database |
parse |
Less-structured input → stronger typed output or structured failure |
It only returns a boolean and discards the learned structure |
validate |
Checks validity, usually boolean or diagnostic result |
It mutates input, or returns a stronger type without saying parse |
assert |
Throws if false; narrows on success |
It returns ordinary data or performs business work |
The prefix is the at-a-glance signal; when behavior and prefix disagree, the prefix lies and every caller inherits the wrong expectation. (When the work becomes artifact-specific casing, prefix/suffix policy, or rename mechanics, route to naming-conventions.)
Naming Smells (Peter Hilton's catalog)
- Meaningless —
foo, bar, tmp, x
- Abstract —
data, object, thing, item
- Numeric suffixes —
employee2 hides the distinction
- Abbreviations —
acc (accumulator? accuracy? account?)
- Vague verbs —
get, process, handle, manage say nothing about behavior
- Type-encoded —
array, string, int as variable names
- Weasel suffixes —
Info, Data, Manager, Helper, Utils, Base, Entity
Machine-Reader Naming Smells
Names are now read by LLM routers, tool selectors, schema validators, design-token transformers, GraphQL introspection clients, and changelog generators. This adds several smells beyond Hilton's:
| Smell |
Example |
Why it fails |
| Hallucinated specificity |
getUserInvoices() actually returns all billing documents |
Human and model readers infer a narrower referent than the code provides |
| Implementation-step naming |
runStep3() / doPipelineThing() |
The name encodes how the code was built, not the domain action |
| Tool affordance mismatch |
MCP/OpenAI tool update_record can delete records |
A model may call a destructive capability under a safe-looking name |
| Generic schema fields |
data, result, payload, status everywhere |
Machine readers cannot distinguish entity, state, and outcome semantics |
| Enum drift |
PENDING now means queued, blocked, and waiting for payment |
A stable wire value has accumulated multiple referents |
DDD Ubiquitous Language
Core domain classes (entities, value objects, events, services) must use business terminology. Zero tolerance for weasel words. Technical terms are acceptable only in infrastructure classes where no domain equivalent exists.
Translation tax: every time a developer mentally translates between code vocabulary and business vocabulary, cognitive overhead accumulates — and it compounds across teams and codebase lifetime.
2. Semantic Versioning (SemVer 2.0.0)
Format: MAJOR.MINOR.PATCH
| Increment |
When |
Example |
| MAJOR |
Backward-incompatible API changes |
Removing a method, changing return type |
| MINOR |
New backward-compatible functionality |
Adding an endpoint, new optional param |
| PATCH |
Backward-compatible bug fixes only |
Fixing a calculation, patching a security issue |
What Constitutes a Breaking Change
- Removing a public method, class, endpoint, or field
- Changing return types or parameter types
- Changing default behavior users depend on
- Renaming public APIs without aliases
- Changing serialization format
- NOT breaking: adding optional params, new endpoints, new types
SemVer Nuances Agents Often Miss
- SemVer requires a declared public API. If the public API is vague, the version number cannot communicate compatibility truthfully — declare the surface before applying the rules.
0.y.z is initial development (SemVer §4): anything MAY change at any time; the public API is not considered stable. Do not read a 0.4 → 0.5 bump as a "safe minor" — many ecosystems (npm by default) treat a 0.y.z MINOR bump as potentially breaking. Version 1.0.0 is what declares a stable public API; it is a semantic commitment, not a maturity badge.
- Deprecating public-API functionality requires a MINOR bump — deprecation is a forward-compatibility signal even before removal. Removing or renaming public API without an alias is MAJOR.
- Precedence: a pre-release version has lower precedence than its normal version (
1.0.0-rc.1 < 1.0.0), and build metadata (1.0.0+20130313144700) is ignored when determining precedence (SemVer §11). A release candidate never accidentally outranks the final release; never use build metadata to signal compatibility.
- Hyrum's Law caveat: at sufficient scale, all observable behavior of an API becomes something some consumer depends on, even behavior the written contract never promised. A bug-fix that changes observed output can break real consumers — weigh that before calling it a safe PATCH.
Pre-release tags
1.0.0-alpha.1, 1.0.0-beta.2, 1.0.0-rc.1
SemVer vs CalVer
| Aspect |
SemVer |
CalVer |
| Encodes |
API compatibility intent |
Release timeline |
| Best for |
Libraries, APIs, packages |
OS releases, browsers, enterprise |
| Weakness |
Subjective "what's breaking?" |
No compatibility signal |
3. Semantic Commit Messages (Conventional Commits)
Format: type(scope): description
| Type |
Purpose |
SemVer Bump |
feat |
New feature |
MINOR (spec-defined) |
fix |
Bug fix |
PATCH (spec-defined) |
docs |
Documentation only |
— † |
style |
Formatting, no logic change |
— † |
refactor |
Code change, no feature/fix |
— † |
perf |
Performance improvement |
— † |
test |
Adding/fixing tests |
— † |
build |
Build system, dependencies |
— † |
ci |
CI configuration |
— † |
chore |
Maintenance, no source change |
— † |
Spec Meaning vs Release-Tool Policy
Only three mappings are normative. Conventional Commits 1.0.0 assigns a SemVer effect to exactly three things: fix → PATCH, feat → MINOR, and a breaking change (! or BREAKING CHANGE: footer) → MAJOR. Every other type is allowed but carries no implicit SemVer effect unless your release tooling defines one (spec §"Summary" + the SemVer-relationship FAQ). The † rows above therefore depend on local configuration: many tools bump nothing for perf/refactor/docs by default, while some configs map perf → PATCH. Do not assert "perf is a PATCH bump" as a property of the spec — it is a property of a particular tool's config.
If one change honestly needs multiple types, split the commit whenever possible so the type remains a truthful signal. If it cannot be split, choose the type that communicates the externally observable change, then use the body/footer for secondary facts.
Breaking changes — add ! after the type/scope or a BREAKING CHANGE: footer → MAJOR bump. The footer token must be uppercase BREAKING CHANGE (or BREAKING-CHANGE); a lowercased "breaking change" is not recognized by tooling. ! and the footer may be used together — the ! is the at-a-glance signal, the footer carries the migration detail.
feat(auth)!: replace session tokens with JWT
BREAKING CHANGE: Session-based auth removed. All clients must use Bearer tokens.
Automation chain: Conventional Commits → semantic-release → changelog → publish. Fully automated versioning from commit messages. This is the concrete reason the type choice is semantic, not cosmetic: a feature mislabeled chore is silently dropped from the changelog and never triggers the MINOR bump it earned.
4. Semantic CSS
Class Names Describe Purpose, Not Appearance
| Bad (appearance) |
Good (purpose) |
.red-text |
.error-message |
.left-sidebar |
.navigation |
.big-button |
.primary-action |
.mt-4 (alone) |
.card__spacing (with utility supplement) |
BEM: .block__element--modifier
Names encode component structure. .card__title belongs to .card without reading HTML.
Design-Token Naming: Three-Layer Architecture
/* Layer 1: Primitive (raw values, no meaning) */
--blue-500: oklch(0.62 0.18 260);
--spacing-4: 1rem;
/* Layer 2: Semantic (purpose-driven) */
--color-text-primary: var(--grey-900);
--color-bg-danger: var(--red-100);
--spacing-component-gap: var(--spacing-4);
/* Layer 3: Component (scoped to specific component) */
--button-bg-hover: var(--color-bg-interactive-hover);
--card-border-radius: var(--radius-md);
Rule: UI code references semantic tokens. Semantic tokens reference primitives. Primitives hold raw values. Never skip a layer.
Anti-patterns: --light-blue (breaks on rebrand), --color-1 (meaningless), --homepage-hero-cta-bg (too coupled to one location).
The DTCG interchange format (stable Final Report, 2025.10)
The three-layer architecture above is the authoring discipline; the Design Tokens Community Group (DTCG) format is the vendor-neutral interchange format that lets that discipline travel between tools (Figma, Style Dictionary, Tokens Studio, etc.). It reached its first stable version — a Final Community Group Report, 2025.10, on 2025-10-28 (stable, but explicitly not a W3C Standard); the skill previously cited a /drafts/ URL, which is now superseded. Semantic primitives that matter for naming and meaning:
| DTCG concept |
Semantic role |
| Token name |
Human-readable sign for a design decision |
$value |
The value the token resolves to |
$type |
Category such as color, dimension, duration, number, fontWeight, typography, border, shadow — makes the value's kind explicit |
$description |
Human-readable explanation of intended use |
Alias / reference ({group.token}) |
A token can point to another token while keeping its own semantic name — the interchange equivalent of var(--…) |
| Group |
File organization only; tools should not infer a token's type or purpose from its group |
$deprecated |
Compatibility signal with optional reason/replacement — token removal or rename is a compatibility event for consumers |
{
"color": {
"text-primary": { "$value": "{grey.900}", "$type": "color", "$description": "Default body text" }
}
}
- Aliasing is what makes appearance-coupled names wrong — the reference syntax lets one semantic token (
--color-bg-danger / color.status.danger.bg) resolve to different primitive values under different themes, so a name baked to one appearance (--light-blue) cannot follow a theme switch. If color.status.success.bg aliases green today and blue tomorrow, the semantic token survives a rebrand because the sign's purpose stayed stable.
- File convention:
.tokens / .tokens.json, media type application/design-tokens+json.
- Treat richer theming and specific color-space support (OKLCH / Display-P3 / CSS Color 4) as the consuming tool's capability layered on the format, not as a guarantee the format spec itself makes — verify against the tool you target. For token taxonomy, component APIs, theming governance, and migration, route to
design-system-architecture / theme-system-design; semantics owns only whether the names and aliases preserve intended meaning.
Cascade Layers (@layer) — the layer name and order are a priority signal
CSS cascade layers (CSS Cascade Level 5) let you name groups of rules and declare their precedence explicitly, independent of source order or selector specificity. Both halves are semantic — semantics owns only the meaning of the layer names and the priority signal, not selector specificity, @scope, or full cascade mechanics (route those to CSS-architecture / frontend skills):
/* The order statement IS the contract: later layers win. */
@layer reset, base, components, utilities;
@layer components { .card { padding: 1rem; } }
@layer utilities { .p-0 { padding: 0; } } /* wins over components, despite equal specificity */
- The layer name encodes intent (
reset / base / theme / components / utilities) the same way a semantic token name does — @layer hacks or @layer z1 is the appearance-named --light-blue of the cascade: it says nothing about why its rules should win.
- The
@layer name, name, … statement encodes priority — reordering it silently changes which rules win across the whole sheet, with no selector change. Treat that order as a published contract; a reorder is a behavioral change, not a cosmetic one.
- Layer contents must match the layer's promise — component one-offs accumulating inside a
tokens layer is layer drift; move the rule to its owning layer.
- Unlayered styles outrank every layer, so "drop it outside a layer to force it to win" is a meaning-defeating escape hatch; an
overrides layer should be rare and justified — prefer fixing the owning layer or layer order.
5. Semantic Data Modeling
Column-Naming Rules
| Rule |
Bad |
Good |
| Describe what, not where |
external_price |
retail_price_cents |
| Include unit |
weight |
weight_grams |
| Include precision |
revenue |
revenue_cents (integer) |
| Boolean prefix |
active |
is_active |
| Timestamp suffix |
created |
created_at |
Unit and Temporal Naming
Numbers and timestamps are the places where syntax most often lies — a bare type passes every check while the meaning is wrong:
| Data shape |
Bad |
Good |
Meaning preserved |
| integer money |
price |
price_cents |
Unit and precision |
| decimal money |
price |
price_amount (+ currency) |
Representation choice |
| duration |
timeout |
timeout_ms |
Unit |
| weight |
weight |
weight_grams |
Unit |
| percentage |
discount |
discount_percent or discount_ratio |
50 vs 0.5 |
| instant |
created |
created_at |
Point in time |
| local date |
ship_at |
ship_date |
Calendar date, not instant |
| range |
period |
billing_period_start_at / billing_period_end_at |
Boundary semantics |
Semantic Types (Beyond Primitives)
A raw string can hold an email, URL, SQL query, or credit-card number — semantically different despite identical type. Semantic types make illegal states unrepresentable:
| Semantic Type |
Underlying |
Why Distinct |
Money |
{ amount: number, currency: string } |
Prevents mixing currencies |
Email |
validated string |
Ensures format, enables operations |
OrderId |
branded string |
Prevents passing a UserId where OrderId expected |
Percentage |
number (0–100 or 0–1) |
Prevents 50 vs 0.5 confusion |
Branded Types in TypeScript
TypeScript is structurally typed: two strings are interchangeable no matter what they mean. A brand adds a phantom, compile-time-only tag so the type system treats OrderId and UserId as distinct. Prefer a reusable helper and a parser/constructor boundary; two brand forms, chosen by stakes:
// Form A — string-tag brand. Covers most app code; one reusable helper.
type Brand<T, Tag extends string> = T & { readonly __brand: Tag };
type OrderId = Brand<string, 'OrderId'>;
type UserId = Brand<string, 'UserId'>;
function getOrder(id: OrderId): Order { /* ... */ }
getOrder(userId); // Compile error: UserId is not assignable to OrderId
// Form B — unique symbol brand. For libraries / high-stakes domains:
// the tag cannot be forged or collided with from another module.
declare const OrderIdBrand: unique symbol;
type OrderId2 = string & { readonly [OrderIdBrand]: true };
Smart constructor over raw cast. A brand only guarantees distinctness, not validity. Mint branded values through a validating constructor so the cast happens in exactly one audited place — this is the brand's bridge to parse-don't-validate below:
function parseOrderId(input: string): OrderId | null {
return /^ord_[0-9a-f]{16}$/.test(input) ? (input as OrderId) : null;
}
Rules:
- Brand only values where mixups are plausible and costly: IDs, units, money, URLs, emails, permission scopes, state-machine states.
- Mint branded values at a parser or smart constructor, not by scattering raw casts at call sites.
- Gotcha: the brand is a compile-time fiction. The phantom property does not exist at runtime — never write
if (value.__brand === 'OrderId'); it is always undefined. Runtime checks belong in the smart constructor.
- For soundness, narrowing, exhaustiveness, validator choice, and runtime-boundary mechanics, route to
type-safety; semantics owns only when a type/brand/unit name encodes the wrong meaning.
Parse, Don't Validate
Instead of checking data after the fact, parse it into a type that guarantees validity:
// Bad: validate then trust
function processEmail(input: string) {
if (!isValidEmail(input)) throw new Error('Invalid email');
sendEmail(input); // input is still just string
}
// Good: parse into semantic type
function parseEmail(input: string): Email | null {
return isValidEmail(input) ? (input as Email) : null;
}
The core idea: preserve the knowledge the parser gained in the type, instead of discarding it after a boolean check and re-validating downstream. A parseEmail that returns Email | null pushes the "is this valid?" question to the boundary once; everything past the boundary holds a value whose type already proves validity.
6. Semantic UI / UX
Affordances and Signifiers (Don Norman)
- Affordance — what an object allows you to do (a button affords pressing)
- Signifier — what communicates the affordance (the button's raised appearance, shadow, cursor change)
Semantic UI ensures signifiers match affordances: clickable things look clickable, draggable things look draggable, disabled things look disabled.
Semantic HTML — the element choice is the first signal
The HTML element you pick is a meaning declaration, read by browsers, assistive tech, and crawlers before any class or label. A <div onclick> styled to look like a button is the markup equivalent of process(data): it works mechanically but encodes none of the meaning.
| Meaning |
Semantic element |
Appearance-only anti-pattern |
| "This triggers an action" |
<button type="button"> |
<div onclick> (no keyboard focus, no role) |
| "This navigates" |
<a href> |
<span onclick> routing in JS |
| "This is the primary document content" |
<main>, <article> |
<div class="content"> |
| "This is a named region" |
<nav>, <header>, <footer>, <aside> |
stacked <div>s |
| "This is a heading" |
<h1> / <h2> |
<div class="title"> |
| "This emphasizes / is important" |
<em>, <strong> |
<i>, <b> (presentational only) |
| "This is a machine-readable time/date" |
<time datetime="2026-06-06"> |
<span>June 6</span> |
| "This is a list of peer items" |
<ul> / <ol> with <li> |
repeated <div> siblings |
| "This is tabular data" |
<table> |
grid of <div>s |
Rules:
- Choose the element whose native meaning matches the referent before adding ARIA or JavaScript behavior.
- Class names should describe the nature of the content or component role, not only the desired presentation.
- Do not derive business meaning from opaque
id values; use explicit data fields or structured attributes when meaning must be machine-readable.
- Verifying the resulting accessibility contract — roles, focus order, screen-reader announcement,
aria-* — belongs to a11y. Semantics flags the <div>-that-should-be-a-<button>; a11y proves the fix is actually accessible.
Semantic Color
| Color |
Western Meaning |
Risk |
| Red |
Danger, error, stop, loss |
Color-only distinction fails for users with color-vision differences |
| Green |
Success, go, profit, safe |
See above |
| Yellow / Amber |
Warning, caution, pending |
Low contrast on white backgrounds |
| Blue |
Information, link, trust |
Overloaded — can mean anything neutral |
| Grey |
Disabled, inactive, secondary |
Must have sufficient contrast |
Rule: color must never be the sole differentiator. Always pair with icon, text, or shape. (The color-redundancy rule is a semantic-signal rule that also reaches terminal output, logs, and data viz — not only product UI.)
Signifier Checks
Semantics can flag when a UI signal lies; semiotics and a11y own the deeper checks:
| Signal |
Semantic question |
Verify with |
| Color |
Does the color's judgment match the state, and is color not the only signal? |
semiotics, a11y, color-system-design |
| Icon |
Does the icon convention point to the intended action/state? |
semiotics |
| Disabled state |
Does it communicate unavailable rather than loading or low priority? |
semiotics, a11y |
| Native element |
Does the HTML element match action, navigation, heading, landmark, list, or temporal meaning? |
a11y, frontend skills |
| Button label |
Does the verb/object match the action? |
microcopy |
| Badge/status |
Does the label/color/icon point to one state, not several? |
semiotics, a11y |
Microcopy Semantics
- Button labels — verbs with clear objects: "Save changes" not "Submit"
- Error messages — name what broke + how to fix: "Password must be 8+ characters" not "Invalid input"
- Confirmations — name what will happen: "Delete 3 orders permanently?" not "Are you sure?"
- Empty states — explain value + provide action — not just "No data"
For the full UX-text pattern catalog (button label rules, empty-state structure, tooltip rules, dialog rules, toast rules), use the dedicated microcopy skill — semantics owns only the underlying meaning rule.
7. Semantic APIs
For the whole API surface (resources, schemas, pagination, idempotency, versioning, auth, error-envelope design) use api-design; for RFC-level method/status/header/cache detail use http-semantics. Semantics owns whether a specific name, status, field, or error signal tells the truth.
REST Resource Naming
| Rule |
Bad |
Good |
| Nouns, not verbs |
/getOrders |
/orders |
| Plural for collections |
/order |
/orders |
| HTTP method = verb |
POST /createUser |
POST /users |
| Kebab-case |
/orderItems |
/order-items |
| Max 2 nesting levels |
/a/1/b/2/c/3 |
/orders/123/items |
HTTP Status Codes as Semantic Signals
| Family |
Semantic Meaning |
Responsibility |
| 2xx |
Success |
Server fulfilled the request |
| 3xx |
Redirect |
Client must follow |
| 4xx |
Client error |
Client's fault |
| 5xx |
Server error |
Server's fault |
Do not report a failed HTTP request as 200 merely because the transport succeeded. RFC 9110 defines 200 as request success; if the request failed, the status code should carry that failure class. Domain-level partial success can still use a typed success payload when the request itself truly succeeded.
Choosing which code is itself a semantic decision — these distinctions carry meaning consumers act on:
| Code |
Signals |
Common mis-use it replaces |
201 Created |
A resource was created; include a Location header |
200 on a create |
202 Accepted |
Accepted for async processing, not yet done |
200 on a queued job |
204 No Content |
Success, intentionally empty body |
200 with {} |
400 Bad Request |
Malformed/unparseable request |
catch-all for any client error |
401 Unauthorized |
Not authenticated (no/invalid credentials) |
403 when the user simply isn't logged in |
403 Forbidden |
Authenticated but not permitted |
401 when the user is logged in |
404 Not Found |
Resource absent (or hidden for privacy) |
403/400 leaking existence; 200 with null |
409 Conflict |
State conflict (duplicate, version clash) |
400 for a stale-write |
412 Precondition Failed |
A conditional request's precondition failed |
silently overwriting lost-update protection |
415 Unsupported Media Type |
Content type unsupported |
generic 400 |
422 Unprocessable Content |
Syntactically valid but semantically invalid |
400 for a validation failure |
429 Too Many Requests |
Rate limited; pair with Retry-After |
503/400/500 for throttling |
503 Service Unavailable |
Temporary server-side unavailability |
500 hiding retryable semantics in the body |
Machine-readable error bodies — RFC 9457 Problem Details
A correct status code tells the client which class of failure occurred; the body should tell it what specifically went wrong, in a structured, predictable shape — not an ad-hoc { "error": "..." } invented per endpoint. RFC 9457 Problem Details for HTTP APIs (July 2023, Standards Track; obsoletes RFC 7807) is the vendor-neutral standard for that body. Media type application/problem+json; the object's reserved members are semantic:
{
"type": "https://example.com/probs/insufficient-funds",
"title": "Insufficient funds",
"status": 402,
"detail": "Account 12345 has a balance of 30, but the order total is 50.",
"instance": "/orders/9001"
}
type — a URI identifying the problem class (stable, dereferenceable, the machine key clients branch on).
title — short human-readable summary of the problem class (does not change per occurrence).
status — the HTTP status code, duplicated in-body so it survives proxies/logging; must match the actual response status when present.
detail / instance — explanation and identifier for this specific occurrence; detail is human-facing and should not be parsed for structured data. Typed extension members carry problem-specific structured data such as validation pointers.
RFC 9457 added (over 7807) explicit guidance for representing multiple problems (e.g. several field-validation errors via an errors extension) and a shared registry of problem types; when different problem types compete, represent the most relevant/urgent one rather than a vague batch envelope. The semantic rule: the status code is the class signal, the type URI is the *ma
…(truncated)
1---2name: semantics3description: Semantics: choosing and auditing the meaning encoded by names, status codes, versions, commits, tokens, and signals across code, APIs, and UIs. Do NOT use for Should onboarding be hyphenated, and how does English compound morphology affect that decision? Do NOT use for What casing should a new database timestamp column use -- kebab, snake, or camel? Do NOT use for Rename this function and update every call-site across the repo. Do NOT use for Type the relation between refund and payment as IS-A, PART-OF, causal, or thematic. Do NOT use for Design the full REST resource surface with pagination, idempotency keys, and auth boundaries.4license: MIT5---67# Semantics89## Concept of the skill1011Semantics in software is meaning encoding: every name, status code, version number, commit type, token, telemetry attribute, tool definition, and typed value is a sign that points at a referent under a convention.1213## Coverage1415Cross-domain semantic thinking for all naming and meaning decisions:1617- **Naming in code** — the fundamental what + why principle, verb-prefix return/side-effect contracts (`get`/`find`/`fetch`/`parse`/`ensure`/`assert`), naming smells (Peter Hilton's seven categories), machine-reader naming smells, DDD ubiquitous language, scalar/count/collection/boolean rules18- **Semantic versioning** — SemVer 2.0.0 (MAJOR.MINOR.PATCH), the declared-public-API precondition, what counts as breaking, the `0.y.z` initial-development rule, deprecation-as-MINOR, precedence, Hyrum's Law, SemVer vs CalVer19- **Semantic commit messages** — Conventional Commits format, type catalog (`feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`), the spec-vs-tooling SemVer mapping, breaking-change syntax, the SemVer-from-commits automation chain20- **Semantic CSS** — purpose-not-appearance class names, BEM (`.block__element--modifier`), three-layer design-token architecture (primitive → semantic → component), the DTCG stable interchange format (`$value`/`$type`/`$description`/`$deprecated`, `.tokens`), cascade-layer (`@layer`) names and order as an explicit priority signal21- **Semantic data modeling** — column-naming rules (unit suffixes, boolean prefixes, timestamp/instant/date/range distinctions), semantic types beyond primitives, branded TypeScript types (string-tag and `unique symbol`), smart constructors, parse-don't-validate22- **Semantic UI / UX** — Don Norman affordances and signifiers, semantic HTML element choice (`<button>` vs `<div onclick>`, `<time datetime>`, lists), semantic color (with the never-color-alone rule), signifier checks, microcopy semantics23- **Semantic APIs** — REST resource naming (nouns + HTTP verbs, plural collections, kebab-case, max-2-nesting), HTTP status codes as semantic signals (the never-200-with-error-body rule + within-family disambiguation), RFC 9457 Problem Details for machine-readable error payloads, GraphQL naming **and schema semantics** (field/enum/nullability as wire contract, `@deprecated`, `@oneOf`, the media-type-dependent transport-status rule)24- **Semantic tool, telemetry & schema contracts for machine readers** — LLM tool/function names, descriptions as routing prompts, argument enums and units, destructive-operation legibility, result-field contracts, OpenTelemetry observability-attribute conventions, prompt-artifact boundary names, and versioning machine-read artifacts25- **Universal anti-patterns** — generic names, semantic drift, misleading names, appearance-based names, abbreviation ambiguity, cargo-cult naming, machine-reader drift, unit drift, enum reuse2627## Philosophy of the skill28Every name is a micro-decision that compounds across the codebase. A function called `process(data)` forces every future reader to open the implementation to understand its purpose. A design token called `--light-blue` breaks the moment someone adds dark mode. An API returning HTTP 200 with an error body confuses every consumer. These are not style preferences — they are semantic failures that create real debugging time and real misunderstandings.2930This skill exists because naming quality degrades silently. No test catches `handleRefresh()` being reused for too many unrelated actions. No linter flags `--big-spacing`. No CI gate rejects `employee2`. Without explicit semantic rules loaded at decision time, agents default to the first name that compiles rather than the name that communicates. The discipline is to make meaning a first-class artifact, not a residue.3132The audience for names has widened. Names are no longer read only by humans and compilers — LLM-backed routers, agents, and tool-selection layers now read tool names, function names, and field names to decide what to invoke; dashboards and alerts read telemetry attribute names no human wrote at emit time; design-token transformers and GraphQL introspection clients read names as wire contracts. An ambiguous `provider` or a vague `process` degrades automated tool selection the same way it degrades a human reader, so semantic precision is now also a machine-routing concern, not only a readability one. (For the design of the tool/function interface itself, defer to agent-engineering and tool-call-flow; semantics owns only whether the chosen words encode the right meaning.)3334The mental model: a `Sign` (the name) acquires meaning from its `Referent` (what it points to) and from `Convention` (the shared agreement). `Affordance` (what the name implies you can do) is the bridge between sign and user expectation. **Semantic drift** happens when referents change but signs do not — a silent lie in the code, the kind no test catches.3536> Semantics is to code what road signage is to driving — the sign isn't the road, but bad signage causes crashes even when the road is fine.3738## Semantic Audit Workflow (Sign · Referent · Convention)3940When auditing any name, status, version, token, schema field, tool, or signal, run the same repeatable check — it operationalizes the mental model and works across every domain in this skill:41421. **Name the Sign.** What is the literal signal? (`process(data)`, `200`, `--light-blue`, `feat:`, `OrderId`, `get_user`, `payment.status`.)432. **Find the Referent.** What does it actually point at *now*? Read the implementation/behavior, not the name's promise. (Does `process` reconcile revenue? Did the request actually fail? Does the token resolve to a brand color? Can the tool delete?)443. **Check the Convention.** Under the shared agreement for this artifact kind (SemVer spec, Conventional Commits, RFC 9110, DTCG, OpenTelemetry conventions, DDD ubiquitous language, the local API style), does the Sign correctly encode the Referent?454. **Predict the inference.** Would a competent reader, client, design tool, router, or LLM infer the actual referent from the sign *without opening implementation details*?465. **Repair the mismatch.** Fix the Sign, split the Referent, or update the Convention — in the same change as the behavior change so the drift never ships. A comment explaining the lie is not a semantic repair.4748A mismatch at step 3/4 is **semantic drift** — the defect to fix.4950| Failure | Symptom | Repair |51|---|---|---|52| Referent changed, sign stayed | `handleRefresh()` now syncs, imports, and reconciles | Rename to the current operation or split the behavior |53| Convention changed, sign stayed | `--light-blue` now maps to a dark-theme accent | Move appearance to a primitive token and purpose to a semantic token |54| Sign overclaims certainty | `getOrder()` returns `undefined` | Rename to `findOrder()` or change behavior to `getOrderOrThrow()` |55| Sign hides unit | `revenue` stores integer cents | Rename to `revenue_cents` or wrap in `Money` |56| Sign hides protocol outcome | HTTP 200 with `{ "error": ... }` | Use the correct 4xx/5xx family plus a typed error body |57| Sign hides tool capability | `run` can delete records | Rename to the actual destructive action and make the boundary legible |5859## When to Use6061- Choosing semantically truthful names for variables, functions, classes, files, database columns, events, and types.62- Auditing whether a name still matches the behavior, domain concept, unit, or compatibility promise it now represents.63- Deciding whether one API name, status code, error code, field, or problem type tells the truth (route whole-surface design to api-design and protocol detail to http-semantics).64- Choosing semantic CSS classes, design-token names, or token layers that survive rebrand, dark mode, and component reuse.65- Choosing Conventional Commit type/scope or SemVer bump based on the meaning of a change.66- Modeling domain language with branded/refined types or parse-don't-validate boundaries.67- Auditing whether an LLM tool/function name, telemetry attribute, or prompt-artifact slot truthfully encodes its referent.68- Reviewing UI labels, colors, affordances, and status indicators for meaning alignment before microcopy or a11y checks.69- Reviewing code for naming quality when the problem is the meaning encoded by a name, not the mechanical rename.7071## Boundary Routing7273| User need | Use | Why |74|---|---|---|75| Word form, compound order, abbreviation policy, audience register, or blame-free phrasing | linguistics | Linguistics owns language form and register; semantics owns the meaning encoded by a signal. |76| Casing format, prefix/suffix convention, or rename mechanics across call sites | naming-conventions or refactor | Naming conventions and refactor own mechanical consistency; semantics decides whether the words are truthful. |77| Relation type between two concepts | semantic-relations | Semantic-relations owns IS-A, PART-OF, causal, thematic, synonymy, polysemy, and graph-edge typing. |78| Multi-channel visual sign systems (icons, badges, shapes, color metaphors) | semiotics | Semiotics owns visual/sign-system coherence; semantics owns the meaning of one textual identifier or signal. |79| Functional UI text pattern | microcopy | Microcopy owns button labels, empty states, dialogs, tooltips, validation, and toast text. |80| Full API surface: resources, schemas, pagination, idempotency, auth, error-envelope design | api-design | Api-design owns the whole surface; semantics owns whether one name/status/field tells the truth. |81| RFC-level method/status/header/cache protocol detail | http-semantics | Http-semantics owns the protocol contract; semantics owns meaning alignment when a signal contradicts its outcome. |82| Type soundness, narrowing, exhaustiveness, validator choice | type-safety | Type-safety owns the guarantees; semantics owns when a type/brand/unit name encodes the wrong meaning. |83| Telemetry strategy, span/metric/signal selection, dashboards, SLOs | observability-modeling | Observability-modeling owns instrumentation strategy; semantics owns whether one attribute/metric name tells the truth. |84| The LLM tool surface itself (which tools, granularity, statefulness, orchestration) | agent-engineering or tool-call-flow | Those own the interface design; semantics owns whether the chosen tool names/args/fields encode the right meaning. |85| Classification structure, facets, or category assignment rules | taxonomy-design | Taxonomy owns the structure; semantics owns the names and signals inside it. |86| Accessibility compliance or assistive-technology behavior | a11y | Semantics can detect a missing non-color signal or non-semantic element; a11y verifies the accessible contract. |87| Branching, rebasing, commit boundaries, release tags, or history shape | version-control | Version-control owns repository history; semantics owns version/commit meaning. |8889---9091## 1. Semantic Naming in Code9293### The Fundamental Principle9495A name must encode **what** something is, **why** it exists, and what contract it exposes to readers or tools. The reader should not need to open the implementation to distinguish the domain concept, unit, side effect, lifecycle state, or compatibility signal.9697### Rules9899| Rule | Bad | Good | Why |100|------|-----|------|-----|101| Use full words | `calc()`, `mgr`, `btn` | `calculateProfitAndMargin()`, `manager`, `button` | Abbreviations are ambiguous (`auth` = authentication or authorization?) |102| Encode intent | `data`, `info`, `result` | `unpaidInvoices`, `customerProfile`, `validationErrors` | Generic names carry zero information |103| Use domain language | `UserRequestsProduct` | `customerPlacesOrder` | Match stakeholder vocabulary (DDD ubiquitous language) |104| Name the distinction | `employee`, `employee2` | `manager`, `recentHire` | Numeric suffixes hide the actual difference |105| Scalars need qualifiers | `store = "text"` | `storeName = "text"` | `store` implies an object, not a string |106| Counts need suffixes | `products = 0` | `productCount = 0` | `products` implies a collection |107| Collections use plurals | `orderList` | `orders` | The data structure already implies "list" |108| Booleans read as questions | `active` | `isActive`, `hasOrders`, `canEdit` | Reads naturally in conditionals |109110### Verb Prefixes Encode a Return/Side-Effect Contract111112A function's leading verb is a promise about its return shape, cost, certainty, and side effects; readers and callers (human and LLM) route on it. Keep the verb honest:113114| Prefix / shape | Implied contract | Violated when |115|--------|------------------|---------------|116| `get` / `read` | Cheap, synchronous, always returns the value (or throws if absence is exceptional); no I/O surprise | `getUser()` makes a network call or silently returns `null` |117| `find` / `query` | Lookup where absence is normal; may return nothing (`T | null` / list) | `findOrder()` throws on a missing order instead of returning `null` |118| `fetch` | Async, does I/O, transport can fail | `fetchConfig()` reads an in-memory constant |119| `load` | Reads and initializes or caches | `loadX()` is a pure synchronous accessor |120| `list` | Returns a collection (possibly empty), never a scalar | `listUser()` returns one record |121| `is` / `has` / `can` / `should` | Returns a boolean now, positive polarity, no side effects | `isReady()` returns a promise, mutates state, or is inverted |122| `create` / `update` / `delete` | Mutates; not idempotent for create/delete | `createSession()` returns a cached existing session |123| `ensure` / `upsert` | Idempotent; safe to call repeatedly | `ensureDir()` throws if the dir exists |124| `compute` / `calculate` / `build` | Pure derivation from inputs, no I/O | `calculateTotal()` writes to the database |125| `parse` | Less-structured input → stronger typed output or structured failure | It only returns a boolean and discards the learned structure |126| `validate` | Checks validity, usually boolean or diagnostic result | It mutates input, or returns a stronger type without saying `parse` |127| `assert` | Throws if false; narrows on success | It returns ordinary data or performs business work |128129The prefix is the at-a-glance signal; when behavior and prefix disagree, the prefix lies and every caller inherits the wrong expectation. (When the work becomes artifact-specific casing, prefix/suffix policy, or rename mechanics, route to `naming-conventions`.)130131### Naming Smells (Peter Hilton's catalog)1321331. **Meaningless** — `foo`, `bar`, `tmp`, `x`1342. **Abstract** — `data`, `object`, `thing`, `item`1353. **Numeric suffixes** — `employee2` hides the distinction1364. **Abbreviations** — `acc` (accumulator? accuracy? account?)1375. **Vague verbs** — `get`, `process`, `handle`, `manage` say nothing about behavior1386. **Type-encoded** — `array`, `string`, `int` as variable names1397. **Weasel suffixes** — `Info`, `Data`, `Manager`, `Helper`, `Utils`, `Base`, `Entity`140141### Machine-Reader Naming Smells142143Names are now read by LLM routers, tool selectors, schema validators, design-token transformers, GraphQL introspection clients, and changelog generators. This adds several smells beyond Hilton's:144145| Smell | Example | Why it fails |146|---|---|---|147| Hallucinated specificity | `getUserInvoices()` actually returns all billing documents | Human and model readers infer a narrower referent than the code provides |148| Implementation-step naming | `runStep3()` / `doPipelineThing()` | The name encodes how the code was built, not the domain action |149| Tool affordance mismatch | MCP/OpenAI tool `update_record` can delete records | A model may call a destructive capability under a safe-looking name |150| Generic schema fields | `data`, `result`, `payload`, `status` everywhere | Machine readers cannot distinguish entity, state, and outcome semantics |151| Enum drift | `PENDING` now means queued, blocked, and waiting for payment | A stable wire value has accumulated multiple referents |152153### DDD Ubiquitous Language154155Core domain classes (entities, value objects, events, services) must use business terminology. Zero tolerance for weasel words. Technical terms are acceptable only in infrastructure classes where no domain equivalent exists.156157**Translation tax**: every time a developer mentally translates between code vocabulary and business vocabulary, cognitive overhead accumulates — and it compounds across teams and codebase lifetime.158159---160161## 2. Semantic Versioning (SemVer 2.0.0)162163### Format: `MAJOR.MINOR.PATCH`164165| Increment | When | Example |166|-----------|------|---------|167| **MAJOR** | Backward-incompatible API changes | Removing a method, changing return type |168| **MINOR** | New backward-compatible functionality | Adding an endpoint, new optional param |169| **PATCH** | Backward-compatible bug fixes only | Fixing a calculation, patching a security issue |170171### What Constitutes a Breaking Change172173- Removing a public method, class, endpoint, or field174- Changing return types or parameter types175- Changing default behavior users depend on176- Renaming public APIs without aliases177- Changing serialization format178- **NOT breaking**: adding optional params, new endpoints, new types179180### SemVer Nuances Agents Often Miss181182- **SemVer requires a declared public API.** If the public API is vague, the version number cannot communicate compatibility truthfully — declare the surface before applying the rules.183- **`0.y.z` is initial development (SemVer §4): anything MAY change at any time; the public API is not considered stable.** Do not read a `0.4 → 0.5` bump as a "safe minor" — many ecosystems (npm by default) treat a `0.y.z` MINOR bump as potentially breaking. Version `1.0.0` is what *declares* a stable public API; it is a semantic commitment, not a maturity badge.184- **Deprecating public-API functionality requires a MINOR bump** — deprecation is a forward-compatibility signal even before removal. Removing or renaming public API without an alias is MAJOR.185- **Precedence: a pre-release version has lower precedence than its normal version** (`1.0.0-rc.1 < 1.0.0`), and build metadata (`1.0.0+20130313144700`) is ignored when determining precedence (SemVer §11). A release candidate never accidentally outranks the final release; never use build metadata to signal compatibility.186- **Hyrum's Law caveat:** at sufficient scale, *all* observable behavior of an API becomes something some consumer depends on, even behavior the written contract never promised. A bug-fix that changes observed output can break real consumers — weigh that before calling it a safe PATCH.187188### Pre-release tags189190`1.0.0-alpha.1`, `1.0.0-beta.2`, `1.0.0-rc.1`191192### SemVer vs CalVer193194| Aspect | SemVer | CalVer |195|--------|--------|--------|196| Encodes | API compatibility intent | Release timeline |197| Best for | Libraries, APIs, packages | OS releases, browsers, enterprise |198| Weakness | Subjective "what's breaking?" | No compatibility signal |199200---201202## 3. Semantic Commit Messages (Conventional Commits)203204### Format: `type(scope): description`205206| Type | Purpose | SemVer Bump |207|------|---------|------------|208| `feat` | New feature | MINOR *(spec-defined)* |209| `fix` | Bug fix | PATCH *(spec-defined)* |210| `docs` | Documentation only | — † |211| `style` | Formatting, no logic change | — † |212| `refactor` | Code change, no feature/fix | — † |213| `perf` | Performance improvement | — † |214| `test` | Adding/fixing tests | — † |215| `build` | Build system, dependencies | — † |216| `ci` | CI configuration | — † |217| `chore` | Maintenance, no source change | — † |218219### Spec Meaning vs Release-Tool Policy220221**Only three mappings are normative.** Conventional Commits 1.0.0 assigns a SemVer effect to exactly three things: `fix` → PATCH, `feat` → MINOR, and a breaking change (`!` or `BREAKING CHANGE:` footer) → MAJOR. Every other type is **allowed but carries no implicit SemVer effect** unless your release tooling defines one (spec §"Summary" + the SemVer-relationship FAQ). The `† ` rows above therefore depend on local configuration: many tools bump nothing for `perf`/`refactor`/`docs` by default, while some configs map `perf` → PATCH. Do not assert "`perf` is a PATCH bump" as a property of the spec — it is a property of a particular tool's config.222223If one change honestly needs multiple types, split the commit whenever possible so the type remains a truthful signal. If it cannot be split, choose the type that communicates the externally observable change, then use the body/footer for secondary facts.224225**Breaking changes** — add `!` after the type/scope or a `BREAKING CHANGE:` footer → MAJOR bump. The footer token must be uppercase `BREAKING CHANGE` (or `BREAKING-CHANGE`); a lowercased "breaking change" is not recognized by tooling. `!` and the footer may be used together — the `!` is the at-a-glance signal, the footer carries the migration detail.226227```228feat(auth)!: replace session tokens with JWT229230BREAKING CHANGE: Session-based auth removed. All clients must use Bearer tokens.231```232233**Automation chain**: Conventional Commits → semantic-release → changelog → publish. Fully automated versioning from commit messages. This is the concrete reason the type choice is semantic, not cosmetic: a feature mislabeled `chore` is silently dropped from the changelog and never triggers the MINOR bump it earned.234235---236237## 4. Semantic CSS238239### Class Names Describe Purpose, Not Appearance240241| Bad (appearance) | Good (purpose) |242|-----------------|----------------|243| `.red-text` | `.error-message` |244| `.left-sidebar` | `.navigation` |245| `.big-button` | `.primary-action` |246| `.mt-4` (alone) | `.card__spacing` (with utility supplement) |247248### BEM: `.block__element--modifier`249250Names encode component structure. `.card__title` belongs to `.card` without reading HTML.251252### Design-Token Naming: Three-Layer Architecture253254```css255/* Layer 1: Primitive (raw values, no meaning) */256--blue-500: oklch(0.62 0.18 260);257--spacing-4: 1rem;258259/* Layer 2: Semantic (purpose-driven) */260--color-text-primary: var(--grey-900);261--color-bg-danger: var(--red-100);262--spacing-component-gap: var(--spacing-4);263264/* Layer 3: Component (scoped to specific component) */265--button-bg-hover: var(--color-bg-interactive-hover);266--card-border-radius: var(--radius-md);267```268269**Rule**: UI code references *semantic* tokens. Semantic tokens reference *primitives*. Primitives hold raw values. Never skip a layer.270271**Anti-patterns**: `--light-blue` (breaks on rebrand), `--color-1` (meaningless), `--homepage-hero-cta-bg` (too coupled to one location).272273### The DTCG interchange format (stable Final Report, 2025.10)274275The three-layer architecture above is the *authoring discipline*; the **Design Tokens Community Group (DTCG) format** is the vendor-neutral *interchange* format that lets that discipline travel between tools (Figma, Style Dictionary, Tokens Studio, etc.). It reached its **first stable version — a Final Community Group Report, 2025.10, on 2025-10-28** (stable, but explicitly *not* a W3C Standard); the skill previously cited a `/drafts/` URL, which is now superseded. Semantic primitives that matter for naming and meaning:276277| DTCG concept | Semantic role |278|---|---|279| Token name | Human-readable sign for a design decision |280| `$value` | The value the token resolves to |281| `$type` | Category such as `color`, `dimension`, `duration`, `number`, `fontWeight`, `typography`, `border`, `shadow` — makes the value's *kind* explicit |282| `$description` | Human-readable explanation of intended use |283| Alias / reference (`{group.token}`) | A token can point to another token while keeping its own semantic name — the interchange equivalent of `var(--…)` |284| Group | File organization only; tools should *not* infer a token's type or purpose from its group |285| `$deprecated` | Compatibility signal with optional reason/replacement — token removal or rename is a compatibility event for consumers |286287```json288{289 "color": {290 "text-primary": { "$value": "{grey.900}", "$type": "color", "$description": "Default body text" }291 }292}293```294295- **Aliasing is what makes appearance-coupled names wrong** — the reference syntax lets one *semantic* token (`--color-bg-danger` / `color.status.danger.bg`) resolve to different *primitive* values under different themes, so a name baked to one appearance (`--light-blue`) cannot follow a theme switch. If `color.status.success.bg` aliases green today and blue tomorrow, the semantic token survives a rebrand because the sign's *purpose* stayed stable.296- **File convention**: `.tokens` / `.tokens.json`, media type `application/design-tokens+json`.297- Treat richer theming and specific color-space support (OKLCH / Display-P3 / CSS Color 4) as the consuming *tool's* capability layered on the format, not as a guarantee the format spec itself makes — verify against the tool you target. For token taxonomy, component APIs, theming governance, and migration, route to `design-system-architecture` / `theme-system-design`; semantics owns only whether the names and aliases preserve intended meaning.298299### Cascade Layers (`@layer`) — the layer name and order are a priority signal300301CSS cascade layers (CSS Cascade Level 5) let you name groups of rules and declare their precedence explicitly, independent of source order or selector specificity. Both halves are semantic — semantics owns only the meaning of the layer names and the priority signal, **not** selector specificity, `@scope`, or full cascade mechanics (route those to CSS-architecture / frontend skills):302303```css304/* The order statement IS the contract: later layers win. */305@layer reset, base, components, utilities;306307@layer components { .card { padding: 1rem; } }308@layer utilities { .p-0 { padding: 0; } } /* wins over components, despite equal specificity */309```310311- **The layer *name* encodes intent** (`reset` / `base` / `theme` / `components` / `utilities`) the same way a semantic token name does — `@layer hacks` or `@layer z1` is the appearance-named `--light-blue` of the cascade: it says nothing about *why* its rules should win.312- **The `@layer name, name, …` statement encodes priority** — reordering it silently changes which rules win across the whole sheet, with no selector change. Treat that order as a published contract; a reorder is a behavioral change, not a cosmetic one.313- **Layer contents must match the layer's promise** — component one-offs accumulating inside a `tokens` layer is layer drift; move the rule to its owning layer.314- Unlayered styles outrank every layer, so "drop it outside a layer to force it to win" is a meaning-defeating escape hatch; an `overrides` layer should be rare and justified — prefer fixing the owning layer or layer order.315316---317318## 5. Semantic Data Modeling319320### Column-Naming Rules321322| Rule | Bad | Good |323|------|-----|------|324| Describe what, not where | `external_price` | `retail_price_cents` |325| Include unit | `weight` | `weight_grams` |326| Include precision | `revenue` | `revenue_cents` (integer) |327| Boolean prefix | `active` | `is_active` |328| Timestamp suffix | `created` | `created_at` |329330### Unit and Temporal Naming331332Numbers and timestamps are the places where syntax most often lies — a bare type passes every check while the meaning is wrong:333334| Data shape | Bad | Good | Meaning preserved |335|---|---|---|---|336| integer money | `price` | `price_cents` | Unit and precision |337| decimal money | `price` | `price_amount` (+ currency) | Representation choice |338| duration | `timeout` | `timeout_ms` | Unit |339| weight | `weight` | `weight_grams` | Unit |340| percentage | `discount` | `discount_percent` or `discount_ratio` | 50 vs 0.5 |341| instant | `created` | `created_at` | Point in time |342| local date | `ship_at` | `ship_date` | Calendar date, not instant |343| range | `period` | `billing_period_start_at` / `billing_period_end_at` | Boundary semantics |344345### Semantic Types (Beyond Primitives)346347A raw `string` can hold an email, URL, SQL query, or credit-card number — semantically different despite identical type. Semantic types make illegal states unrepresentable:348349| Semantic Type | Underlying | Why Distinct |350|--------------|------------|--------------|351| `Money` | `{ amount: number, currency: string }` | Prevents mixing currencies |352| `Email` | validated `string` | Ensures format, enables operations |353| `OrderId` | branded `string` | Prevents passing a `UserId` where `OrderId` expected |354| `Percentage` | `number` (0–100 or 0–1) | Prevents 50 vs 0.5 confusion |355356### Branded Types in TypeScript357358TypeScript is **structurally** typed: two `string`s are interchangeable no matter what they mean. A *brand* adds a phantom, compile-time-only tag so the type system treats `OrderId` and `UserId` as distinct. Prefer a reusable helper and a parser/constructor boundary; two brand forms, chosen by stakes:359360```typescript361// Form A — string-tag brand. Covers most app code; one reusable helper.362type Brand<T, Tag extends string> = T & { readonly __brand: Tag };363type OrderId = Brand<string, 'OrderId'>;364type UserId = Brand<string, 'UserId'>;365366function getOrder(id: OrderId): Order { /* ... */ }367getOrder(userId); // Compile error: UserId is not assignable to OrderId368369// Form B — unique symbol brand. For libraries / high-stakes domains:370// the tag cannot be forged or collided with from another module.371declare const OrderIdBrand: unique symbol;372type OrderId2 = string & { readonly [OrderIdBrand]: true };373```374375**Smart constructor over raw cast.** A brand only guarantees *distinctness*, not *validity*. Mint branded values through a validating constructor so the cast happens in exactly one audited place — this is the brand's bridge to parse-don't-validate below:376377```typescript378function parseOrderId(input: string): OrderId | null {379 return /^ord_[0-9a-f]{16}$/.test(input) ? (input as OrderId) : null;380}381```382383Rules:384385- Brand only values where mixups are plausible and costly: IDs, units, money, URLs, emails, permission scopes, state-machine states.386- Mint branded values at a parser or smart constructor, not by scattering raw casts at call sites.387- **Gotcha**: the brand is a *compile-time fiction*. The phantom property does not exist at runtime — never write `if (value.__brand === 'OrderId')`; it is always `undefined`. Runtime checks belong in the smart constructor.388- For soundness, narrowing, exhaustiveness, validator choice, and runtime-boundary mechanics, route to `type-safety`; semantics owns only when a type/brand/unit name encodes the wrong meaning.389390### Parse, Don't Validate391392Instead of checking data after the fact, parse it into a type that *guarantees* validity:393394```typescript395// Bad: validate then trust396function processEmail(input: string) {397 if (!isValidEmail(input)) throw new Error('Invalid email');398 sendEmail(input); // input is still just string399}400401// Good: parse into semantic type402function parseEmail(input: string): Email | null {403 return isValidEmail(input) ? (input as Email) : null;404}405```406407The core idea: **preserve the knowledge the parser gained** in the type, instead of discarding it after a boolean check and re-validating downstream. A `parseEmail` that returns `Email | null` pushes the "is this valid?" question to the boundary once; everything past the boundary holds a value whose type already proves validity.408409---410411## 6. Semantic UI / UX412413### Affordances and Signifiers (Don Norman)414415- **Affordance** — what an object allows you to do (a button affords pressing)416- **Signifier** — what communicates the affordance (the button's raised appearance, shadow, cursor change)417418Semantic UI ensures signifiers match affordances: clickable things look clickable, draggable things look draggable, disabled things look disabled.419420### Semantic HTML — the element choice is the first signal421422The HTML element you pick *is* a meaning declaration, read by browsers, assistive tech, and crawlers before any class or label. A `<div onclick>` styled to look like a button is the markup equivalent of `process(data)`: it works mechanically but encodes none of the meaning.423424| Meaning | Semantic element | Appearance-only anti-pattern |425|---|---|---|426| "This triggers an action" | `<button type="button">` | `<div onclick>` (no keyboard focus, no role) |427| "This navigates" | `<a href>` | `<span onclick>` routing in JS |428| "This is the primary document content" | `<main>`, `<article>` | `<div class="content">` |429| "This is a named region" | `<nav>`, `<header>`, `<footer>`, `<aside>` | stacked `<div>`s |430| "This is a heading" | `<h1>` / `<h2>` | `<div class="title">` |431| "This emphasizes / is important" | `<em>`, `<strong>` | `<i>`, `<b>` (presentational only) |432| "This is a machine-readable time/date" | `<time datetime="2026-06-06">` | `<span>June 6</span>` |433| "This is a list of peer items" | `<ul>` / `<ol>` with `<li>` | repeated `<div>` siblings |434| "This is tabular data" | `<table>` | grid of `<div>`s |435436Rules:437438- Choose the element whose native meaning matches the referent *before* adding ARIA or JavaScript behavior.439- Class names should describe the nature of the content or component role, not only the desired presentation.440- Do not derive business meaning from opaque `id` values; use explicit data fields or structured attributes when meaning must be machine-readable.441- **Verifying the resulting accessibility contract — roles, focus order, screen-reader announcement, `aria-*` — belongs to `a11y`.** Semantics flags the `<div>`-that-should-be-a-`<button>`; a11y proves the fix is actually accessible.442443### Semantic Color444445| Color | Western Meaning | Risk |446|-------|----------------|------|447| Red | Danger, error, stop, loss | Color-only distinction fails for users with color-vision differences |448| Green | Success, go, profit, safe | See above |449| Yellow / Amber | Warning, caution, pending | Low contrast on white backgrounds |450| Blue | Information, link, trust | Overloaded — can mean anything neutral |451| Grey | Disabled, inactive, secondary | Must have sufficient contrast |452453**Rule**: color must never be the *sole* differentiator. Always pair with icon, text, or shape. (The color-redundancy rule is a semantic-signal rule that also reaches terminal output, logs, and data viz — not only product UI.)454455### Signifier Checks456457Semantics can flag when a UI signal lies; semiotics and a11y own the deeper checks:458459| Signal | Semantic question | Verify with |460|---|---|---|461| Color | Does the color's judgment match the state, and is color not the only signal? | `semiotics`, `a11y`, `color-system-design` |462| Icon | Does the icon convention point to the intended action/state? | `semiotics` |463| Disabled state | Does it communicate unavailable rather than loading or low priority? | `semiotics`, `a11y` |464| Native element | Does the HTML element match action, navigation, heading, landmark, list, or temporal meaning? | `a11y`, frontend skills |465| Button label | Does the verb/object match the action? | `microcopy` |466| Badge/status | Does the label/color/icon point to one state, not several? | `semiotics`, `a11y` |467468### Microcopy Semantics469470- **Button labels** — verbs with clear objects: "Save changes" not "Submit"471- **Error messages** — name what broke + how to fix: "Password must be 8+ characters" not "Invalid input"472- **Confirmations** — name what will happen: "Delete 3 orders permanently?" not "Are you sure?"473- **Empty states** — explain value + provide action — not just "No data"474475For the full UX-text pattern catalog (button label rules, empty-state structure, tooltip rules, dialog rules, toast rules), use the dedicated `microcopy` skill — semantics owns only the underlying meaning rule.476477---478479## 7. Semantic APIs480481> For the *whole* API surface (resources, schemas, pagination, idempotency, versioning, auth, error-envelope design) use `api-design`; for RFC-level method/status/header/cache detail use `http-semantics`. Semantics owns whether a specific name, status, field, or error signal tells the truth.482483### REST Resource Naming484485| Rule | Bad | Good |486|------|-----|------|487| Nouns, not verbs | `/getOrders` | `/orders` |488| Plural for collections | `/order` | `/orders` |489| HTTP method = verb | `POST /createUser` | `POST /users` |490| Kebab-case | `/orderItems` | `/order-items` |491| Max 2 nesting levels | `/a/1/b/2/c/3` | `/orders/123/items` |492493### HTTP Status Codes as Semantic Signals494495| Family | Semantic Meaning | Responsibility |496|--------|------------------|---------------|497| 2xx | Success | Server fulfilled the request |498| 3xx | Redirect | Client must follow |499| 4xx | Client error | Client's fault |500| 5xx | Server error | Server's fault |501502**Do not report a failed HTTP request as 200 merely because the transport succeeded.** RFC 9110 defines 200 as request success; if the request failed, the status code should carry that failure class. Domain-level partial success can still use a typed success payload when the request itself truly succeeded.503504Choosing *which* code is itself a semantic decision — these distinctions carry meaning consumers act on:505506| Code | Signals | Common mis-use it replaces |507|------|---------|----------------------------|508| `201 Created` | A resource was created; include a `Location` header | `200` on a create |509| `202 Accepted` | Accepted for async processing, not yet done | `200` on a queued job |510| `204 No Content` | Success, intentionally empty body | `200` with `{}` |511| `400 Bad Request` | Malformed/unparseable request | catch-all for any client error |512| `401 Unauthorized` | Not authenticated (no/invalid credentials) | `403` when the user simply isn't logged in |513| `403 Forbidden` | Authenticated but not permitted | `401` when the user *is* logged in |514| `404 Not Found` | Resource absent (or hidden for privacy) | `403`/`400` leaking existence; `200` with `null` |515| `409 Conflict` | State conflict (duplicate, version clash) | `400` for a stale-write |516| `412 Precondition Failed` | A conditional request's precondition failed | silently overwriting lost-update protection |517| `415 Unsupported Media Type` | Content type unsupported | generic `400` |518| `422 Unprocessable Content` | Syntactically valid but semantically invalid | `400` for a validation failure |519| `429 Too Many Requests` | Rate limited; pair with `Retry-After` | `503`/`400`/`500` for throttling |520| `503 Service Unavailable` | Temporary server-side unavailability | `500` hiding retryable semantics in the body |521522### Machine-readable error bodies — RFC 9457 Problem Details523524A correct status code tells the client *which class* of failure occurred; the **body** should tell it *what specifically* went wrong, in a structured, predictable shape — not an ad-hoc `{ "error": "..." }` invented per endpoint. **RFC 9457 *Problem Details for HTTP APIs*** (July 2023, Standards Track; obsoletes RFC 7807) is the vendor-neutral standard for that body. Media type **`application/problem+json`**; the object's reserved members are semantic:525526```json527{528 "type": "https://example.com/probs/insufficient-funds",529 "title": "Insufficient funds",530 "status": 402,531 "detail": "Account 12345 has a balance of 30, but the order total is 50.",532 "instance": "/orders/9001"533}534```535536- `type` — a URI identifying the *problem class* (stable, dereferenceable, the machine key clients branch on).537- `title` — short human-readable summary of the problem class (does not change per occurrence).538- `status` — the HTTP status code, duplicated in-body so it survives proxies/logging; must match the actual response status when present.539- `detail` / `instance` — explanation and identifier for *this specific* occurrence; `detail` is human-facing and should not be parsed for structured data. Typed extension members carry problem-specific structured data such as validation pointers.540541RFC 9457 added (over 7807) explicit guidance for **representing multiple problems** (e.g. several field-validation errors via an `errors` extension) and a shared **registry of problem types**; when different problem types compete, represent the most relevant/urgent one rather than a vague batch envelope. The semantic rule: the status code is the *class signal*, the `type` URI is the *ma542543…(truncated)