diff --git a/src/quant_engine/snapshot_admin_server_v1.py b/src/quant_engine/snapshot_admin_server_v1.py
index 995b187..1bea589 100644
--- a/src/quant_engine/snapshot_admin_server_v1.py
+++ b/src/quant_engine/snapshot_admin_server_v1.py
@@ -42,11 +42,6 @@ QUALITATIVE_SELL_BROWSABLE_TABLES = (
EDITABLE_TABLES = {
"settings",
"account_snapshot",
- "collection_runs",
- "collection_snapshots",
- "collection_source_errors",
- "sell_strategy_results",
- "satellite_recommendations",
}
@@ -83,10 +78,23 @@ def list_browsable_tables(workspace_db_path: Path) -> list[dict[str, Any]]:
"row_count": row_count,
"editable": table in EDITABLE_TABLES,
})
+ tables.sort(key=lambda item: (
+ 0 if item["table"] == "account_snapshot" else 1 if item["table"] == "settings" else 2,
+ 0 if item["row_count"] else 1,
+ item["table"],
+ ))
return tables
-def fetch_table_rows(table: str, workspace_db_path: Path, *, limit: int = 50, offset: int = 0) -> dict[str, Any]:
+def fetch_table_rows(
+ table: str,
+ workspace_db_path: Path,
+ *,
+ limit: int = 50,
+ offset: int = 0,
+ filter_text: str = "",
+ column_filters: dict[str, str] | None = None,
+) -> dict[str, Any]:
db_path = _resolve_table_db(table, workspace_db_path)
if db_path is None:
raise ValueError(f"unknown or non-browsable table: {table}")
@@ -94,14 +102,27 @@ def fetch_table_rows(table: str, workspace_db_path: Path, *, limit: int = 50, of
return {"table": table, "db": str(db_path), "columns": [], "rows": [], "total": 0, "limit": limit, "offset": offset, "editable": table in EDITABLE_TABLES}
with sqlite3.connect(db_path) as conn:
conn.row_factory = sqlite3.Row
- total = conn.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0] # noqa: S608 - whitelisted table name
- cursor = conn.execute(
- f"SELECT rowid as _rowid, * FROM {table} ORDER BY rowid DESC LIMIT ? OFFSET ?", # noqa: S608 - whitelisted table name
- (limit, offset),
- )
- rows = [dict(row) for row in cursor.fetchall()]
+ cursor = conn.execute(f"SELECT rowid as _rowid, * FROM {table} ORDER BY rowid DESC",) # noqa: S608 - whitelisted table name
+ all_rows = [dict(row) for row in cursor.fetchall()]
columns = [description[0] for description in cursor.description] if cursor.description else []
- return {"table": table, "db": str(db_path), "columns": columns, "rows": rows, "total": total, "limit": limit, "offset": offset, "editable": table in EDITABLE_TABLES}
+ cleaned_filter_text = str(filter_text or "").strip().lower()
+ normalized_column_filters = {str(key): str(value).strip().lower() for key, value in (column_filters or {}).items() if str(value).strip()}
+
+ def _match_row(row: dict[str, Any]) -> bool:
+ display_row = {k: v for k, v in row.items() if not str(k).startswith("_")}
+ haystack = json.dumps(display_row, ensure_ascii=False, default=str).lower()
+ if cleaned_filter_text and cleaned_filter_text not in haystack:
+ return False
+ for key, needle in normalized_column_filters.items():
+ cell = str(display_row.get(key, "") or "").lower()
+ if needle not in cell:
+ return False
+ return True
+
+ filtered_rows = [row for row in all_rows if _match_row(row)]
+ total = len(filtered_rows)
+ rows = filtered_rows[offset: offset + limit]
+ return {"table": table, "db": str(db_path), "columns": columns, "rows": rows, "total": total, "limit": limit, "offset": offset, "editable": table in EDITABLE_TABLES, "filter_text": cleaned_filter_text, "column_filters": normalized_column_filters}
def fetch_domain_rows(domain: str, workspace_db_path: Path) -> dict[str, Any]:
@@ -503,6 +524,9 @@ def render_index_html() -> str:
padding: 0;
vertical-align: top;
font-size: 12px;
+ max-width: 220px;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.sheet th {
position: sticky;
@@ -537,6 +561,10 @@ def render_index_html() -> str:
z-index: 1;
}
.sheet th.rownum { z-index: 3; }
+ .sheet th.col-wide, .sheet td.col-wide { min-width: 180px; }
+ .sheet th.col-xwide, .sheet td.col-xwide { min-width: 260px; }
+ .sheet th.col-narrow, .sheet td.col-narrow { min-width: 88px; }
+ .sheet th.col-micro, .sheet td.col-micro { min-width: 68px; }
.note-box {
margin-top: 12px;
display: grid;
@@ -578,6 +606,42 @@ def render_index_html() -> str:
flex-wrap: wrap;
align-items: center;
}
+ .top-banner {
+ display: grid;
+ gap: 8px;
+ margin-top: 12px;
+ padding: 14px 16px;
+ border-radius: 16px;
+ border: 1px solid rgba(56, 189, 248, .28);
+ background: linear-gradient(135deg, rgba(14, 165, 233, .18), rgba(15, 23, 42, .92));
+ box-shadow: 0 16px 50px rgba(15, 23, 42, .35);
+ }
+ .top-banner-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 10px;
+ }
+ .top-banner .stat {
+ padding: 10px 12px;
+ border-radius: 12px;
+ background: rgba(2, 6, 23, .45);
+ border: 1px solid rgba(255,255,255,.08);
+ }
+ .top-banner .stat .label {
+ display: block;
+ font-size: 11px;
+ color: var(--muted);
+ margin-bottom: 4px;
+ text-transform: uppercase;
+ letter-spacing: .04em;
+ }
+ .top-banner .stat strong {
+ display: block;
+ color: var(--text);
+ font-size: 14px;
+ line-height: 1.35;
+ word-break: break-word;
+ }
.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -589,12 +653,111 @@ def render_index_html() -> str:
flex-wrap: wrap;
}
.sheet tr.selected td {
- background: rgba(56, 189, 248, .10);
+ background: rgba(14, 165, 233, .20);
+ box-shadow: inset 0 1px 0 rgba(186, 230, 253, .24), inset 0 -1px 0 rgba(186, 230, 253, .14);
}
.sheet tr.selected td.rownum {
- background: rgba(56, 189, 248, .18);
+ background: rgba(14, 165, 233, .36);
color: #e0f2fe;
}
+ .sheet tr.selected td[contenteditable="true"] {
+ background: rgba(34, 211, 238, .14);
+ outline: 1px solid rgba(103, 232, 249, .30);
+ outline-offset: -1px;
+ font-weight: 600;
+ }
+ .sheet tr.selected td.selected-field {
+ background: rgba(251, 191, 36, .12);
+ outline-color: rgba(251, 191, 36, .42);
+ }
+ .sheet tr.selected td.selected-field:focus {
+ background: rgba(251, 191, 36, .16);
+ outline-color: rgba(251, 191, 36, .78);
+ box-shadow: 0 0 0 2px rgba(251, 191, 36, .18);
+ }
+ .sheet tr.selected td[contenteditable="true"]:focus {
+ background: rgba(103, 232, 249, .18);
+ outline: 2px solid rgba(125, 211, 252, .78);
+ outline-offset: -2px;
+ box-shadow: 0 0 0 2px rgba(14, 165, 233, .16);
+ }
+ .table-banner {
+ display: grid;
+ gap: 8px;
+ margin-bottom: 12px;
+ padding: 12px 14px;
+ border-radius: 14px;
+ border: 1px solid rgba(148, 163, 184, .18);
+ background: rgba(15, 23, 42, .55);
+ }
+ .table-banner .meta-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
+ }
+ .table-banner .muted {
+ color: #cbd5e1;
+ }
+ .panel.snapshot-panel {
+ border-color: rgba(34, 197, 94, .22);
+ box-shadow: 0 0 0 1px rgba(34, 197, 94, .06) inset, 0 18px 50px rgba(15, 23, 42, .18);
+ }
+ .panel.snapshot-panel .panel-head {
+ background: linear-gradient(90deg, rgba(34, 197, 94, .12), rgba(15, 23, 42, 0));
+ border-bottom: 1px solid rgba(34, 197, 94, .16);
+ }
+ .panel.snapshot-panel .status {
+ border-left: 3px solid rgba(34, 197, 94, .55);
+ background: rgba(34, 197, 94, .08);
+ }
+ .snapshot-callout {
+ display: grid;
+ gap: 8px;
+ margin-top: 10px;
+ padding: 12px;
+ border-radius: 14px;
+ border: 1px solid rgba(34, 197, 94, .18);
+ background: rgba(3, 7, 18, .42);
+ }
+ .snapshot-callout strong { color: #dcfce7; }
+ .snapshot-callout .muted { color: #bbf7d0; }
+ .snapshot-detail-card {
+ display: grid;
+ gap: 8px;
+ padding: 12px;
+ border-radius: 14px;
+ border: 1px solid rgba(59, 130, 246, .16);
+ background: linear-gradient(180deg, rgba(15, 23, 42, .72), rgba(3, 7, 18, .48));
+ }
+ .snapshot-detail-card .meta-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
+ }
+ .snapshot-detail-card .field-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px;
+ }
+ .snapshot-detail-card .field {
+ padding: 8px 10px;
+ border-radius: 12px;
+ background: rgba(255,255,255,.04);
+ border: 1px solid rgba(255,255,255,.08);
+ font-size: 12px;
+ white-space: pre-wrap;
+ word-break: break-word;
+ }
+ .snapshot-detail-card .field strong {
+ display:block;
+ color: #dbeafe;
+ margin-bottom: 4px;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: .04em;
+ }
.diff-list {
display: grid;
gap: 8px;
@@ -788,10 +951,31 @@ def render_index_html() -> str:
Open table browser
+
+
+
+ Approval
+ Loading...
+
+
+ Lock
+ Loading...
+
+
+ Selection
+ No row selected.
+
+
+ Diff
+ Pending diff loading...
+
+
+
Snapshot approval state and lock state are pinned here for immediate review.
+
-
+
Workspace
@@ -935,7 +1119,7 @@ def render_index_html() -> str:
-
Settings
+
Settings 0 rows
@@ -968,7 +1152,7 @@ def render_index_html() -> str:
-
Account Snapshot
+
Account Snapshot 0 rows
@@ -979,6 +1163,10 @@ def render_index_html() -> str:
Canonical column order follows spec/15_account_snapshot_contract.yaml
+
+
Account snapshot editing surface
+
This panel is intentionally separated from settings so row selection, field edits, and save approval stay visually dominant.
+