Skip to content

UI: Center align coverage columns and standardize naming - #53

Open
execsumo wants to merge 115 commits into
mode-io:mainfrom
execsumo:main
Open

UI: Center align coverage columns and standardize naming#53
execsumo wants to merge 115 commits into
mode-io:mainfrom
execsumo:main

Conversation

@execsumo

Copy link
Copy Markdown

Centers matrix coverage columns, standardizes last column names, and makes Hooks tables visually consistent with Skills tables.

execsumo and others added 30 commits June 13, 2026 12:55
- Add hooks router, schemas, container setup and business logic
- Add comprehensive integration and unit tests for the hooks capability

Co-Authored-By: agy <antigravity-cli>
… registry

- Create hooks feature with components, sheets, API queries, models and localizations
- Add routes and wire hooks into App.tsx and capability registries (overview, sidebar, invalidation)
- Update README with Antigravity (agy) integration details

Co-Authored-By: agy <antigravity-cli>
Add Hooks column to the harness capability matrix (Claude Code: Yes,
others: Not Yet), a How-it-works Hooks section, the hooks manifest
paths, the local-first action, and check off Hook support on the
roadmap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… category terms

- Update HookSpec to utilize canonical event and match properties
- Add data migration path in store from-dict loader for raw Claude Code strings
- Update FastAPI endpoints and request/response validation schemas

Co-Authored-By: agy <antigravity-cli>
…sses

- Add specific hook codecs and representation filters for Codex, Cursor, OpenCode, and Antigravity
- Generalize FileBackedHooksAdapter to delegate layout formatting to codec mappers
- Register hook binding profiles to supported harness definitions in catalog

Co-Authored-By: agy <antigravity-cli>
…sheet views

- Update forms and state keys to submit and read canonical match categories and events
- Re-run OpenAPI type generator to sync typescript clients
- Adjust localizations and controllers to map new schemas

Co-Authored-By: agy <antigravity-cli>
…ecks

- Update HookStoreTests and FileBackedHooksAdapterTests to verify canonical parameters
- Add comprehensive HookRoutesTests verifying config updates for all 5 harnesses
- Add unit mappers tests covering round-tripping and format layout checks

Co-Authored-By: agy <antigravity-cli>
Map canonical user_prompt_submit to Antigravity PreInvocation (flat handler list, no matcher), surface representability caveat, and add round-trip + caveat + foreign-preservation tests.

Co-Authored-By: agy <antigravity-cli>
Hooks now sync across all feasible harnesses, not just Claude Code:
Codex and Cursor are full, OpenCode and Antigravity are partial. Update
the capability matrix and rewrite the How-it-works Hooks section to
describe the canonical event/category model and the representability
matrix (including the Antigravity PreInvocation caveat).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…to-global

The permissions Needs-Review page was a stub and the inventory discarded the
parsed spec for unmanaged entries (spec=null, displayName=hash), so existing
harness permission rules were invisible and unpromotable.

- inventory: populate spec + a readable display_name ("allow · shell: git *")
  for unmanaged entries from the harness-observed parsed_spec
- mutations/router: add promote_permission + POST /permissions/{id}/promote to
  adopt an observed rule into the managed manifest (reclassifies the existing
  binding without rewriting harness config)
- frontend: real PermissionsNeedsReviewPage listing unmanaged rules with a
  Promote action; filterPermissionsNeedsReview selector + promote mutation
- tests: integration round-trip (readable + promotable) and selector coverage

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the permissions fix for the hooks family: the Needs-Review page was a
stub and the inventory discarded the parsed spec for unmanaged entries, so
existing harness hooks were invisible and unpromotable.

- inventory: populate spec + readable display_name ("pre_tool_use · shell: cmd")
  for unmanaged entries from the harness-observed parsed_spec
- mutations/router: add promote_hook + POST /hooks/{id}/promote
- adapters: exclude bookkeeping `id` from hook drift comparison so a promoted
  external hook reads back as managed rather than drifted (neutral for hooks
  skill-manager itself wrote, which already match by id)
