From 19bd9bcee0859fb114bac3842d907d44c93c9684 Mon Sep 17 00:00:00 2001 From: webmixgamer Date: Thu, 22 Oct 2026 16:01:13 +0100 Subject: [PATCH] feat(skills): Select all / Deselect all in Assign skills, and a confirm before a save drops an approval requirement (abilityai/trinity-enterprise#539) The Assign skills dialog gains Select all and Deselect all on the list's header row. Both change only the draft (Save still writes, Reset restores the saved set) and act only on the rows the dialog lists: - Select all never ticks a deprecated skill (a held one keeps its tick), a set's locked row, or a name the PUT refuses. - Deselect all unticks every listed skill except a set-only one. - An assignment whose skill left the library is kept by both; its card's Unassign removes it. Each button is disabled with its reason as the title (not loaded, no library, empty library, a save in flight, nothing to change), and the dialog never opens on Deselect all. Save now asks first when it would take a skill with an approval requirement off the agent, because a person's unassign drops it (trinity-enterprise#753). A save that removes anything re-reads the gate map first; an unreadable map asks for any removal; a name an assigned set still holds, or one in conflict, does not ask. Focus no longer falls out of the dialog when the control holding it is disabled or removed: a bulk click, a manual tick, Reset, Save, Confirm. The rules are the pure utils/skillAssignDraft.js (node spec); the wiring is mounted through the real Teleport. 27 mutants, all killed. Co-Authored-By: Claude Opus 5.5 --- docs/memory/architecture/frontend.md | 2 +- docs/memory/feature-flows/skill-assignment.md | 10 +- docs/memory/feature-flows/skills-tab.md | 19 +- ...-that-disables-itself-drops-modal-focus.md | 23 + ...ort-stub-recreates-nodes-on-every-patch.md | 11 + docs/memory/requirements/skills.md | 2 + .../automation/skills-and-playbooks.md | 2 +- .../components/skills/SkillAssignModal.vue | 206 +++++++- src/frontend/src/utils/skillAssignDraft.js | 160 +++++++ .../tests/unit/skillAssignDraft.spec.js | 218 +++++++++ .../tests/unit/skillAssignSelectAll.spec.js | 442 ++++++++++++++++++ tests/registry.json | 21 + 12 files changed, 1097 insertions(+), 19 deletions(-) create mode 100644 docs/memory/learnings/2026-10-22-a-control-that-disables-itself-drops-modal-focus.md create mode 100644 docs/memory/learnings/2026-10-22-a-teleport-stub-recreates-nodes-on-every-patch.md create mode 100644 src/frontend/src/utils/skillAssignDraft.js create mode 100644 src/frontend/tests/unit/skillAssignDraft.spec.js create mode 100644 src/frontend/tests/unit/skillAssignSelectAll.spec.js diff --git a/docs/memory/architecture/frontend.md b/docs/memory/architecture/frontend.md index a715bc431d..5d5a261915 100644 --- a/docs/memory/architecture/frontend.md +++ b/docs/memory/architecture/frontend.md @@ -45,7 +45,7 @@ **Library system-install section (ent#126):** the install surface is a third stacked `
` on `views/Library.vue`, between Agent Templates and Skills. It originally shipped as a `?tab=`-driven strip on `Templates.vue`; ent#263 renamed that page to **Library** ("installable assets for your fleet") and chose stacked sections with jump anchors over tabs/filter pills, so ent#126 conforms to the page's model rather than reintroducing a competing one — the ordering puts Systems next to Agent Templates because both *install agents* (a template creates one, a manifest creates a wired fleet), leaving Skills last as the one that configures agents that already exist. The section is hidden outright below `creator` (gated on `hasMinRole('creator')`, mirroring `POST /api/systems/deploy`) rather than shown-and-disabled, since a browse surface gains nothing from a dead panel. It hosts `components/systems/` — `SystemInstallPanel` (bundled cards / upload / paste + Preview + Deploy) → `ManifestPreview` (agents, permission topology, schedules, blockers, acknowledgement gate) → `DeployResult` (all five `status` values) — over the new domain-scoped `stores/systems.js` (Invariant #6: a *System* is a manifest-deployed name-prefix group, a *System View* is a saved tag filter — different domains). The store's `normalizeError` collapses six response shapes and switches on `status`, never the HTTP code (`partial`/`invalid` are 200; `failed` is **500 with the full report as the body**), and binds `preview` to `previewedText` so Deploy is disabled after an edit. Plain `