diff --git a/bible-local/architecture/data-model.md b/bible-local/architecture/data-model.md
index e7d915a..e800237 100644
--- a/bible-local/architecture/data-model.md
+++ b/bible-local/architecture/data-model.md
@@ -43,7 +43,8 @@ For array sections:
## Special Field Handling
-- `status` is rendered as a colored badge
+- `status` is rendered as a colored badge; in table sections it may collapse to an icon-only presentation column
+- `severity` may render as both its source text field and a separate icon-only leading table column
- arrays such as `mac_addresses` may be rendered as line-separated values or badges inside one cell
- nested values such as `status_history` may be rendered in expandable detail blocks inside one cell
diff --git a/bible-local/architecture/runtime-flows.md b/bible-local/architecture/runtime-flows.md
index 25ea49f..05fb7aa 100644
--- a/bible-local/architecture/runtime-flows.md
+++ b/bible-local/architecture/runtime-flows.md
@@ -15,7 +15,16 @@
1. read the raw `status` value from the payload
2. normalize only for presentation matching (`OK`, `Warning`, `Critical`, `Unknown`, `Empty`)
3. apply status badge class
-4. do not change the raw value shown to the user
+4. in dense table layouts, render `status` as an icon-only column with an empty header when it improves scanning
+5. preserve the raw status value in accessible labeling even when the visible cell shows only a pictogram
+
+## Severity Presentation Flow
+
+1. read the raw `severity` value from the payload
+2. map the raw value only to a presentation glyph/color class
+3. when a table includes `severity`, add a leftmost icon-only column for it
+4. keep the original textual `severity` column visible in the table
+5. preserve the raw severity value in accessible labeling for the pictogram cell
## Unknown Field Invariant
diff --git a/bible-local/architecture/ui-information-architecture.md b/bible-local/architecture/ui-information-architecture.md
index a519d56..c0a1b59 100644
--- a/bible-local/architecture/ui-information-architecture.md
+++ b/bible-local/architecture/ui-information-architecture.md
@@ -60,4 +60,6 @@ Preferred order:
- tables must remain readable on desktop and mobile
- section navigation must work without JavaScript
-- color must not be the only status indicator; always show text
+- color must not be the only status indicator; pair it with a shape or glyph
+- table `status` columns may use icon-only cells and an empty header when that improves scanability, but the raw status value must remain available via accessible labeling
+- table sections with `severity` may add a separate leftmost icon-only column for fast scanning while keeping the textual `severity` field visible
diff --git a/bible-local/decisions/2026-04-22-status-columns-use-icon-only-presentation.md b/bible-local/decisions/2026-04-22-status-columns-use-icon-only-presentation.md
new file mode 100644
index 0000000..008f3a7
--- /dev/null
+++ b/bible-local/decisions/2026-04-22-status-columns-use-icon-only-presentation.md
@@ -0,0 +1,31 @@
+# Decision: Status Table Columns Use Icon-Only Presentation
+
+**Date:** 2026-04-22
+**Status:** active
+
+## Context
+
+Dense hardware tables frequently repeat the same `status` values.
+
+Showing a textual `status` header and textual badges in every row wastes horizontal space and reduces scan speed, especially in sensor subtables.
+
+The viewer still needs to keep status meaning explicit and avoid relying on color alone.
+
+## Decision
+
+Table columns named `status` render as compact icon-only columns.
+
+This includes:
+
+- an empty visible header cell for the `status` column
+- a minimal-width table column sized for the pictogram
+- a glyph plus color to distinguish state
+- accessible labeling that preserves the raw status value without showing repeated text in the cell
+
+Object sections may continue to show status as a regular field value.
+
+## Consequences
+
+- Table layouts gain more room for source fields such as `name`, `model`, and `location`.
+- Status meaning remains available to assistive technologies even when the visible cell is icon-only.
+- Future table UI work should keep `status` compact instead of reintroducing wide text badges by default.
diff --git a/viewer/render.go b/viewer/render.go
index 30f857e..d2787cf 100644
--- a/viewer/render.go
+++ b/viewer/render.go
@@ -49,6 +49,7 @@ var hiddenTableFields = map[string]struct{}{
const vendorDeviceIDField = "ven:dev"
var commonPreferredColumns = []string{
+ "severity_icon",
"status",
"slot",
"location",
@@ -333,6 +334,9 @@ func collectColumns(section string, rows []map[string]any) []string {
}
seen[key] = struct{}{}
}
+ if hasSeverity(row) {
+ seen["severity_icon"] = struct{}{}
+ }
if hasVendorDeviceID(row) {
seen[vendorDeviceIDField] = struct{}{}
}
@@ -494,6 +498,9 @@ func formatStringValue(value string) string {
}
func formatRowValue(column string, row map[string]any) string {
+ if column == "severity_icon" {
+ return strings.TrimSpace(formatValue(row["severity"]))
+ }
if column == vendorDeviceIDField {
return formatVendorDeviceID(row)
}
@@ -589,6 +596,10 @@ func hasVendorDeviceID(value map[string]any) bool {
return formatVendorDeviceID(value) != ""
}
+func hasSeverity(value map[string]any) bool {
+ return strings.TrimSpace(formatValue(value["severity"])) != ""
+}
+
func isHiddenTableField(section string, key string) bool {
if isHiddenField(key) {
return true
diff --git a/viewer/render_test.go b/viewer/render_test.go
index d89a137..0cbf6e3 100644
--- a/viewer/render_test.go
+++ b/viewer/render_test.go
@@ -47,6 +47,18 @@ func TestRenderHTMLIncludesKnownSectionsAndFields(t *testing.T) {
t.Fatalf("expected rendered html to contain %q", needle)
}
}
+ for _, needle := range []string{
+ `
`,
+ `
`,
+ ``,
+ } {
+ if !strings.Contains(text, needle) {
+ t.Fatalf("expected rendered html to contain %q", needle)
+ }
+ }
+ if strings.Contains(text, "
status
") {
+ t.Fatalf("expected status table headers to be rendered without visible text")
+ }
if strings.Contains(text, "2026-03-15T12:00:00Z") {
t.Fatalf("expected RFC3339 timestamp to be rendered in human-readable form")
@@ -266,6 +278,12 @@ func TestRenderHTMLGroupsPCIeDevicesByClass(t *testing.T) {
if strings.Contains(text, "
device_class
") {
t.Fatalf("expected device_class column to be hidden from PCIe tables")
}
+ if !strings.Contains(text, `
`) {
+ t.Fatalf("expected grouped PCIe tables to render compact status header cells")
+ }
+ if !strings.Contains(text, ``) {
+ t.Fatalf("expected grouped PCIe tables to render icon-only status cells with accessible labels")
+ }
if strings.Index(text, "
Display controller
") > strings.Index(text, "
Network controller
") {
t.Fatalf("expected PCIe class groups to be sorted by device_class")
}
@@ -307,10 +325,17 @@ func TestRenderHTMLAddsSeverityFilterForEventLogs(t *testing.T) {
``,
`data-severity="critical"`,
`data-severity="info"`,
+ `
`,
+ ``,
+ ``,
+ `
severity
`,
"/static/view.js",
} {
if !strings.Contains(text, needle) {
t.Fatalf("expected rendered html to contain %q", needle)
}
}
+ if strings.Contains(text, "