- frontend: real HooksNeedsReviewPage listing unmanaged hooks with a Promote
  action; filterHooksNeedsReview selector + promote mutation
- tests: integration round-trip (readable + promotable) and selector coverage

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Hooks

Hooks was reusing MCP's iconKey ("mcp"), making the two indistinguishable
in the sidebar. Give Hooks its own iconKey and a Webhook icon.

Also bring MCP Servers and Hooks to parity with Skills/Slash Commands by
adding a Board view (Disabled everywhere / Selective / Enabled everywhere
drag columns), scoped to single-card drag only - no multi-drag, no
optimistic pinning, since MCP's enable path can pop a config dialog
mid-drag that a pin can't safely anticipate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Hermes Agent is now a catalog harness (CLI probe `hermes`, root ~/.hermes)
with skills, MCP (~/.hermes/mcp.json, `mcpServers`), and slash-command
(~/.hermes/commands, frontmatter) bindings, so it flows app-wide. Closing
the slash allowlist gap: SlashTargetId + TARGET_ORDER now include hermes
(backend models/schema, frontend types), openapi regenerated. Hooks and
permissions are intentionally unbound (see handoff.md).

Settings "Harness roots" drop the trailing /skills and show the harness
root the app writes into. All home paths render as ~/… via a frontend
display-only formatter (lib/paths: formatHomePath + useFormatPath +
HomeDirProvider) fed by a new homeDir field on /api/health; API values
stay absolute so keys and the MCP config-choice round-trip are unaffected.

Also: remove README.zh-CN.md from this fork; add handoff.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…with fork

Adopts the product-accurate Hermes Agent implementation from upstream
mode-io/skill-manager PR mode-io#51 (commit 4f085f8), replacing our earlier
speculative one whose formats were unverified guesses:

- MCP: ~/.hermes/config.yaml (YAML) under `mcp_servers`, standalone
  HermesMapper with no `type` field (stdio=command/args/env,
  http/sse=url/transport/headers). Adds ruamel.yaml for round-trip-safe
  YAML I/O and refactors FileBackedMcpAdapter to mutate the document
  in place (_ensure_subtree) so YAML comments/quoting survive — this
  changes the write path for every config-subtree MCP harness.
- Skills: categorized layout ~/.hermes/skills/<category>/<skill>/;
  shared skills link under the `skill-manager` category.
- Hub-awareness: reads Hermes' .hub/lock.json + .bundled_manifest to
  exclude official/builtin/optional and self-learned skills, adopting
  only external-hub skills; threads `origin_harness` provenance through
  the skill store manifest.
- Home override: SKILL_MANAGER_HERMES_HOME → HERMES_HOME → ~/.hermes.

Reconciled with fork-only work that upstream lacks: kept our Hermes
slash-command binding (still provisional — upstream deliberately omits
it) and its SlashTargetId/TARGET_ORDER allowlist entries, the `agy`
harness, and our theme-neutral hermes-logo.svg (dropped upstream's png).

Independently verified: backend 309+133, typecheck, npm 269, build,
openapi (no drift) all green; CommentedMap read-back path confirmed
against a live YAML config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PR mode-io#51 cherry-pick merge left two Hermes Agent cells in the harness
logo table: upstream's (referenced the dropped hermes-logo.png — a now
broken image link) and ours (hermes-logo.svg). Collapse to one cell using
the existing svg and upstream's real product docs link
(hermes-agent.nousresearch.com), placed last to match the frontend
HarnessLogoKey order.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 0b54469 upstream merge resolution re-introduced the duplicate Hermes
README cell (fixed in 1224eb1), added a duplicate 'hermes' key in _MAPPERS,
and restored the png logo this fork replaced with svg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
execsumo and others added 30 commits July 24, 2026 17:39
The detail endpoint returned only name/description/prompt/tools, which
is not enough to render a Skills-style detail view. It now also returns
the raw document, the store path, and a per-harness row carrying state,
install path, and installMethod (symlink vs rendered vs none) — so the
view can show where each copy actually lives and why Codex differs.

