mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-28 22:17:48 +02:00
* feat(core): add skill detail path and query helpers - paths.workspace(slug).skillDetail(id) → /:slug/skills/:id - skillDetailOptions(wsId, skillId) for fetching a single skill - selectSkillAssignments(agents) folds the cached agent list into Map<skillId, Agent[]>; returns a stable reference so consumers can memoize against agent-array identity without re-rendering on unrelated agent updates Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(views): add cross-platform openExternal helper On Electron, route through window.desktopAPI.openExternal so the http/https-only guard in the main process kicks in — direct window.open inside Electron opens a new renderer window instead of handing the URL to the OS shell. On web, fall back to window.open with noopener+noreferrer. SSR-safe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(skills): extract edit-permission hook and origin helper - use-can-edit-skill: mirrors the server's rule (admin/owner ∨ creator) so the UI can hide/disable actions instead of waiting for a 403. Takes wsId explicitly per the repo rule for workspace-aware hooks. - lib/origin: discriminated view over Skill.config.origin (manual / runtime_local / clawhub / skills_sh) so consumers don't spread JSONB parsing across the UI tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(skills): rewrite skills list page and collapse import UI - SkillsPage rewritten: new hero header, single table layout with columns (Name / Used by / Source · Added by / Updated), agent avatar stack per skill, filter tabs aligned with Issues/MyIssues header (Button variant=outline + Tooltip + bg-accent active state). - CreateSkillDialog: dedicated dialog for the manual/import entry points, replaces the inline row-triggered dialog. - runtime-local import: dialog variant deleted; panel is now the single entry point, embeddable inside CreateSkillDialog. Panel covered by a new test. - Deleted runtime-local-skill-row (no longer needed — row rendering lives in SkillsPage directly) and the old skills-page.test.tsx (structure diverged beyond salvaging; will be re-added alongside the detail-page tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(skills): add skill detail page and wire routes on web and desktop - SkillDetailPage: dedicated view for a single skill (name, description, origin, assignments, file listing). Uses skillDetailOptions and the new origin / use-can-edit-skill helpers. - apps/web: /:workspaceSlug/skills/:id Next.js route. - apps/desktop: /:slug/skills/:id added to the memory router under WorkspaceRouteLayout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(skills): bump runtime-local-skill-import-panel timeouts for CI The test chains a five-step async cascade (runtime list → setSelectedRuntimeId effect → skills query → auto-select effect → row render). Comfortable on local (~600ms) but tight against RTL's 1 s default on CI where jsdom + Vitest import takes ~100s. Bump findByText and the two waitFor calls to 5 s each — no production behaviour change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
50 lines
1.8 KiB
TypeScript
50 lines
1.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { paths, isGlobalPath } from "./paths";
|
|
|
|
describe("paths.workspace(slug)", () => {
|
|
const ws = paths.workspace("acme");
|
|
|
|
it("builds dashboard paths with slug prefix", () => {
|
|
expect(ws.issues()).toBe("/acme/issues");
|
|
expect(ws.issueDetail("abc-123")).toBe("/acme/issues/abc-123");
|
|
expect(ws.projects()).toBe("/acme/projects");
|
|
expect(ws.projectDetail("p1")).toBe("/acme/projects/p1");
|
|
expect(ws.autopilots()).toBe("/acme/autopilots");
|
|
expect(ws.autopilotDetail("a1")).toBe("/acme/autopilots/a1");
|
|
expect(ws.agents()).toBe("/acme/agents");
|
|
expect(ws.inbox()).toBe("/acme/inbox");
|
|
expect(ws.myIssues()).toBe("/acme/my-issues");
|
|
expect(ws.runtimes()).toBe("/acme/runtimes");
|
|
expect(ws.skills()).toBe("/acme/skills");
|
|
expect(ws.skillDetail("skl_123")).toBe("/acme/skills/skl_123");
|
|
expect(ws.settings()).toBe("/acme/settings");
|
|
});
|
|
|
|
it("URL-encodes special characters in ids", () => {
|
|
expect(ws.issueDetail("id with space")).toBe("/acme/issues/id%20with%20space");
|
|
});
|
|
});
|
|
|
|
describe("paths (global)", () => {
|
|
it("builds global paths without slug", () => {
|
|
expect(paths.login()).toBe("/login");
|
|
expect(paths.newWorkspace()).toBe("/workspaces/new");
|
|
expect(paths.invite("inv-1")).toBe("/invite/inv-1");
|
|
expect(paths.authCallback()).toBe("/auth/callback");
|
|
});
|
|
});
|
|
|
|
describe("isGlobalPath", () => {
|
|
it("returns true for pre-workspace routes", () => {
|
|
expect(isGlobalPath("/login")).toBe(true);
|
|
expect(isGlobalPath("/workspaces/new")).toBe(true);
|
|
expect(isGlobalPath("/invite/abc")).toBe(true);
|
|
expect(isGlobalPath("/auth/callback")).toBe(true);
|
|
});
|
|
|
|
it("returns false for workspace-scoped paths", () => {
|
|
expect(isGlobalPath("/acme/issues")).toBe(false);
|
|
expect(isGlobalPath("/")).toBe(false);
|
|
});
|
|
});
|