Also adds a regression test that PUT preserves fields the client omits;
the current edit dialog blanks them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…arse

'About' was sourced from the `description` frontmatter key alone, and the
writer re-rendered files from only name/description/tools — so every
other key was destroyed on the first save. The 11 real Claude agents in
this workspace carry model, permissionMode, maxTurns, disallowedTools,
skills, mcpServers, effort, and hooks. All of it was collateral damage.

The parser now keeps the frontmatter mapping verbatim and the writer
merges edits into it, so keys we do not interpret survive untouched. The
one exception is our own retired compile keys (capabilities, harnesses),
which are still dropped since nothing reads them.

The detail view gains a Configuration section listing every remaining
key/value as-is — no per-harness whitelist to maintain, so Cursor's
readonly/is_background and Codex's sandbox_mode will display without
further work. Replaces the tool chips, whose hardcoded white border
broke in light mode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`effort: ""` round-tripped to `effort:`, which reparses as null rather
than the empty string it was. Quote empty strings on write.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
README now states what Skill Manager actually does with agent
frontmatter: it interprets name/description/tools and leaves every other
key alone, rather than "shrinking" files to those three.

plan-agents-simplify.md decision 6 said the opposite of the shipped
behavior, so it is marked amended with the corrected model appended —
including the lesson, since the same bug shape (rewriting a whole
artifact from the subset of fields you understand) shipped twice in this
rebuild.

handoff.md gains the detail-view and frontmatter work, the two frontend
wire-contract failures that passed their own mocked tests, and the final
suite counts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last column's value ("0 / 5", "Active", "Action") rendered offset from
its header and past the row's right edge, on every matrix table.

MatrixTable emitted a <colgroup> that assumed a fixed cell layout: checkbox,
identity, N harness, compact, coverage. Real views don't match it — the agents
and permissions/hooks review pages render no compact cell and end in Action
instead of coverage — and the compact/harness cells are toggled per breakpoint
with `display: none`, which generates no box and so occupies no column at all.
Every mismatch shifted the trailing cells one column left, parking the last
value in the zero-width compact column (its content overflowing, uncentered)
and leaving the real 96px column empty at the right edge.

Drop the colgroup: with `table-layout: fixed` the widths already come from the
header cells' own `matrix-table__th--*` classes, which track exactly the cells
each view renders at each breakpoint. That leaves `harnessColumnCount` and
`hasCheckbox` with nothing to do, so they're gone from the props and callers.

Tests can't catch this class of bug — jsdom has no layout — so they now assert
the invariant that broke instead: header and body agree cell-for-cell, and
MatrixTable declares no columns of its own. Verified visually in the app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…argets

One sequence everywhere: Claude, Codex, Antigravity, Cursor, OpenCode,
Hermes, OpenClaw. Every family (Settings, Skills, MCP, Hooks, Permissions,
Agents) already derives column order from SUPPORTED_HARNESS_DEFINITIONS via
bindings_for_family, so reordering the catalog reorders all of them at once.

Slash commands was the one family with its own curated TARGET_ORDER, which
is why OpenCode led that matrix. Drop it and resolve targets the way Agents
does: catalog order, minus the harnesses disabled in Settings. A disabled
harness now leaves the slash matrix entirely instead of showing as a dead
column.

Filtering also drops that harness's tracked and unmanaged review rows. The
command files and sync records are untouched on disk — re-enabling the
harness brings the rows back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The review matrix had a fallback that synthesized its columns from the review
rows when the payload's target list came back empty. That fallback ordered
columns by whichever row was seen first and hardcoded enabled: true — the two
things this change is meant to fix, reintroduced in the one view the report
was about.

It was also unreachable: a review row can only exist for a target the payload
lists, so rows are empty whenever targets are. Drop it and read the payload,
which is already canonically ordered and already excludes disabled harnesses.

Also aligns the hand-written SlashTargetId union with the backend Literal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…loop

_manage_entry called require_enabled_adapter at the top of its binding loop,
so whether it rejected a missing install *before* creating any binding
depended entirely on which harness the loop reached first. That happened to
be Codex, which is why
test_manage_rejects_missing_harness_install_before_creating_bindings passed:
Codex was first in the catalog, so its check fired before Claude's copy was
adopted.

Reordering the catalog put Claude first and exposed it — Claude's copy was
adopted, then Codex raised, leaving a half-applied mutation. The test asserts
no binding is created, which is the intended guarantee; it was just being met
by accident. Hoist the validation into a pre-flight pass so it holds
regardless of catalog order.

The paired assertion changes are behavioral, not cosmetic: the managed
package directory is ingested from harness_sightings[0], so with Claude first
the store now names it after Claude's copy (trace-lens-copy) instead of
Codex's (trace-lens). Existing managed skills are unaffected; this only
applies to new manage operations on a skill found under different names in
different harnesses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ly at once

resolve_slash_targets ran once at container build and its result was frozen
into the read-model and mutation services, so disabling a harness in Settings
did not leave the slash matrix until the process restarted. Agents already
solved this at container.py:295; use the same shape — a plain callable,
resolved once per public method into a local.

migrate_legacy_slash_commands keeps its one-shot build-time resolve. It is a
migration, not a read path.

Adds a regression test that disables a harness through the settings route and
asserts the very next GET /api/slash-commands omits it, with no rebuild.
Regenerates the OpenAPI schema for the reordered SlashTargetId enum.

Implementation by agy; reviewed, and validation re-run independently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Now that resolved targets exclude harnesses disabled in Settings, the
`not target.enabled` branch in _require_target was dead and every disabled
target fell through to "unknown slash command target: opencode" — which is
wrong twice over: the target is known, and the reason is recoverable by
flipping it back on in Settings.

Route it to "harness support is disabled", the phrasing MCP, hooks,
permissions, and skills already use for this case.

The accompanying test documents a two-layer behavior worth knowing: an id
that is not a slash-command harness at all never reaches the mutation layer,
because the request schema's Literal rejects it with a 422 first. The
"unknown slash command target" message is reachable only from internal
callers.

Docs: README states the canonical harness order and where it is declared, so
the next person adding a harness edits the catalog tuple and nothing else,
plus the no-restart and no-data-loss behavior of disabling a harness. New
handoff entry records the ordering rule and the two latent bugs the reorder
exposed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The repo root had accumulated four one-off codemod scripts (fix_slash.py,
patch_mcp.py, patch_slash.py, update_reviews.py) from the review-matrix
work. Their edits are all present in committed source, so the scripts were
spent — removed.

Ignore what should never have been a candidate for commit in the first
place: .DS_Store, the 28M .codegraph index, .rtk state, and the per-agent
instruction files local tooling generates (.clinerules, .agents/). Nothing
previously tracked is affected.

Also drops the RTK instruction block that had been appended to CLAUDE.md.
It duplicates the user's global ~/.claude config verbatim, and this file is
a shared working agreement in a fork that can be contributed upstream —
machine-local tooling guidance does not belong in it. `rtk init` regenerates
it if wanted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d UI

Salvaged from the stale permissions-ux-redesign branch (c2d2770), which was
87 commits behind main. Its second commit — the Tier 2 page unification —
is deliberately left behind: it merges In Use and Needs Review into one page,
which contradicts the 2026-07-24 decision to standardize every family on
those two views.

Permissions carry a decision (allow/ask/deny) AND a per-harness applied
state. The UI reused "Enabled/Disabled" for the latter on rows that already
carry a decision, producing nonsense like "Enable all" on a deny rule. The
applied-state axis is now Applied / Not applied / Differs / Untracked, and
the decision chip owns allow/ask/deny.

Drops the bulk-select column from the In Use matrix. It was decorative:
PermissionsInUsePage passed `checkedIds={new Set()}` and
`onToggleChecked={() => {}}`, so it could never select anything. The Needs
Review page keeps its checkboxes — those are wired to real state.

Drift resolution reads "Keep central" / "Take harness's" rather than "Use
central spec" / "Adopt harness spec", and promotion is "Adopt" throughout.

Resolved against main rather than replayed:
- MatrixTable keeps no <colgroup> (b967504) and does not regain the
  `hasCheckboxColumn` prop. That prop existed only to keep the colgroup
  aligned; with widths coming from header-cell classes it has no purpose.
- The matrix identity cell keeps main's OverflowTooltipText inside the
  branch's <button>, so it stays keyboard-accessible without losing
  overflow handling.
- Needs Review keeps main's matrix layout and "Adopt all eligible" action;
  the branch's older card layout is discarded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The branch survives at fork/permissions-ux-redesign, so without this note
the next person finds two unmerged commits and no reason they were split.
States which half landed, and that the page-unification half is a
cross-family design decision rather than a branch to merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The SPA catch-all guarded traversal with str.startswith(dist_root), which a
sibling directory whose name extends "dist" (e.g. dist-backup/) satisfies.
Use Path.is_relative_to and add a regression test with a decoy sibling.
The local API is unauthenticated by design, which is only safe if browsers
cannot be driven to it. Add an ASGI guard that (a) rejects non-loopback
Host headers, closing DNS-rebinding, and (b) rejects mutations whose
Origin is non-loopback, closing cross-site simple-request CSRF. Local
non-browser clients send no Origin and are unaffected.

Binding a non-loopback --host now requires an explicit --allow-remote
flag with a loud warning; start(1) passes it through to the spawned
serve process.
… package

The LLM scan feature was removed earlier but left its optional deps
(azure-identity, google-genai, litellm), its prompt payloads (still shipped
as package-data and in the PyInstaller spec), and stale test artifacts.

The skill_manager.db package was fully dead: Database was constructed and
stored on the container but no router, service, or test ever used it, while
every launch created skill-manager.db and ran four migrations. Settings live
in settings.json. Also dedupe the double certifi pin.
The three marketplace clients hardcoded skill-manager/0.1 while the app is
at 0.3.1.
…dits

npm audit flagged two high-severity advisories in the production tree;
the in-range bump clears all but GHSA-qwww-vcr4-c8h2, which only affects
unstable RSC/server-action code paths this client-only SPA never executes
(patched in 8.3.0; allowlisted with justification).

Add scripts/audit_gate.cjs (npm audit minus a reviewed allowlist) as
npm run audit:check, wire it into frontend-validate, and add pip-audit
to backend-compat.
npm audit attributes advisories inline on the owning package and as plain
strings on dependents (react-router-dom via ["react-router"]). The first
version treated the string as an unattributed finding, so the allowlist
could never match it. Resolve strings through the vulnerabilities map and
dedupe; add AUDIT_GATE_JSON_FILE to replay recorded audit payloads so the
parsing is testable offline.
The file now tracks open items only; the Tier 1 batch that landed in
98c3417 is recorded in handoff.md. Former Tiers 2-4 become 1-3, and
cross-references (including the now-shipped audit gate and the deleted
db package) are updated.
Tier-1 recommendations batch (RECOMMENDATIONS.md):

- supply-chain: .github/dependabot.yml (npm+pip+github-actions, weekly) and SHA-pin every action in ci.yml/release.yml with # vN comments

- lint: ruff gate enforcing I (import sorting, applied tree-wide) + F (pyflakes), baselined green via [tool.ruff] in pyproject.toml; requirements-dev.txt + 'Backend lint' CI step

- tests: tests/unit/test_writer_round_trip.py pins idempotency, owned-field preservation, and loss-surface characterization for the slash frontmatter codec and every MCP transport mapper

- harness: provisional support_note on the Hermes slash binding (surfaces via SlashTarget.supportNote)

isort-only reorg applied across the tree (mechanical; full suite green). A blanket ruff --fix was attempted and reverted (it broke test collection), so the baseline cleanup is deferred to a verified per-module pass. pyright/ESLint and writer field-preservation hardening remain (tracked in RECOMMENDATIONS.md).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants