From 927a11833853f8d7e846da36008ef7a3e1f23b71 Mon Sep 17 00:00:00 2001 From: Hein Date: Sun, 26 Apr 2026 23:13:41 +0200 Subject: [PATCH] feat(ui): add maintenance page for task management * Implement maintenance page with task and log display * Add backfill and metadata retry functionality * Integrate grid component for project display in thoughts page * Update types for maintenance tasks and logs * Enhance sidebar and shell for new maintenance navigation --- Makefile | 18 +- README.md | 34 + internal/app/admin_actions.go | 79 ++ internal/app/app.go | 3 + internal/app/resolvespec_admin_test.go | 18 + internal/app/status.go | 2 + internal/app/status_test.go | 9 + internal/auth/access_tracker.go | 91 +- internal/auth/access_tracker_test.go | 42 + .../mcpserver/streamable_integration_test.go | 80 ++ internal/tools/learnings.go | 18 + internal/tools/learnings_test.go | 70 ++ llm/memory.md | 11 +- migrations/001_enable_vector.sql | 1 + migrations/020_generated_schema.sql | 248 +++- migrations/100_rls_and_grants.sql | 15 +- migrations/{ => _old}/002_create_thoughts.sql | 0 migrations/{ => _old}/003_add_projects.sql | 0 .../{ => _old}/004_create_thought_links.sql | 0 .../{ => _old}/005_create_match_thoughts.sql | 0 .../{ => _old}/007_embeddings_table.sql | 0 .../{ => _old}/008_update_match_thoughts.sql | 0 migrations/{ => _old}/010_fulltext_index.sql | 0 .../{ => _old}/011_household_knowledge.sql | 0 .../{ => _old}/012_home_maintenance.sql | 0 migrations/{ => _old}/013_family_calendar.sql | 0 migrations/{ => _old}/014_meal_planning.sql | 0 .../{ => _old}/015_professional_crm.sql | 0 .../{ => _old}/016_create_stored_files.sql | 0 .../017_agent_skills_guardrails.sql | 0 migrations/{ => _old}/018_chat_histories.sql | 0 .../{ => _old}/019_tool_annotations.sql | 0 ui/package.json | 22 +- ui/pnpm-lock.yaml | 1034 ++++++----------- ui/src/App.svelte | 22 +- ui/src/api.ts | 124 +- .../components/dashboard/AccessTable.svelte | 2 + .../dashboard/ConnectionBreakdown.svelte | 43 + .../components/dashboard/DashboardPage.svelte | 16 + .../components/dashboard/StatusCards.svelte | 20 +- .../components/learnings/LearningsPage.svelte | 219 ++++ .../maintenance/MaintenancePage.svelte | 184 +++ .../components/projects/ProjectsPage.svelte | 99 +- ui/src/components/shell/AdminShell.svelte | 6 + ui/src/components/shell/AppSidebar.svelte | 4 +- .../components/thoughts/ThoughtsPage.svelte | 444 +++++-- ui/src/gridTheme.ts | 18 + ui/src/types.ts | 100 +- 48 files changed, 2228 insertions(+), 868 deletions(-) create mode 100644 internal/app/admin_actions.go create mode 100644 internal/tools/learnings_test.go rename migrations/{ => _old}/002_create_thoughts.sql (100%) rename migrations/{ => _old}/003_add_projects.sql (100%) rename migrations/{ => _old}/004_create_thought_links.sql (100%) rename migrations/{ => _old}/005_create_match_thoughts.sql (100%) rename migrations/{ => _old}/007_embeddings_table.sql (100%) rename migrations/{ => _old}/008_update_match_thoughts.sql (100%) rename migrations/{ => _old}/010_fulltext_index.sql (100%) rename migrations/{ => _old}/011_household_knowledge.sql (100%) rename migrations/{ => _old}/012_home_maintenance.sql (100%) rename migrations/{ => _old}/013_family_calendar.sql (100%) rename migrations/{ => _old}/014_meal_planning.sql (100%) rename migrations/{ => _old}/015_professional_crm.sql (100%) rename migrations/{ => _old}/016_create_stored_files.sql (100%) rename migrations/{ => _old}/017_agent_skills_guardrails.sql (100%) rename migrations/{ => _old}/018_chat_histories.sql (100%) rename migrations/{ => _old}/019_tool_annotations.sql (100%) create mode 100644 ui/src/components/dashboard/ConnectionBreakdown.svelte create mode 100644 ui/src/components/learnings/LearningsPage.svelte create mode 100644 ui/src/components/maintenance/MaintenancePage.svelte create mode 100644 ui/src/gridTheme.ts diff --git a/Makefile b/Makefile index bc6e572..e75b982 100644 --- a/Makefile +++ b/Makefile @@ -20,10 +20,26 @@ LDFLAGS := -s -w \ -X $(BUILDINFO_PKG).Commit=$(COMMIT_SHA) \ -X $(BUILDINFO_PKG).BuildDate=$(BUILD_DATE) -.PHONY: all build clean migrate release-version test generate-migrations generate-models check-schema-drift build-cli ui-install ui-build ui-dev ui-check +.PHONY: all build clean migrate release-version test generate-migrations generate-models check-schema-drift build-cli ui-install ui-build ui-dev ui-check help all: build +help: + @echo "Available targets:" + @echo " build Build server binary (includes UI build)" + @echo " build-cli Build CLI binary" + @echo " test Run all tests (includes UI check)" + @echo " clean Remove build artifacts" + @echo " migrate Run database migrations" + @echo " release-version Tag and push a new patch release (PATCH_INCREMENT=N)" + @echo " generate-migrations Generate SQL migration from DBML schema files" + @echo " generate-models Generate Go models from DBML schema" + @echo " check-schema-drift Verify generated migration matches current schema" + @echo " ui-install Install UI dependencies" + @echo " ui-build Build UI assets" + @echo " ui-dev Start UI dev server" + @echo " ui-check Run UI type checks" + build: ui-build @mkdir -p $(BIN_DIR) go build -ldflags "$(LDFLAGS)" -o $(SERVER_BIN) $(CMD_SERVER) diff --git a/README.md b/README.md index 73638b8..ff97098 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,9 @@ The AMCS directory is used to store configuration and code for the Advanced Modu | `get_project_context` | Recent + semantic context for a project; uses explicit `project` or the active session project | | `set_active_project` | Set session project scope; requires a stateful MCP session | | `get_active_project` | Get current session project | +| `add_learning` | Create a curated learning record distinct from raw thoughts | +| `get_learning` | Retrieve a structured learning by ID | +| `list_learnings` | List structured learnings by project/category/area/status/priority/tag/query | | `summarize_thoughts` | LLM prose summary over a filtered set | | `recall_context` | Semantic + recency context block for injection | | `link_thoughts` | Create a typed relationship between thoughts | @@ -66,6 +69,17 @@ The AMCS directory is used to store configuration and code for the Advanced Modu | `describe_tools` | List all available MCP tools with names, descriptions, categories, and model-authored usage notes; call this at the start of a session to orient yourself | | `annotate_tool` | Persist your own usage notes for a specific tool; notes are returned by `describe_tools` in future sessions | +## Learnings + +Learnings are curated, structured memory records for durable insights you want to keep distinct from raw thoughts. Use them for normalized lessons, decisions, and evidence-backed findings that should be easy to retrieve and review over time. + +Compared with `capture_thought`, learnings are more explicit and reviewable: they include a required `summary`, optional `details`, and structured fields like `category`, `area`, `status`, `priority`, `confidence`, and `tags`, plus optional links to a `project`, `related_thought_id`, or `related_skill_id`. + +Use: +- `add_learning` to create a curated learning. +- `get_learning` to fetch one by ID. +- `list_learnings` to filter curated learnings across project and status dimensions. + ## Self-Documenting Tools AMCS includes a built-in tool directory that models can read and annotate. @@ -598,6 +612,26 @@ Run the SQL migrations against a local database with: The web UI now lives in the top-level `ui/` module and is embedded into the Go binary at build time with `go:embed`. +### Admin UI deployment model + +AMCS uses a **lightweight embedded SPA panel** model: + +- the Svelte admin app is compiled to static assets +- assets are embedded in the server binary and served from `/` +- backend APIs (`/api/status`, `/api/rs/*`, admin action routes, OAuth endpoints) stay on the same origin +- auth is enforced server-side for all sensitive API routes + +This keeps deployment simple (single binary/container) while preserving SPA ergonomics for operator workflows. + +### UI stack baseline + +The admin frontend baseline is: + +- Svelte 5 for the app shell and pages +- ResolveSpec-backed APIs for data access +- `@warkypublic/svelix` for admin UX components (including `GridlerFull` and form controllers) +- `@warkypublic/artemis-kit` as the default JavaScript tooling dependency baseline in `ui/package.json` + **Use `pnpm` for all UI work in this repo.** - `make build` — runs the real UI build first, then compiles the Go server diff --git a/internal/app/admin_actions.go b/internal/app/admin_actions.go new file mode 100644 index 0000000..97c8157 --- /dev/null +++ b/internal/app/admin_actions.go @@ -0,0 +1,79 @@ +package app + +import ( + "encoding/json" + "log/slog" + "net/http" + + "git.warky.dev/wdevs/amcs/internal/tools" +) + +type adminActions struct { + backfill *tools.BackfillTool + retry *tools.EnrichmentRetryer + logger *slog.Logger +} + +func newAdminActions(backfill *tools.BackfillTool, retry *tools.EnrichmentRetryer, logger *slog.Logger) *adminActions { + return &adminActions{ + backfill: backfill, + retry: retry, + logger: logger, + } +} + +func (a *adminActions) backfillHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + w.Header().Set("Allow", http.MethodPost) + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + var in tools.BackfillInput + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } + + _, out, err := a.backfill.Handle(r.Context(), nil, in) + if err != nil { + if a.logger != nil { + a.logger.Warn("admin backfill failed", slog.String("error", err.Error())) + } + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(out) + }) +} + +func (a *adminActions) retryMetadataHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + w.Header().Set("Allow", http.MethodPost) + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + var in tools.RetryEnrichmentInput + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } + + _, out, err := a.retry.Handle(r.Context(), nil, in) + if err != nil { + if a.logger != nil { + a.logger.Warn("admin metadata retry failed", slog.String("error", err.Error())) + } + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(out) + }) +} diff --git a/internal/app/app.go b/internal/app/app.go index 4f8e020..f5b82ca 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -191,6 +191,7 @@ func routes(logger *slog.Logger, cfg *config.Config, info buildinfo.Info, db *st filesTool := tools.NewFilesTool(db, activeProjects) enrichmentRetryer := tools.NewEnrichmentRetryer(context.Background(), db, bgMetadata, cfg.Capture, cfg.AI.Metadata.Timeout, activeProjects, logger) backfillTool := tools.NewBackfillTool(db, bgEmbeddings, activeProjects, logger) + adminActions := newAdminActions(backfillTool, enrichmentRetryer, logger) toolSet := mcpserver.ToolSet{ Capture: tools.NewCaptureTool(db, embeddings, cfg.Capture, activeProjects, enrichmentRetryer, backfillTool), @@ -236,6 +237,8 @@ func routes(logger *slog.Logger, cfg *config.Config, info buildinfo.Info, db *st mux.HandleFunc("/api/oauth/register", oauthRegisterHandler(dynClients, logger)) mux.HandleFunc("/api/oauth/authorize", oauthAuthorizeHandler(dynClients, authCodes, logger)) mux.HandleFunc("/api/oauth/token", oauthTokenHandler(oauthRegistry, tokenStore, authCodes, logger)) + mux.Handle("/api/admin/actions/backfill", authMiddleware(adminActions.backfillHandler())) + mux.Handle("/api/admin/actions/retry-metadata", authMiddleware(adminActions.retryMetadataHandler())) mux.HandleFunc("/favicon.ico", serveFavicon) mux.HandleFunc("/images/project.jpg", serveHomeImage) mux.HandleFunc("/images/icon.png", serveIcon) diff --git a/internal/app/resolvespec_admin_test.go b/internal/app/resolvespec_admin_test.go index 83b2846..9e1cb87 100644 --- a/internal/app/resolvespec_admin_test.go +++ b/internal/app/resolvespec_admin_test.go @@ -61,6 +61,7 @@ func TestResolveSpecGuardAllowsSupportedMutations(t *testing.T) { entity string operation string }{ + {name: "learnings read", entity: "learnings", operation: "read"}, {name: "projects create", entity: "projects", operation: "create"}, {name: "thoughts update", entity: "thoughts", operation: "update"}, {name: "thoughts delete", entity: "thoughts", operation: "delete"}, @@ -119,6 +120,13 @@ func TestResolveSpecGuardBlocksUnsupportedMutations(t *testing.T) { wantCode: http.StatusForbidden, wantMessageIn: `operation "delete" is not allowed for public.stored_files`, }, + { + name: "mutations blocked for learnings", + entity: "learnings", + operation: "delete", + wantCode: http.StatusForbidden, + wantMessageIn: `operation "delete" is not allowed for public.learnings`, + }, { name: "unknown operation is rejected", entity: "projects", @@ -152,3 +160,13 @@ func TestResolveSpecGuardBlocksUnsupportedMutations(t *testing.T) { }) } } + +func TestResolveSpecModelsIncludeLearnings(t *testing.T) { + models := resolveSpecModels() + for _, model := range models { + if model.schema == "public" && model.entity == "learnings" { + return + } + } + t.Fatal("resolveSpecModels() missing public.learnings") +} diff --git a/internal/app/status.go b/internal/app/status.go index 2021bb3..2f8c063 100644 --- a/internal/app/status.go +++ b/internal/app/status.go @@ -25,6 +25,7 @@ type statusAPIResponse struct { TotalKnown int `json:"total_known"` ConnectedWindow string `json:"connected_window"` Entries []auth.AccessSnapshot `json:"entries"` + Metrics auth.AccessMetrics `json:"metrics"` OAuthEnabled bool `json:"oauth_enabled"` } @@ -40,6 +41,7 @@ func statusSnapshot(info buildinfo.Info, tracker *auth.AccessTracker, oauthEnabl TotalKnown: len(entries), ConnectedWindow: "last 10 minutes", Entries: entries, + Metrics: tracker.Metrics(20), OAuthEnabled: oauthEnabled, } } diff --git a/internal/app/status_test.go b/internal/app/status_test.go index d3255e3..20808ae 100644 --- a/internal/app/status_test.go +++ b/internal/app/status_test.go @@ -49,6 +49,15 @@ func TestStatusSnapshotShowsTrackedAccess(t *testing.T) { if snapshot.Entries[0].KeyID != "client-a" || snapshot.Entries[0].LastPath != "/files" { t.Fatalf("entry = %+v, want keyID client-a and path /files", snapshot.Entries[0]) } + if snapshot.Metrics.TotalRequests != 1 { + t.Fatalf("Metrics.TotalRequests = %d, want 1", snapshot.Metrics.TotalRequests) + } + if snapshot.Metrics.UniqueIPs != 1 { + t.Fatalf("Metrics.UniqueIPs = %d, want 1", snapshot.Metrics.UniqueIPs) + } + if snapshot.Metrics.UniqueAgents != 1 { + t.Fatalf("Metrics.UniqueAgents = %d, want 1", snapshot.Metrics.UniqueAgents) + } } func TestStatusAPIHandlerReturnsJSON(t *testing.T) { diff --git a/internal/auth/access_tracker.go b/internal/auth/access_tracker.go index 94b2d23..85d6e2c 100644 --- a/internal/auth/access_tracker.go +++ b/internal/auth/access_tracker.go @@ -1,7 +1,9 @@ package auth import ( + "net" "sort" + "strings" "sync" "time" ) @@ -16,12 +18,19 @@ type AccessSnapshot struct { } type AccessTracker struct { - mu sync.RWMutex - entries map[string]AccessSnapshot + mu sync.RWMutex + entries map[string]AccessSnapshot + ipCounts map[string]int + agentCounts map[string]int + totalRequests int } func NewAccessTracker() *AccessTracker { - return &AccessTracker{entries: make(map[string]AccessSnapshot)} + return &AccessTracker{ + entries: make(map[string]AccessSnapshot), + ipCounts: make(map[string]int), + agentCounts: make(map[string]int), + } } func (t *AccessTracker) Record(keyID, path, remoteAddr, userAgent string, now time.Time) { @@ -32,14 +41,36 @@ func (t *AccessTracker) Record(keyID, path, remoteAddr, userAgent string, now ti t.mu.Lock() defer t.mu.Unlock() + normalizedRemoteAddr := normalizeRemoteAddr(remoteAddr) + entry := t.entries[keyID] entry.KeyID = keyID entry.LastPath = path - entry.RemoteAddr = remoteAddr + entry.RemoteAddr = normalizedRemoteAddr entry.UserAgent = userAgent entry.LastAccessedAt = now.UTC() entry.RequestCount++ t.entries[keyID] = entry + t.totalRequests++ + + if normalizedRemoteAddr != "" { + t.ipCounts[normalizedRemoteAddr]++ + } + if userAgent != "" { + t.agentCounts[userAgent]++ + } +} + +func normalizeRemoteAddr(value string) string { + trimmed := strings.TrimSpace(value) + if trimmed == "" { + return "" + } + host, _, err := net.SplitHostPort(trimmed) + if err == nil { + return host + } + return trimmed } func (t *AccessTracker) Snapshot() []AccessSnapshot { @@ -79,3 +110,55 @@ func (t *AccessTracker) ConnectedCount(now time.Time, window time.Duration) int } return count } + +type RequestAggregate struct { + Key string `json:"key"` + RequestCount int `json:"request_count"` +} + +type AccessMetrics struct { + TotalRequests int `json:"total_requests"` + UniquePrincipals int `json:"unique_principals"` + UniqueIPs int `json:"unique_ips"` + UniqueAgents int `json:"unique_agents"` + TopIPs []RequestAggregate `json:"top_ips"` + TopAgents []RequestAggregate `json:"top_agents"` +} + +func (t *AccessTracker) Metrics(topN int) AccessMetrics { + if t == nil { + return AccessMetrics{} + } + if topN <= 0 { + topN = 10 + } + + t.mu.RLock() + defer t.mu.RUnlock() + + return AccessMetrics{ + TotalRequests: t.totalRequests, + UniquePrincipals: len(t.entries), + UniqueIPs: len(t.ipCounts), + UniqueAgents: len(t.agentCounts), + TopIPs: topAggregates(t.ipCounts, topN), + TopAgents: topAggregates(t.agentCounts, topN), + } +} + +func topAggregates(items map[string]int, topN int) []RequestAggregate { + out := make([]RequestAggregate, 0, len(items)) + for key, count := range items { + out = append(out, RequestAggregate{Key: key, RequestCount: count}) + } + sort.Slice(out, func(i, j int) bool { + if out[i].RequestCount == out[j].RequestCount { + return out[i].Key < out[j].Key + } + return out[i].RequestCount > out[j].RequestCount + }) + if len(out) > topN { + out = out[:topN] + } + return out +} diff --git a/internal/auth/access_tracker_test.go b/internal/auth/access_tracker_test.go index f383356..a174c4a 100644 --- a/internal/auth/access_tracker_test.go +++ b/internal/auth/access_tracker_test.go @@ -30,6 +30,9 @@ func TestAccessTrackerRecordAndSnapshot(t *testing.T) { if snap[0].UserAgent != "agent-a2" { t.Fatalf("snapshot[0].UserAgent = %q, want agent-a2", snap[0].UserAgent) } + if snap[0].RemoteAddr != "10.0.0.1" { + t.Fatalf("snapshot[0].RemoteAddr = %q, want 10.0.0.1", snap[0].RemoteAddr) + } } func TestAccessTrackerConnectedCount(t *testing.T) { @@ -43,3 +46,42 @@ func TestAccessTrackerConnectedCount(t *testing.T) { t.Fatalf("ConnectedCount() = %d, want 1", got) } } + +func TestAccessTrackerMetrics(t *testing.T) { + tracker := NewAccessTracker() + now := time.Date(2026, 4, 4, 12, 0, 0, 0, time.UTC) + + tracker.Record("client-a", "/mcp", "10.0.0.1:1234", "agent-a", now) + tracker.Record("client-a", "/mcp", "10.0.0.1:1234", "agent-a", now.Add(1*time.Second)) + tracker.Record("client-b", "/files", "10.0.0.2:5678", "agent-b", now.Add(2*time.Second)) + tracker.Record("client-c", "/files", "10.0.0.2:5678", "agent-b", now.Add(3*time.Second)) + + metrics := tracker.Metrics(5) + if metrics.TotalRequests != 4 { + t.Fatalf("TotalRequests = %d, want 4", metrics.TotalRequests) + } + if metrics.UniquePrincipals != 3 { + t.Fatalf("UniquePrincipals = %d, want 3", metrics.UniquePrincipals) + } + if metrics.UniqueIPs != 2 { + t.Fatalf("UniqueIPs = %d, want 2", metrics.UniqueIPs) + } + if metrics.UniqueAgents != 2 { + t.Fatalf("UniqueAgents = %d, want 2", metrics.UniqueAgents) + } + if len(metrics.TopIPs) != 2 { + t.Fatalf("len(TopIPs) = %d, want 2", len(metrics.TopIPs)) + } + if metrics.TopIPs[0].RequestCount != 2 || metrics.TopIPs[1].RequestCount != 2 { + t.Fatalf("TopIPs counts = %+v, want both counts to be 2", metrics.TopIPs) + } + if metrics.TopIPs[0].Key != "10.0.0.1" && metrics.TopIPs[0].Key != "10.0.0.2" { + t.Fatalf("TopIPs[0].Key = %q, want normalized IP", metrics.TopIPs[0].Key) + } + if len(metrics.TopAgents) != 2 { + t.Fatalf("len(TopAgents) = %d, want 2", len(metrics.TopAgents)) + } + if metrics.TopAgents[0].RequestCount != 2 || metrics.TopAgents[1].RequestCount != 2 { + t.Fatalf("TopAgents counts = %+v, want both counts to be 2", metrics.TopAgents) + } +} diff --git a/internal/mcpserver/streamable_integration_test.go b/internal/mcpserver/streamable_integration_test.go index 89a82a7..fc3fdca 100644 --- a/internal/mcpserver/streamable_integration_test.go +++ b/internal/mcpserver/streamable_integration_test.go @@ -105,6 +105,86 @@ func TestStreamableHTTPReturnsStructuredToolErrors(t *testing.T) { t.Fatalf("build_date = %#v, want %q", got["build_date"], "2026-03-31T00:00:00Z") } }) + + t.Run("add_learning_requires_summary", func(t *testing.T) { + _, err := cs.CallTool(context.Background(), &mcp.CallToolParams{ + Name: "add_learning", + Arguments: map[string]any{}, + }) + if err == nil { + t.Fatal("CallTool(add_learning) error = nil, want error") + } + + rpcErr, data := requireWireError(t, err) + if rpcErr.Code != jsonrpc.CodeInvalidParams { + t.Fatalf("add_learning code = %d, want %d", rpcErr.Code, jsonrpc.CodeInvalidParams) + } + if data.Type != mcperrors.TypeInvalidArguments { + t.Fatalf("add_learning data.type = %q, want %q", data.Type, mcperrors.TypeInvalidArguments) + } + if data.Field != "summary" { + t.Fatalf("add_learning data.field = %q, want %q", data.Field, "summary") + } + }) + + t.Run("get_learning_requires_id", func(t *testing.T) { + _, err := cs.CallTool(context.Background(), &mcp.CallToolParams{ + Name: "get_learning", + Arguments: map[string]any{}, + }) + if err == nil { + t.Fatal("CallTool(get_learning) error = nil, want error") + } + + rpcErr, data := requireWireError(t, err) + if rpcErr.Code != jsonrpc.CodeInvalidParams { + t.Fatalf("get_learning code = %d, want %d", rpcErr.Code, jsonrpc.CodeInvalidParams) + } + if data.Type != mcperrors.TypeInvalidArguments { + t.Fatalf("get_learning data.type = %q, want %q", data.Type, mcperrors.TypeInvalidArguments) + } + if data.Field != "id" { + t.Fatalf("get_learning data.field = %q, want %q", data.Field, "id") + } + }) + + t.Run("add_learning_unconfigured_returns_structured_error", func(t *testing.T) { + _, err := cs.CallTool(context.Background(), &mcp.CallToolParams{ + Name: "add_learning", + Arguments: map[string]any{ + "summary": "Learning with configured check", + }, + }) + if err == nil { + t.Fatal("CallTool(add_learning) error = nil, want error") + } + + rpcErr, data := requireWireError(t, err) + if rpcErr.Code != jsonrpc.CodeInvalidParams { + t.Fatalf("add_learning code = %d, want %d", rpcErr.Code, jsonrpc.CodeInvalidParams) + } + if data.Type != mcperrors.TypeInvalidInput { + t.Fatalf("add_learning data.type = %q, want %q", data.Type, mcperrors.TypeInvalidInput) + } + }) + + t.Run("list_learnings_unconfigured_returns_structured_error", func(t *testing.T) { + _, err := cs.CallTool(context.Background(), &mcp.CallToolParams{ + Name: "list_learnings", + Arguments: map[string]any{}, + }) + if err == nil { + t.Fatal("CallTool(list_learnings) error = nil, want error") + } + + rpcErr, data := requireWireError(t, err) + if rpcErr.Code != jsonrpc.CodeInvalidParams { + t.Fatalf("list_learnings code = %d, want %d", rpcErr.Code, jsonrpc.CodeInvalidParams) + } + if data.Type != mcperrors.TypeInvalidInput { + t.Fatalf("list_learnings data.type = %q, want %q", data.Type, mcperrors.TypeInvalidInput) + } + }) } func streamableTestToolSet() ToolSet { diff --git a/internal/tools/learnings.go b/internal/tools/learnings.go index b43e3ac..d0f2c17 100644 --- a/internal/tools/learnings.go +++ b/internal/tools/learnings.go @@ -75,6 +75,9 @@ func (t *LearningsTool) Add(ctx context.Context, req *mcp.CallToolRequest, in Ad if summary == "" { return nil, AddLearningOutput{}, errRequiredField("summary") } + if err := t.ensureConfigured(); err != nil { + return nil, AddLearningOutput{}, err + } project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, false) if err != nil { @@ -113,6 +116,10 @@ func (t *LearningsTool) Add(ctx context.Context, req *mcp.CallToolRequest, in Ad } func (t *LearningsTool) Get(ctx context.Context, _ *mcp.CallToolRequest, in GetLearningInput) (*mcp.CallToolResult, GetLearningOutput, error) { + if err := t.ensureConfigured(); err != nil { + return nil, GetLearningOutput{}, err + } + learning, err := t.store.GetLearning(ctx, in.ID) if err != nil { return nil, GetLearningOutput{}, err @@ -121,6 +128,10 @@ func (t *LearningsTool) Get(ctx context.Context, _ *mcp.CallToolRequest, in GetL } func (t *LearningsTool) List(ctx context.Context, req *mcp.CallToolRequest, in ListLearningsInput) (*mcp.CallToolResult, ListLearningsOutput, error) { + if err := t.ensureConfigured(); err != nil { + return nil, ListLearningsOutput{}, err + } + project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, false) if err != nil { return nil, ListLearningsOutput{}, err @@ -146,6 +157,13 @@ func (t *LearningsTool) List(ctx context.Context, req *mcp.CallToolRequest, in L return nil, ListLearningsOutput{Learnings: items}, nil } +func (t *LearningsTool) ensureConfigured() error { + if t == nil || t.store == nil { + return errInvalidInput("learnings tool is not configured") + } + return nil +} + func defaultString(value string, fallback string) string { if value == "" { return fallback diff --git a/internal/tools/learnings_test.go b/internal/tools/learnings_test.go new file mode 100644 index 0000000..b271b59 --- /dev/null +++ b/internal/tools/learnings_test.go @@ -0,0 +1,70 @@ +package tools + +import ( + "context" + "testing" + + "git.warky.dev/wdevs/amcs/internal/mcperrors" + "github.com/google/uuid" +) + +func TestLearningsAddRequiresSummary(t *testing.T) { + tool := &LearningsTool{} + + _, _, err := tool.Add(context.Background(), nil, AddLearningInput{}) + if err == nil { + t.Fatal("Add() error = nil, want error") + } + + _, data := requireRPCError(t, err) + if data.Field != "summary" { + t.Fatalf("Add() error field = %q, want %q", data.Field, "summary") + } +} + +func TestLearningsMethodsRequireConfiguredStore(t *testing.T) { + tool := &LearningsTool{} + + t.Run("add", func(t *testing.T) { + _, _, err := tool.Add(context.Background(), nil, AddLearningInput{Summary: "Keep this"}) + if err == nil { + t.Fatal("Add() error = nil, want error") + } + _, data := requireRPCError(t, err) + if data.Type != mcperrors.TypeInvalidInput { + t.Fatalf("Add() data.type = %q, want %q", data.Type, mcperrors.TypeInvalidInput) + } + }) + + t.Run("get", func(t *testing.T) { + _, _, err := tool.Get(context.Background(), nil, GetLearningInput{ID: uuid.New()}) + if err == nil { + t.Fatal("Get() error = nil, want error") + } + _, data := requireRPCError(t, err) + if data.Type != mcperrors.TypeInvalidInput { + t.Fatalf("Get() data.type = %q, want %q", data.Type, mcperrors.TypeInvalidInput) + } + }) + + t.Run("list", func(t *testing.T) { + _, _, err := tool.List(context.Background(), nil, ListLearningsInput{}) + if err == nil { + t.Fatal("List() error = nil, want error") + } + _, data := requireRPCError(t, err) + if data.Type != mcperrors.TypeInvalidInput { + t.Fatalf("List() data.type = %q, want %q", data.Type, mcperrors.TypeInvalidInput) + } + }) +} + +func TestNormalizeStringSliceTrimsDedupesAndDropsEmpties(t *testing.T) { + got := normalizeStringSlice([]string{" alpha ", "beta", "", "beta", "alpha"}) + if len(got) != 2 { + t.Fatalf("normalizeStringSlice() len = %d, want 2", len(got)) + } + if got[0] != "alpha" || got[1] != "beta" { + t.Fatalf("normalizeStringSlice() = %#v, want [alpha beta]", got) + } +} diff --git a/llm/memory.md b/llm/memory.md index 7af75e3..8775b9c 100644 --- a/llm/memory.md +++ b/llm/memory.md @@ -53,6 +53,7 @@ Do not abandon the project scope or retry without a project. The project simply - Use project memory for code decisions, architecture, TODOs, debugging findings, and context specific to the current repo or workstream. - Before substantial work, always retrieve context with `get_project_context` or `recall_context` so prior decisions inform your approach. - Save durable project facts with `capture_thought` after completing meaningful work. +- Use structured learnings for curated, reusable lessons that should remain distinct from raw thought capture. - Use `save_file` or `upload_file` for project assets the memory should retain, such as screenshots, PDFs, audio notes, and other documents. - If the goal is to retain the artifact itself, store the file directly instead of first reading, transcribing, or summarizing its contents. - For binary files or files larger than 10 MB, call `upload_file` with `content_path` (absolute server-side path) first to get an `amcs://files/{id}` URI, then pass that URI to `save_file` as `content_uri` to link it to a thought. This avoids base64 encoding entirely. @@ -64,6 +65,14 @@ Do not abandon the project scope or retry without a project. The project simply - Stored files and attachment metadata must not be sent to the metadata extraction client. - Do not attach memory to the wrong project. +## Structured Learnings + +- Learnings are curated memory records for durable insights, decisions, and evidence-backed findings. +- Prefer `capture_thought` for fast/raw notes during work; prefer learnings when the information is stable enough to normalize and track. +- Create learnings with `add_learning` (required: `summary`; optional: `details`, `category`, `area`, `status`, `priority`, `confidence`, `action_required`, `tags`, and related links). +- Retrieve one learning with `get_learning` and browse/filter with `list_learnings` (project/category/area/status/priority/tag/query). +- Keep learnings concise, specific, and non-duplicative; use `tags` and status fields so future retrieval is reliable. + ## Global Notebook Rules - Use global memory only for information that is genuinely cross-project or not project-bound. @@ -100,4 +109,4 @@ Notes are returned by `describe_tools` in future sessions. Annotate whenever you ## Short Operational Form -At the start of every session, call `describe_tools` to read the full tool list and any accumulated usage notes. Use AMCS memory in project scope when the current work matches a known project; if no clear project matches, global notebook memory is allowed for non-project-specific information. At the start of every project session call `list_project_skills` and `list_project_guardrails` and apply what is returned; only create new skills or guardrails if none exist. If your MCP client does not preserve sessions across calls, pass `project` explicitly instead of relying on `set_active_project`. Store durable notes with `capture_thought`. For binary files or files larger than 10 MB, call `upload_file` with `content_path` to stage the file and get an `amcs://files/{id}` URI, then pass that URI to `save_file` as `content_uri` to link it to a thought. For small files, use `save_file` or `upload_file` with `content_base64` directly. Browse stored files with `list_files`, and load them with `load_file` only when their contents are needed. Stored files can also be read as raw binary via MCP resources at `amcs://files/{id}`. Never store project-specific memory globally when a matching project exists, and never store memory in the wrong project. If project matching is ambiguous, ask the user. If a tool returns `project_not_found`, call `create_project` with that name and retry — never drop the project scope. Whenever you discover a non-obvious tool behaviour, gotcha, or workflow pattern, record it with `annotate_tool` so future sessions benefit. +At the start of every session, call `describe_tools` to read the full tool list and any accumulated usage notes. Use AMCS memory in project scope when the current work matches a known project; if no clear project matches, global notebook memory is allowed for non-project-specific information. At the start of every project session call `list_project_skills` and `list_project_guardrails` and apply what is returned; only create new skills or guardrails if none exist. If your MCP client does not preserve sessions across calls, pass `project` explicitly instead of relying on `set_active_project`. Store raw/durable notes with `capture_thought`, and store curated durable lessons with `add_learning`. For binary files or files larger than 10 MB, call `upload_file` with `content_path` to stage the file and get an `amcs://files/{id}` URI, then pass that URI to `save_file` as `content_uri` to link it to a thought. For small files, use `save_file` or `upload_file` with `content_base64` directly. Browse stored files with `list_files`, and load them with `load_file` only when their contents are needed. Stored files can also be read as raw binary via MCP resources at `amcs://files/{id}`. Never store project-specific memory globally when a matching project exists, and never store memory in the wrong project. If project matching is ambiguous, ask the user. If a tool returns `project_not_found`, call `create_project` with that name and retry — never drop the project scope. Whenever you discover a non-obvious tool behaviour, gotcha, or workflow pattern, record it with `annotate_tool` so future sessions benefit. diff --git a/migrations/001_enable_vector.sql b/migrations/001_enable_vector.sql index c92e838..06fbee6 100644 --- a/migrations/001_enable_vector.sql +++ b/migrations/001_enable_vector.sql @@ -1,2 +1,3 @@ create extension if not exists vector; create extension if not exists pgcrypto; +CREATE EXTENSION IF NOT EXISTS pg_trgm; \ No newline at end of file diff --git a/migrations/020_generated_schema.sql b/migrations/020_generated_schema.sql index 981611d..4e7c9e5 100644 --- a/migrations/020_generated_schema.sql +++ b/migrations/020_generated_schema.sql @@ -17,6 +17,13 @@ CREATE SEQUENCE IF NOT EXISTS public.identity_projects_id START 1 CACHE 1; +CREATE SEQUENCE IF NOT EXISTS public.identity_thought_links_id + INCREMENT 1 + MINVALUE 1 + MAXVALUE 9223372036854775807 + START 1 + CACHE 1; + CREATE SEQUENCE IF NOT EXISTS public.identity_embeddings_id INCREMENT 1 MINVALUE 1 @@ -38,6 +45,20 @@ CREATE SEQUENCE IF NOT EXISTS public.identity_tool_annotations_id START 1 CACHE 1; +CREATE SEQUENCE IF NOT EXISTS public.identity_project_skills_id + INCREMENT 1 + MINVALUE 1 + MAXVALUE 9223372036854775807 + START 1 + CACHE 1; + +CREATE SEQUENCE IF NOT EXISTS public.identity_project_guardrails_id + INCREMENT 1 + MINVALUE 1 + MAXVALUE 9223372036854775807 + START 1 + CACHE 1; + -- Tables for schema: public CREATE TABLE IF NOT EXISTS public.family_members ( birth_date date, @@ -97,6 +118,7 @@ CREATE TABLE IF NOT EXISTS public.projects ( CREATE TABLE IF NOT EXISTS public.thought_links ( created_at timestamptz DEFAULT now(), from_id bigint NOT NULL, + id serial NOT NULL, relation text NOT NULL, to_id bigint NOT NULL ); @@ -322,6 +344,7 @@ CREATE TABLE IF NOT EXISTS public.agent_guardrails ( CREATE TABLE IF NOT EXISTS public.project_skills ( created_at timestamptz NOT NULL DEFAULT now(), + id serial NOT NULL, project_id uuid NOT NULL, skill_id uuid NOT NULL ); @@ -329,6 +352,7 @@ CREATE TABLE IF NOT EXISTS public.project_skills ( CREATE TABLE IF NOT EXISTS public.project_guardrails ( created_at timestamptz NOT NULL DEFAULT now(), guardrail_id uuid NOT NULL, + id serial NOT NULL, project_id uuid NOT NULL ); @@ -879,6 +903,19 @@ BEGIN END; $$; +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'thought_links' + AND column_name = 'id' + ) THEN + ALTER TABLE public.thought_links ADD COLUMN id serial NOT NULL; + END IF; +END; +$$; + DO $$ BEGIN IF NOT EXISTS ( @@ -3102,6 +3139,19 @@ BEGIN END; $$; +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'project_skills' + AND column_name = 'id' + ) THEN + ALTER TABLE public.project_skills ADD COLUMN id serial NOT NULL; + END IF; +END; +$$; + DO $$ BEGIN IF NOT EXISTS ( @@ -3154,6 +3204,19 @@ BEGIN END; $$; +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'project_guardrails' + AND column_name = 'id' + ) THEN + ALTER TABLE public.project_guardrails ADD COLUMN id serial NOT NULL; + END IF; +END; +$$; + DO $$ BEGIN IF NOT EXISTS ( @@ -3181,7 +3244,7 @@ BEGIN AND constraint_name IN ('family_members_pkey', 'public_family_members_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.family_members DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.family_members DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3209,7 +3272,7 @@ BEGIN AND constraint_name IN ('activities_pkey', 'public_activities_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.activities DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.activities DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3237,7 +3300,7 @@ BEGIN AND constraint_name IN ('important_dates_pkey', 'public_important_dates_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.important_dates DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.important_dates DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3265,7 +3328,7 @@ BEGIN AND constraint_name IN ('thoughts_pkey', 'public_thoughts_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.thoughts DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.thoughts DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3293,7 +3356,7 @@ BEGIN AND constraint_name IN ('projects_pkey', 'public_projects_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.projects DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.projects DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3308,6 +3371,34 @@ BEGIN END; $$; +DO $$ +DECLARE + auto_pk_name text; +BEGIN + -- Drop auto-generated primary key if it exists + SELECT constraint_name INTO auto_pk_name + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'thought_links' + AND constraint_type = 'PRIMARY KEY' + AND constraint_name IN ('thought_links_pkey', 'public_thought_links_pkey'); + + IF auto_pk_name IS NOT NULL THEN + EXECUTE 'ALTER TABLE public.thought_links DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; + END IF; + + -- Add named primary key if it doesn't exist + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'thought_links' + AND constraint_name = 'pk_public_thought_links' + ) THEN + ALTER TABLE public.thought_links ADD CONSTRAINT pk_public_thought_links PRIMARY KEY (id); + END IF; +END; +$$; + DO $$ DECLARE auto_pk_name text; @@ -3321,7 +3412,7 @@ BEGIN AND constraint_name IN ('embeddings_pkey', 'public_embeddings_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.embeddings DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.embeddings DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3349,7 +3440,7 @@ BEGIN AND constraint_name IN ('professional_contacts_pkey', 'public_professional_contacts_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.professional_contacts DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.professional_contacts DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3377,7 +3468,7 @@ BEGIN AND constraint_name IN ('contact_interactions_pkey', 'public_contact_interactions_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.contact_interactions DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.contact_interactions DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3405,7 +3496,7 @@ BEGIN AND constraint_name IN ('opportunities_pkey', 'public_opportunities_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.opportunities DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.opportunities DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3433,7 +3524,7 @@ BEGIN AND constraint_name IN ('stored_files_pkey', 'public_stored_files_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.stored_files DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.stored_files DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3461,7 +3552,7 @@ BEGIN AND constraint_name IN ('household_items_pkey', 'public_household_items_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.household_items DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.household_items DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3489,7 +3580,7 @@ BEGIN AND constraint_name IN ('household_vendors_pkey', 'public_household_vendors_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.household_vendors DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.household_vendors DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3517,7 +3608,7 @@ BEGIN AND constraint_name IN ('maintenance_tasks_pkey', 'public_maintenance_tasks_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.maintenance_tasks DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.maintenance_tasks DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3545,7 +3636,7 @@ BEGIN AND constraint_name IN ('maintenance_logs_pkey', 'public_maintenance_logs_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.maintenance_logs DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.maintenance_logs DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3573,7 +3664,7 @@ BEGIN AND constraint_name IN ('recipes_pkey', 'public_recipes_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.recipes DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.recipes DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3601,7 +3692,7 @@ BEGIN AND constraint_name IN ('meal_plans_pkey', 'public_meal_plans_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.meal_plans DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.meal_plans DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3629,7 +3720,7 @@ BEGIN AND constraint_name IN ('shopping_lists_pkey', 'public_shopping_lists_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.shopping_lists DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.shopping_lists DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3657,7 +3748,7 @@ BEGIN AND constraint_name IN ('chat_histories_pkey', 'public_chat_histories_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.chat_histories DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.chat_histories DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3685,7 +3776,7 @@ BEGIN AND constraint_name IN ('tool_annotations_pkey', 'public_tool_annotations_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.tool_annotations DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.tool_annotations DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3713,7 +3804,7 @@ BEGIN AND constraint_name IN ('learnings_pkey', 'public_learnings_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.learnings DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.learnings DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3741,7 +3832,7 @@ BEGIN AND constraint_name IN ('agent_skills_pkey', 'public_agent_skills_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.agent_skills DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.agent_skills DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3769,7 +3860,7 @@ BEGIN AND constraint_name IN ('agent_guardrails_pkey', 'public_agent_guardrails_pkey'); IF auto_pk_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE public.agent_guardrails DROP CONSTRAINT ' || quote_ident(auto_pk_name); + EXECUTE 'ALTER TABLE public.agent_guardrails DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; END IF; -- Add named primary key if it doesn't exist @@ -3784,6 +3875,62 @@ BEGIN END; $$; +DO $$ +DECLARE + auto_pk_name text; +BEGIN + -- Drop auto-generated primary key if it exists + SELECT constraint_name INTO auto_pk_name + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'project_skills' + AND constraint_type = 'PRIMARY KEY' + AND constraint_name IN ('project_skills_pkey', 'public_project_skills_pkey'); + + IF auto_pk_name IS NOT NULL THEN + EXECUTE 'ALTER TABLE public.project_skills DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; + END IF; + + -- Add named primary key if it doesn't exist + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'project_skills' + AND constraint_name = 'pk_public_project_skills' + ) THEN + ALTER TABLE public.project_skills ADD CONSTRAINT pk_public_project_skills PRIMARY KEY (id); + END IF; +END; +$$; + +DO $$ +DECLARE + auto_pk_name text; +BEGIN + -- Drop auto-generated primary key if it exists + SELECT constraint_name INTO auto_pk_name + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'project_guardrails' + AND constraint_type = 'PRIMARY KEY' + AND constraint_name IN ('project_guardrails_pkey', 'public_project_guardrails_pkey'); + + IF auto_pk_name IS NOT NULL THEN + EXECUTE 'ALTER TABLE public.project_guardrails DROP CONSTRAINT ' || quote_ident(auto_pk_name) || ' cascade'; + END IF; + + -- Add named primary key if it doesn't exist + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'project_guardrails' + AND constraint_name = 'pk_public_project_guardrails' + ) THEN + ALTER TABLE public.project_guardrails ADD CONSTRAINT pk_public_project_guardrails PRIMARY KEY (id); + END IF; +END; +$$; + -- Indexes for schema: public CREATE INDEX IF NOT EXISTS idx_activities_start_date_end_date ON public.activities USING btree (start_date, end_date); @@ -4327,6 +4474,25 @@ BEGIN END; $$; DO $$ +DECLARE + m_cnt bigint; +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_class c + INNER JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = 'identity_thought_links_id' + AND n.nspname = 'public' + AND c.relkind = 'S' + ) THEN + SELECT COALESCE(MAX(id), 0) + 1 + FROM public.thought_links + INTO m_cnt; + + PERFORM setval('public.identity_thought_links_id'::regclass, m_cnt); + END IF; +END; +$$; +DO $$ DECLARE m_cnt bigint; BEGIN @@ -4383,6 +4549,44 @@ BEGIN END IF; END; $$; +DO $$ +DECLARE + m_cnt bigint; +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_class c + INNER JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = 'identity_project_skills_id' + AND n.nspname = 'public' + AND c.relkind = 'S' + ) THEN + SELECT COALESCE(MAX(id), 0) + 1 + FROM public.project_skills + INTO m_cnt; + + PERFORM setval('public.identity_project_skills_id'::regclass, m_cnt); + END IF; +END; +$$; +DO $$ +DECLARE + m_cnt bigint; +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_class c + INNER JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = 'identity_project_guardrails_id' + AND n.nspname = 'public' + AND c.relkind = 'S' + ) THEN + SELECT COALESCE(MAX(id), 0) + 1 + FROM public.project_guardrails + INTO m_cnt; + + PERFORM setval('public.identity_project_guardrails_id'::regclass, m_cnt); + END IF; +END; +$$; -- Comments for schema: public diff --git a/migrations/100_rls_and_grants.sql b/migrations/100_rls_and_grants.sql index fa7d2f1..20dfbdb 100644 --- a/migrations/100_rls_and_grants.sql +++ b/migrations/100_rls_and_grants.sql @@ -39,4 +39,17 @@ GRANT ALL ON TABLE public.project_guardrails TO amcs; -- Chat Histories (018) GRANT ALL ON TABLE public.chat_histories TO amcs; -GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO amcs; \ No newline at end of file +GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO amcs; + +DO $$ +DECLARE + r RECORD; +BEGIN + FOR r IN + SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' -- Change 'public' to your schema name + LOOP + EXECUTE format('ALTER TABLE public.%I OWNER TO amcs', r.tablename); + END LOOP; +END $$; \ No newline at end of file diff --git a/migrations/002_create_thoughts.sql b/migrations/_old/002_create_thoughts.sql similarity index 100% rename from migrations/002_create_thoughts.sql rename to migrations/_old/002_create_thoughts.sql diff --git a/migrations/003_add_projects.sql b/migrations/_old/003_add_projects.sql similarity index 100% rename from migrations/003_add_projects.sql rename to migrations/_old/003_add_projects.sql diff --git a/migrations/004_create_thought_links.sql b/migrations/_old/004_create_thought_links.sql similarity index 100% rename from migrations/004_create_thought_links.sql rename to migrations/_old/004_create_thought_links.sql diff --git a/migrations/005_create_match_thoughts.sql b/migrations/_old/005_create_match_thoughts.sql similarity index 100% rename from migrations/005_create_match_thoughts.sql rename to migrations/_old/005_create_match_thoughts.sql diff --git a/migrations/007_embeddings_table.sql b/migrations/_old/007_embeddings_table.sql similarity index 100% rename from migrations/007_embeddings_table.sql rename to migrations/_old/007_embeddings_table.sql diff --git a/migrations/008_update_match_thoughts.sql b/migrations/_old/008_update_match_thoughts.sql similarity index 100% rename from migrations/008_update_match_thoughts.sql rename to migrations/_old/008_update_match_thoughts.sql diff --git a/migrations/010_fulltext_index.sql b/migrations/_old/010_fulltext_index.sql similarity index 100% rename from migrations/010_fulltext_index.sql rename to migrations/_old/010_fulltext_index.sql diff --git a/migrations/011_household_knowledge.sql b/migrations/_old/011_household_knowledge.sql similarity index 100% rename from migrations/011_household_knowledge.sql rename to migrations/_old/011_household_knowledge.sql diff --git a/migrations/012_home_maintenance.sql b/migrations/_old/012_home_maintenance.sql similarity index 100% rename from migrations/012_home_maintenance.sql rename to migrations/_old/012_home_maintenance.sql diff --git a/migrations/013_family_calendar.sql b/migrations/_old/013_family_calendar.sql similarity index 100% rename from migrations/013_family_calendar.sql rename to migrations/_old/013_family_calendar.sql diff --git a/migrations/014_meal_planning.sql b/migrations/_old/014_meal_planning.sql similarity index 100% rename from migrations/014_meal_planning.sql rename to migrations/_old/014_meal_planning.sql diff --git a/migrations/015_professional_crm.sql b/migrations/_old/015_professional_crm.sql similarity index 100% rename from migrations/015_professional_crm.sql rename to migrations/_old/015_professional_crm.sql diff --git a/migrations/016_create_stored_files.sql b/migrations/_old/016_create_stored_files.sql similarity index 100% rename from migrations/016_create_stored_files.sql rename to migrations/_old/016_create_stored_files.sql diff --git a/migrations/017_agent_skills_guardrails.sql b/migrations/_old/017_agent_skills_guardrails.sql similarity index 100% rename from migrations/017_agent_skills_guardrails.sql rename to migrations/_old/017_agent_skills_guardrails.sql diff --git a/migrations/018_chat_histories.sql b/migrations/_old/018_chat_histories.sql similarity index 100% rename from migrations/018_chat_histories.sql rename to migrations/_old/018_chat_histories.sql diff --git a/migrations/019_tool_annotations.sql b/migrations/_old/019_tool_annotations.sql similarity index 100% rename from migrations/019_tool_annotations.sql rename to migrations/_old/019_tool_annotations.sql diff --git a/ui/package.json b/ui/package.json index 533c3dd..91cb340 100644 --- a/ui/package.json +++ b/ui/package.json @@ -11,22 +11,22 @@ "preview": "vite preview" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.3", - "@tailwindcss/vite": "^4.1.4", - "@types/node": "^24.5.2", - "svelte": "^5.28.2", - "svelte-check": "^4.1.6", - "tailwindcss": "^4.1.4", - "typescript": "^5.8.3", - "vite": "^6.3.2" + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tailwindcss/vite": "^4.2.4", + "@types/node": "^25.6.0", + "svelte": "^5.55.5", + "svelte-check": "^4.4.6", + "tailwindcss": "^4.2.4", + "typescript": "^6.0.3", + "vite": "^8.0.10" }, "dependencies": { - "@sentry/svelte": "^10.49.0", + "@sentry/svelte": "^10.50.0", "@skeletonlabs/skeleton": "^4.15.2", "@skeletonlabs/skeleton-svelte": "^4.15.2", "@tanstack/svelte-virtual": "^3.13.24", "@warkypublic/artemis-kit": "file:../../artemis-kit", "@warkypublic/resolvespec-js": "^1.0.1", - "@warkypublic/svelix": "^0.1.31" + "@warkypublic/svelix": "^0.1.39" } -} +} \ No newline at end of file diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index b89adb6..da556d3 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -9,17 +9,17 @@ importers: .: dependencies: '@sentry/svelte': - specifier: ^10.49.0 - version: 10.49.0(svelte@5.55.1) + specifier: ^10.50.0 + version: 10.50.0(svelte@5.55.5) '@skeletonlabs/skeleton': specifier: ^4.15.2 - version: 4.15.2(tailwindcss@4.2.2) + version: 4.15.2(tailwindcss@4.2.4) '@skeletonlabs/skeleton-svelte': specifier: ^4.15.2 - version: 4.15.2(svelte@5.55.1) + version: 4.15.2(svelte@5.55.5) '@tanstack/svelte-virtual': specifier: ^3.13.24 - version: 3.13.24(svelte@5.55.1) + version: 3.13.24(svelte@5.55.5) '@warkypublic/artemis-kit': specifier: file:../../artemis-kit version: file:../../artemis-kit @@ -27,33 +27,33 @@ importers: specifier: ^1.0.1 version: 1.0.1 '@warkypublic/svelix': - specifier: ^0.1.31 - version: 0.1.37(highlight.js@11.8.0)(svelte@5.55.1)(unified@11.0.5) + specifier: ^0.1.39 + version: 0.1.39(highlight.js@11.8.0)(svelte@5.55.5)(unified@11.0.5) devDependencies: '@sveltejs/vite-plugin-svelte': - specifier: ^5.0.3 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)) + specifier: ^7.0.0 + version: 7.0.0(svelte@5.55.5)(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1)) '@tailwindcss/vite': - specifier: ^4.1.4 - version: 4.2.2(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)) + specifier: ^4.2.4 + version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1)) '@types/node': - specifier: ^24.5.2 - version: 24.12.2 + specifier: ^25.6.0 + version: 25.6.0 svelte: - specifier: ^5.28.2 - version: 5.55.1 + specifier: ^5.55.5 + version: 5.55.5 svelte-check: - specifier: ^4.1.6 - version: 4.4.6(picomatch@4.0.4)(svelte@5.55.1)(typescript@5.9.3) + specifier: ^4.4.6 + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.5)(typescript@6.0.3) tailwindcss: - specifier: ^4.1.4 - version: 4.2.2 + specifier: ^4.2.4 + version: 4.2.4 typescript: - specifier: ^5.8.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 vite: - specifier: ^6.3.2 - version: 6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0) + specifier: ^8.0.10 + version: 8.0.10(@types/node@25.6.0)(jiti@2.6.1) packages: @@ -144,161 +144,14 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} '@exodus/bytes@1.15.0': resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} @@ -349,176 +202,145 @@ packages: resolution: {integrity: sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==} engines: {node: '>=12'} + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} - '@rollup/rollup-android-arm-eabi@4.60.1': - resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.60.1': - resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.1': - resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.1': - resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.1': - resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.1': - resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': - resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.60.1': - resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.60.1': - resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.60.1': - resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.60.1': - resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.60.1': - resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.1': - resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.60.1': - resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.1': - resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.60.1': - resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.60.1': - resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.60.1': - resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.60.1': - resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.60.1': - resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.1': - resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.1': - resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.1': - resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.60.1': - resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.1': - resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} - cpu: [x64] - os: [win32] + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} - '@sentry-internal/browser-utils@10.49.0': - resolution: {integrity: sha512-n0QRx0Ysx6mPfIydTkz7VP0FmwM+/EqMZiRqdsU3aTYsngE9GmEDV0OL1bAy6a8N/C1xf9vntkuAtj6N/8Z51w==} + '@sentry-internal/browser-utils@10.50.0': + resolution: {integrity: sha512-42bxyRTxnCmYlWnvz4CxikuQNanw8UNma2WJrtxJ0f1MAJV2GhQGSHDLnA+lvFlmiz6qct3pfen/NXGyOTegTA==} engines: {node: '>=18'} - '@sentry-internal/feedback@10.49.0': - resolution: {integrity: sha512-JNsUBGv0faCFE7MeZUH99Y9lU9qq3LBALbLxpE1x7ngNrQnVYRlcFgdqaD/btNBKr8awjYL8gmcSkHBWskGqLQ==} + '@sentry-internal/feedback@10.50.0': + resolution: {integrity: sha512-0k9XZF0wn86f77mIO2U3gNNyDZooy139CnEanRzHinrN106vVzvBZ6TUEQoHtoO1fqQxr+nWWVrqV/PXUqk47w==} engines: {node: '>=18'} - '@sentry-internal/replay-canvas@10.49.0': - resolution: {integrity: sha512-7D/NrgH1Qwx5trDYaaTSSJmCb1yVQQLqFG4G/S9x2ltzl9876lSGJL8UeW8ReNQgF3CDAcwbmm/9aXaVSBUNZA==} + '@sentry-internal/replay-canvas@10.50.0': + resolution: {integrity: sha512-jx6RKBmcJSWdI92qDGS/sBv1w+7Cww879Z/moX7bw7ipHa/Ts3iDcB3rgZwvhmi17U+mvYsbJeL2DXkPo3TjPw==} engines: {node: '>=18'} - '@sentry-internal/replay@10.49.0': - resolution: {integrity: sha512-IEy4lwHVMiRE3JAcn+kFKjsTgalDOCSTf20SoFd+nkt6rN/k1RDyr4xpdfF//Kj3UdeTmbuibYjK5H/FLhhnGg==} + '@sentry-internal/replay@10.50.0': + resolution: {integrity: sha512-51FYNfnvVLAWw1rrEWPFfwHuMRb9mkVCFGA4J9/un7SpeGBsQDziGB0Di4fsCxI7+EdSBpfLHPF0csKtCCw0oQ==} engines: {node: '>=18'} - '@sentry/browser@10.49.0': - resolution: {integrity: sha512-bGCHc+wK2Dx67YoSbmtlt04alqWfQ+dasD/GVipVOq50gvw/BBIDHTEWRJEjACl+LrvszeY54V+24p8z4IgysA==} + '@sentry/browser@10.50.0': + resolution: {integrity: sha512-1f6rAvET6myiTaSeYqvaaBwvq1LfxqWjAPIoAW/NVC9bPMkeEcuvgDajHrnZMrBeWoJ81NMyoLkyX+iOc7MoFA==} engines: {node: '>=18'} - '@sentry/core@10.49.0': - resolution: {integrity: sha512-UaFeum3LUM1mB0d67jvKnqId1yWQjyqmaDV6kWngG03x+jqXb08tJdGpSoxjXZe13jFBbiBL/wKDDYIK7rCK4g==} + '@sentry/core@10.50.0': + resolution: {integrity: sha512-J4A+vzUO3adl0TkFCjaN1+4miamrjHiEIYuLHiuu1lmAjq5WIVw32ObvAh4yMwNtxyaEMosTrrh5M6f12XSJFg==} engines: {node: '>=18'} - '@sentry/svelte@10.49.0': - resolution: {integrity: sha512-onQ+dpvjn1impT72Lsp0I0i2C5796pxOY+MyH3BYd139os+8uskatzYZddBTe+r36t8+M0gWk5PQqftcvAaFwQ==} + '@sentry/svelte@10.50.0': + resolution: {integrity: sha512-pkd9HNpZN+8x9i8n24fpV+Q3/sKDkBKyJ29iNzbhGnZ3CeRPwKxwQOoiBBPQkllYWzr/a7cYFtBKNLdpmTFCOg==} engines: {node: '>=18'} peerDependencies: svelte: 3.x || 4.x || 5.x @@ -605,87 +427,79 @@ packages: peerDependencies: acorn: ^8.9.0 - '@sveltejs/vite-plugin-svelte-inspector@4.0.1': - resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22} + '@sveltejs/vite-plugin-svelte@7.0.0': + resolution: {integrity: sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==} + engines: {node: ^20.19 || ^22.12 || >=24} peerDependencies: - '@sveltejs/vite-plugin-svelte': ^5.0.0 - svelte: ^5.0.0 - vite: ^6.0.0 - - '@sveltejs/vite-plugin-svelte@5.1.1': - resolution: {integrity: sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22} - peerDependencies: - svelte: ^5.0.0 - vite: ^6.0.0 + svelte: ^5.46.4 + vite: ^8.0.0-beta.7 || ^8.0.0 '@swc/helpers@0.5.21': resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} - '@tailwindcss/node@4.2.2': - resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} + '@tailwindcss/node@4.2.4': + resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} - '@tailwindcss/oxide-android-arm64@4.2.2': - resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} + '@tailwindcss/oxide-android-arm64@4.2.4': + resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.2.2': - resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} + '@tailwindcss/oxide-darwin-arm64@4.2.4': + resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.2.2': - resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} + '@tailwindcss/oxide-darwin-x64@4.2.4': + resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.2.2': - resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} + '@tailwindcss/oxide-freebsd-x64@4.2.4': + resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -696,24 +510,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.2.2': - resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} + '@tailwindcss/oxide@4.2.4': + resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} engines: {node: '>= 20'} - '@tailwindcss/vite@4.2.2': - resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} + '@tailwindcss/vite@4.2.4': + resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 @@ -884,6 +698,9 @@ packages: '@tiptap/starter-kit@2.27.2': resolution: {integrity: sha512-bb0gJvPoDuyRUQ/iuN52j1//EtWWttw+RXAv1uJxfR0uKf8X7uAqzaOOgwjknoCIDC97+1YHwpGdnRjpDkOBxw==} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -914,8 +731,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@24.12.2': - resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} + '@types/node@25.6.0': + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -945,8 +762,8 @@ packages: resolution: {integrity: sha512-uXP1HouxpOKXfwE6qpy0gCcrMPIgjDT53aVGkfork4QejRSunbKWSKKawW2nIm7RnyFhSjPILMXcnT5xUiXOew==} engines: {node: '>=18'} - '@warkypublic/svelix@0.1.37': - resolution: {integrity: sha512-OZ9of7ctyR1qEVbsfwS7NTAAJKbgWyJPKvYY5QAHdvTJjb6oT7gSRFsRPZp0ji2ht1bCRyYTuVA2EIXvyM/4tw==} + '@warkypublic/svelix@0.1.39': + resolution: {integrity: sha512-CeKOyabAXTt5MXzRkQyG0G7+1wwgYD/e4+fX9gRsQWxlVVrHv8qdbK1HxHHKMmu4ZW9csf0dXWcEIt/TSM9qAg==} peerDependencies: svelte: ^5.0.0 @@ -1237,11 +1054,6 @@ packages: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1374,10 +1186,6 @@ packages: resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} hasBin: true - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -1637,6 +1445,9 @@ packages: resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} engines: {node: '>=18'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + oniguruma-parser@0.12.2: resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} @@ -1659,8 +1470,8 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} - postcss@8.5.8: - resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + postcss@8.5.12: + resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} engines: {node: ^10 || ^12 || >=14} property-information@7.1.0: @@ -1786,9 +1597,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - rollup@4.60.1: - resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true rope-sequence@1.3.4: @@ -1832,22 +1643,22 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - svelte@5.55.1: - resolution: {integrity: sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==} + svelte@5.55.5: + resolution: {integrity: sha512-2uCs/LZ9us+AktdzYJM8OcxQ8qnPS1kpaO7syGT/MgO+6Qr1Ybl+TqPq+97u7PHqmmMlye5ZkoyXONy5mjjAbw==} engines: {node: '>=18'} symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tailwindcss@4.2.2: - resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} + tailwindcss@4.2.4: + resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} tapable@2.3.2: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} tippy.js@6.3.7: @@ -1877,16 +1688,16 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} undici@7.25.0: resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} @@ -1937,31 +1748,34 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite@6.4.1: - resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite@8.0.10: + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true jiti: optional: true less: optional: true - lightningcss: - optional: true sass: optional: true sass-embedded: @@ -2046,43 +1860,43 @@ snapshots: dependencies: css-tree: 3.2.1 - '@cartamd/plugin-anchor@2.2.0(carta-md@4.11.2(svelte@5.55.1))': + '@cartamd/plugin-anchor@2.2.0(carta-md@4.11.2(svelte@5.55.5))': dependencies: - carta-md: 4.11.2(svelte@5.55.1) + carta-md: 4.11.2(svelte@5.55.5) rehype-autolink-headings: 7.1.0 rehype-slug: 6.0.0 - '@cartamd/plugin-attachment@4.2.0(carta-md@4.11.2(svelte@5.55.1))': + '@cartamd/plugin-attachment@4.2.0(carta-md@4.11.2(svelte@5.55.5))': dependencies: - carta-md: 4.11.2(svelte@5.55.1) + carta-md: 4.11.2(svelte@5.55.5) - '@cartamd/plugin-code@4.2.0(carta-md@4.11.2(svelte@5.55.1))': + '@cartamd/plugin-code@4.2.0(carta-md@4.11.2(svelte@5.55.5))': dependencies: '@shikijs/rehype': 3.23.0 - carta-md: 4.11.2(svelte@5.55.1) + carta-md: 4.11.2(svelte@5.55.5) unified: 11.0.5 - '@cartamd/plugin-component@1.1.1(carta-md@4.11.2(svelte@5.55.1))(unified@11.0.5)': + '@cartamd/plugin-component@1.1.1(carta-md@4.11.2(svelte@5.55.5))(unified@11.0.5)': dependencies: - carta-md: 4.11.2(svelte@5.55.1) + carta-md: 4.11.2(svelte@5.55.5) esm-env: 1.2.2 rehype-parse: 9.0.1 unified: 11.0.5 unist-util-visit: 5.1.0 - '@cartamd/plugin-emoji@4.3.0(carta-md@4.11.2(svelte@5.55.1))': + '@cartamd/plugin-emoji@4.3.0(carta-md@4.11.2(svelte@5.55.5))': dependencies: bezier-easing: 2.1.0 - carta-md: 4.11.2(svelte@5.55.1) + carta-md: 4.11.2(svelte@5.55.5) node-emoji: 2.2.0 remark-emoji: 5.0.2 - '@cartamd/plugin-math@4.3.1(carta-md@4.11.2(svelte@5.55.1))(svelte@5.55.1)': + '@cartamd/plugin-math@4.3.1(carta-md@4.11.2(svelte@5.55.5))(svelte@5.55.5)': dependencies: - carta-md: 4.11.2(svelte@5.55.1) + carta-md: 4.11.2(svelte@5.55.5) rehype-katex: 7.0.1 remark-math: 6.0.0 - svelte: 5.55.1 + svelte: 5.55.5 transitivePeerDependencies: - supports-color @@ -2110,82 +1924,20 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} - '@esbuild/aix-ppc64@0.25.12': + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 optional: true - '@esbuild/android-arm64@0.25.12': + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 optional: true - '@esbuild/android-arm@0.25.12': - optional: true - - '@esbuild/android-x64@0.25.12': - optional: true - - '@esbuild/darwin-arm64@0.25.12': - optional: true - - '@esbuild/darwin-x64@0.25.12': - optional: true - - '@esbuild/freebsd-arm64@0.25.12': - optional: true - - '@esbuild/freebsd-x64@0.25.12': - optional: true - - '@esbuild/linux-arm64@0.25.12': - optional: true - - '@esbuild/linux-arm@0.25.12': - optional: true - - '@esbuild/linux-ia32@0.25.12': - optional: true - - '@esbuild/linux-loong64@0.25.12': - optional: true - - '@esbuild/linux-mips64el@0.25.12': - optional: true - - '@esbuild/linux-ppc64@0.25.12': - optional: true - - '@esbuild/linux-riscv64@0.25.12': - optional: true - - '@esbuild/linux-s390x@0.25.12': - optional: true - - '@esbuild/linux-x64@0.25.12': - optional: true - - '@esbuild/netbsd-arm64@0.25.12': - optional: true - - '@esbuild/netbsd-x64@0.25.12': - optional: true - - '@esbuild/openbsd-arm64@0.25.12': - optional: true - - '@esbuild/openbsd-x64@0.25.12': - optional: true - - '@esbuild/openharmony-arm64@0.25.12': - optional: true - - '@esbuild/sunos-x64@0.25.12': - optional: true - - '@esbuild/win32-arm64@0.25.12': - optional: true - - '@esbuild/win32-ia32@0.25.12': - optional: true - - '@esbuild/win32-x64@0.25.12': + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 optional: true '@exodus/bytes@1.15.0': {} @@ -2201,7 +1953,7 @@ snapshots: '@floating-ui/utils@0.2.11': {} - '@friendofsvelte/tipex@0.1.1(highlight.js@11.8.0)(svelte@5.55.1)': + '@friendofsvelte/tipex@0.1.1(highlight.js@11.8.0)(svelte@5.55.5)': dependencies: '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) '@tiptap/extension-code-block': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) @@ -2217,7 +1969,7 @@ snapshots: '@tiptap/starter-kit': 2.27.2 iconify-icon: 1.0.8 lowlight: 2.9.0 - svelte: 5.55.1 + svelte: 5.55.5 transitivePeerDependencies: - highlight.js @@ -2250,119 +2002,104 @@ snapshots: dependencies: jsbi: 4.3.2 + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@oxc-project/types@0.127.0': {} + '@popperjs/core@2.11.8': {} '@remirror/core-constants@3.0.0': {} - '@rollup/rollup-android-arm-eabi@4.60.1': + '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true - '@rollup/rollup-android-arm64@4.60.1': + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': optional: true - '@rollup/rollup-darwin-arm64@4.60.1': + '@rolldown/binding-darwin-x64@1.0.0-rc.17': optional: true - '@rollup/rollup-darwin-x64@4.60.1': + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': optional: true - '@rollup/rollup-freebsd-arm64@4.60.1': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': optional: true - '@rollup/rollup-freebsd-x64@4.60.1': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.1': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.1': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.1': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.1': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.1': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-x64-musl@4.60.1': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.1': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.1': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.1': - optional: true - - '@sentry-internal/browser-utils@10.49.0': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': dependencies: - '@sentry/core': 10.49.0 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true - '@sentry-internal/feedback@10.49.0': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.17': {} + + '@sentry-internal/browser-utils@10.50.0': dependencies: - '@sentry/core': 10.49.0 + '@sentry/core': 10.50.0 - '@sentry-internal/replay-canvas@10.49.0': + '@sentry-internal/feedback@10.50.0': dependencies: - '@sentry-internal/replay': 10.49.0 - '@sentry/core': 10.49.0 + '@sentry/core': 10.50.0 - '@sentry-internal/replay@10.49.0': + '@sentry-internal/replay-canvas@10.50.0': dependencies: - '@sentry-internal/browser-utils': 10.49.0 - '@sentry/core': 10.49.0 + '@sentry-internal/replay': 10.50.0 + '@sentry/core': 10.50.0 - '@sentry/browser@10.49.0': + '@sentry-internal/replay@10.50.0': dependencies: - '@sentry-internal/browser-utils': 10.49.0 - '@sentry-internal/feedback': 10.49.0 - '@sentry-internal/replay': 10.49.0 - '@sentry-internal/replay-canvas': 10.49.0 - '@sentry/core': 10.49.0 + '@sentry-internal/browser-utils': 10.50.0 + '@sentry/core': 10.50.0 - '@sentry/core@10.49.0': {} - - '@sentry/svelte@10.49.0(svelte@5.55.1)': + '@sentry/browser@10.50.0': dependencies: - '@sentry/browser': 10.49.0 - '@sentry/core': 10.49.0 + '@sentry-internal/browser-utils': 10.50.0 + '@sentry-internal/feedback': 10.50.0 + '@sentry-internal/replay': 10.50.0 + '@sentry-internal/replay-canvas': 10.50.0 + '@sentry/core': 10.50.0 + + '@sentry/core@10.50.0': {} + + '@sentry/svelte@10.50.0(svelte@5.55.5)': + dependencies: + '@sentry/browser': 10.50.0 + '@sentry/core': 10.50.0 magic-string: 0.30.21 - svelte: 5.55.1 + svelte: 5.55.5 '@shikijs/core@3.23.0': dependencies: @@ -2410,7 +2147,7 @@ snapshots: '@skeletonlabs/skeleton-common@4.15.2': {} - '@skeletonlabs/skeleton-svelte@4.15.2(svelte@5.55.1)': + '@skeletonlabs/skeleton-svelte@4.15.2(svelte@5.55.5)': dependencies: '@internationalized/date': 3.12.0 '@skeletonlabs/skeleton-common': 4.15.2 @@ -2433,7 +2170,7 @@ snapshots: '@zag-js/rating-group': 1.39.1 '@zag-js/slider': 1.39.1 '@zag-js/steps': 1.39.1 - '@zag-js/svelte': 1.39.1(svelte@5.55.1) + '@zag-js/svelte': 1.39.1(svelte@5.55.5) '@zag-js/switch': 1.39.1 '@zag-js/tabs': 1.39.1 '@zag-js/tags-input': 1.39.1 @@ -2441,11 +2178,11 @@ snapshots: '@zag-js/toggle-group': 1.39.1 '@zag-js/tooltip': 1.39.1 '@zag-js/tree-view': 1.39.1 - svelte: 5.55.1 + svelte: 5.55.5 - '@skeletonlabs/skeleton@4.15.2(tailwindcss@4.2.2)': + '@skeletonlabs/skeleton@4.15.2(tailwindcss@4.2.4)': dependencies: - tailwindcss: 4.2.2 + tailwindcss: 4.2.4 '@svar-ui/core-locales@2.5.1': {} @@ -2504,33 +2241,20 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)))(svelte@5.55.1)(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0))': + '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5)(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)) - debug: 4.4.3 - svelte: 5.55.1 - vite: 6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0) - transitivePeerDependencies: - - supports-color - - '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0))': - dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)))(svelte@5.55.1)(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)) - debug: 4.4.3 deepmerge: 4.3.1 - kleur: 4.1.5 magic-string: 0.30.21 - svelte: 5.55.1 - vite: 6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0) - vitefu: 1.1.3(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)) - transitivePeerDependencies: - - supports-color + obug: 2.1.1 + svelte: 5.55.5 + vite: 8.0.10(@types/node@25.6.0)(jiti@2.6.1) + vitefu: 1.1.3(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1)) '@swc/helpers@0.5.21': dependencies: tslib: 2.8.1 - '@tailwindcss/node@4.2.2': + '@tailwindcss/node@4.2.4': dependencies: '@jridgewell/remapping': 2.3.5 enhanced-resolve: 5.20.1 @@ -2538,70 +2262,70 @@ snapshots: lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.2.2 + tailwindcss: 4.2.4 - '@tailwindcss/oxide-android-arm64@4.2.2': + '@tailwindcss/oxide-android-arm64@4.2.4': optional: true - '@tailwindcss/oxide-darwin-arm64@4.2.2': + '@tailwindcss/oxide-darwin-arm64@4.2.4': optional: true - '@tailwindcss/oxide-darwin-x64@4.2.2': + '@tailwindcss/oxide-darwin-x64@4.2.4': optional: true - '@tailwindcss/oxide-freebsd-x64@4.2.2': + '@tailwindcss/oxide-freebsd-x64@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.2.2': + '@tailwindcss/oxide-linux-x64-musl@4.2.4': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.2.2': + '@tailwindcss/oxide-wasm32-wasi@4.2.4': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': optional: true - '@tailwindcss/oxide@4.2.2': + '@tailwindcss/oxide@4.2.4': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-x64': 4.2.2 - '@tailwindcss/oxide-freebsd-x64': 4.2.2 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-x64-musl': 4.2.2 - '@tailwindcss/oxide-wasm32-wasi': 4.2.2 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 - '@tailwindcss/vite@4.2.2(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0))': + '@tailwindcss/vite@4.2.4(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1))': dependencies: - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - tailwindcss: 4.2.2 - vite: 6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0) + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: 8.0.10(@types/node@25.6.0)(jiti@2.6.1) - '@tanstack/svelte-virtual@3.13.24(svelte@5.55.1)': + '@tanstack/svelte-virtual@3.13.24(svelte@5.55.5)': dependencies: '@tanstack/virtual-core': 3.14.0 - svelte: 5.55.1 + svelte: 5.55.5 '@tanstack/virtual-core@3.14.0': {} @@ -2777,6 +2501,11 @@ snapshots: '@tiptap/extension-text-style': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) '@tiptap/pm': 2.27.2 + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -2808,9 +2537,9 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@24.12.2': + '@types/node@25.6.0': dependencies: - undici-types: 7.16.0 + undici-types: 7.19.2 '@types/trusted-types@2.0.7': {} @@ -2836,25 +2565,25 @@ snapshots: dependencies: uuid: 13.0.0 - '@warkypublic/svelix@0.1.37(highlight.js@11.8.0)(svelte@5.55.1)(unified@11.0.5)': + '@warkypublic/svelix@0.1.39(highlight.js@11.8.0)(svelte@5.55.5)(unified@11.0.5)': dependencies: - '@cartamd/plugin-anchor': 2.2.0(carta-md@4.11.2(svelte@5.55.1)) - '@cartamd/plugin-attachment': 4.2.0(carta-md@4.11.2(svelte@5.55.1)) - '@cartamd/plugin-code': 4.2.0(carta-md@4.11.2(svelte@5.55.1)) - '@cartamd/plugin-component': 1.1.1(carta-md@4.11.2(svelte@5.55.1))(unified@11.0.5) - '@cartamd/plugin-emoji': 4.3.0(carta-md@4.11.2(svelte@5.55.1)) - '@cartamd/plugin-math': 4.3.1(carta-md@4.11.2(svelte@5.55.1))(svelte@5.55.1) - '@friendofsvelte/tipex': 0.1.1(highlight.js@11.8.0)(svelte@5.55.1) + '@cartamd/plugin-anchor': 2.2.0(carta-md@4.11.2(svelte@5.55.5)) + '@cartamd/plugin-attachment': 4.2.0(carta-md@4.11.2(svelte@5.55.5)) + '@cartamd/plugin-code': 4.2.0(carta-md@4.11.2(svelte@5.55.5)) + '@cartamd/plugin-component': 1.1.1(carta-md@4.11.2(svelte@5.55.5))(unified@11.0.5) + '@cartamd/plugin-emoji': 4.3.0(carta-md@4.11.2(svelte@5.55.5)) + '@cartamd/plugin-math': 4.3.1(carta-md@4.11.2(svelte@5.55.5))(svelte@5.55.5) + '@friendofsvelte/tipex': 0.1.1(highlight.js@11.8.0)(svelte@5.55.5) '@js-temporal/polyfill': 0.5.1 '@svar-ui/svelte-grid': 2.6.2 '@warkypublic/artemis-kit': 1.0.10 '@warkypublic/resolvespec-js': 1.0.1 - carta-md: 4.11.2(svelte@5.55.1) + carta-md: 4.11.2(svelte@5.55.5) github-markdown-css: 5.9.0 isomorphic-dompurify: 3.10.0 katex: 0.16.45 monaco-editor: 0.55.1 - svelte: 5.55.1 + svelte: 5.55.5 transitivePeerDependencies: - '@noble/hashes' - canvas @@ -3114,12 +2843,12 @@ snapshots: dependencies: proxy-compare: 3.0.1 - '@zag-js/svelte@1.39.1(svelte@5.55.1)': + '@zag-js/svelte@1.39.1(svelte@5.55.5)': dependencies: '@zag-js/core': 1.39.1 '@zag-js/types': 1.39.1 '@zag-js/utils': 1.39.1 - svelte: 5.55.1 + svelte: 5.55.5 '@zag-js/switch@1.39.1': dependencies: @@ -3207,7 +2936,7 @@ snapshots: dependencies: require-from-string: 2.0.2 - carta-md@4.11.2(svelte@5.55.1): + carta-md@4.11.2(svelte@5.55.5): dependencies: diff: 5.2.2 esm-env: 1.2.2 @@ -3216,7 +2945,7 @@ snapshots: remark-parse: 11.0.0 remark-rehype: 11.1.2 shiki: 3.23.0 - svelte: 5.55.1 + svelte: 5.55.5 unified: 11.0.5 transitivePeerDependencies: - supports-color @@ -3304,35 +3033,6 @@ snapshots: entities@8.0.0: {} - esbuild@0.25.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 - escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -3511,8 +3211,6 @@ snapshots: dependencies: commander: 8.3.0 - kleur@4.1.5: {} - lightningcss-android-arm64@1.32.0: optional: true @@ -3946,6 +3644,8 @@ snapshots: emojilib: 2.4.0 skin-tone: 2.0.0 + obug@2.1.1: {} + oniguruma-parser@0.12.2: {} oniguruma-to-es@4.3.6: @@ -3968,7 +3668,7 @@ snapshots: picomatch@4.0.4: {} - postcss@8.5.8: + postcss@8.5.12: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -4189,36 +3889,26 @@ snapshots: require-from-string@2.0.2: {} - rollup@4.60.1: + rolldown@1.0.0-rc.17: dependencies: - '@types/estree': 1.0.8 + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.1 - '@rollup/rollup-android-arm64': 4.60.1 - '@rollup/rollup-darwin-arm64': 4.60.1 - '@rollup/rollup-darwin-x64': 4.60.1 - '@rollup/rollup-freebsd-arm64': 4.60.1 - '@rollup/rollup-freebsd-x64': 4.60.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 - '@rollup/rollup-linux-arm-musleabihf': 4.60.1 - '@rollup/rollup-linux-arm64-gnu': 4.60.1 - '@rollup/rollup-linux-arm64-musl': 4.60.1 - '@rollup/rollup-linux-loong64-gnu': 4.60.1 - '@rollup/rollup-linux-loong64-musl': 4.60.1 - '@rollup/rollup-linux-ppc64-gnu': 4.60.1 - '@rollup/rollup-linux-ppc64-musl': 4.60.1 - '@rollup/rollup-linux-riscv64-gnu': 4.60.1 - '@rollup/rollup-linux-riscv64-musl': 4.60.1 - '@rollup/rollup-linux-s390x-gnu': 4.60.1 - '@rollup/rollup-linux-x64-gnu': 4.60.1 - '@rollup/rollup-linux-x64-musl': 4.60.1 - '@rollup/rollup-openbsd-x64': 4.60.1 - '@rollup/rollup-openharmony-arm64': 4.60.1 - '@rollup/rollup-win32-arm64-msvc': 4.60.1 - '@rollup/rollup-win32-ia32-msvc': 4.60.1 - '@rollup/rollup-win32-x64-gnu': 4.60.1 - '@rollup/rollup-win32-x64-msvc': 4.60.1 - fsevents: 2.3.3 + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 rope-sequence@1.3.4: {} @@ -4256,19 +3946,19 @@ snapshots: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 - svelte-check@4.4.6(picomatch@4.0.4)(svelte@5.55.1)(typescript@5.9.3): + svelte-check@4.4.6(picomatch@4.0.4)(svelte@5.55.5)(typescript@6.0.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.4) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.55.1 - typescript: 5.9.3 + svelte: 5.55.5 + typescript: 6.0.3 transitivePeerDependencies: - picomatch - svelte@5.55.1: + svelte@5.55.5: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 @@ -4289,11 +3979,11 @@ snapshots: symbol-tree@3.2.4: {} - tailwindcss@4.2.2: {} + tailwindcss@4.2.4: {} tapable@2.3.2: {} - tinyglobby@0.2.15: + tinyglobby@0.2.16: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 @@ -4322,11 +4012,11 @@ snapshots: tslib@2.8.1: {} - typescript@5.9.3: {} + typescript@6.0.3: {} uc.micro@2.1.0: {} - undici-types@7.16.0: {} + undici-types@7.19.2: {} undici@7.25.0: {} @@ -4394,23 +4084,21 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0): + vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1): dependencies: - esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.4) + lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.8 - rollup: 4.60.1 - tinyglobby: 0.2.15 + postcss: 8.5.12 + rolldown: 1.0.0-rc.17 + tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 24.12.2 + '@types/node': 25.6.0 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.32.0 - vitefu@1.1.3(vite@6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)): + vitefu@1.1.3(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1)): optionalDependencies: - vite: 6.4.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0) + vite: 8.0.10(@types/node@25.6.0)(jiti@2.6.1) w3c-keyname@2.2.8: {} diff --git a/ui/src/App.svelte b/ui/src/App.svelte index f9a60b8..ee9edfa 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -111,7 +111,27 @@ if (!response.ok) { throw new Error(`Status request failed with ${response.status}`); } - data = (await response.json()) as StatusResponse; + const raw = (await response.json()) as Partial | null; + data = { + title: raw?.title ?? 'AMCS', + description: raw?.description ?? '', + version: raw?.version ?? 'unknown', + build_date: raw?.build_date ?? 'unknown', + commit: raw?.commit ?? 'unknown', + connected_count: raw?.connected_count ?? 0, + total_known: raw?.total_known ?? 0, + connected_window: raw?.connected_window ?? 'last 10 minutes', + oauth_enabled: !!raw?.oauth_enabled, + entries: Array.isArray(raw?.entries) ? raw.entries : [], + metrics: { + total_requests: raw?.metrics?.total_requests ?? 0, + unique_principals: raw?.metrics?.unique_principals ?? 0, + unique_ips: raw?.metrics?.unique_ips ?? 0, + unique_agents: raw?.metrics?.unique_agents ?? 0, + top_ips: Array.isArray(raw?.metrics?.top_ips) ? raw.metrics.top_ips : [], + top_agents: Array.isArray(raw?.metrics?.top_agents) ? raw.metrics.top_agents : [] + } + }; } catch (err) { error = err instanceof Error ? err.message : 'Failed to load status'; } finally { diff --git a/ui/src/api.ts b/ui/src/api.ts index ce4280e..34d9052 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -94,20 +94,21 @@ async function rsCall( function rsReadMany( entity: string, - options?: { filters?: ResolveSpecFilter[]; limit?: number; sort?: { column: string; direction: 'asc' | 'desc' }[] } + options?: { filters?: ResolveSpecFilter[]; limit?: number; offset?: number; sort?: { column: string; direction: 'asc' | 'desc' }[] } ): Promise { - return rsCall(`/api/rs/public/${entity}`, 'read', { + return rsCall(`/api/rs/public/${entity}`, 'read', { options: { ...(options?.filters?.length ? { filters: options.filters } : {}), ...(options?.sort?.length ? { sort: options.sort } : {}), - ...(options?.limit ? { limit: options.limit } : {}) + ...(options?.limit ? { limit: options.limit } : {}), + ...(options?.offset ? { offset: options.offset } : {}) } - }); + }).then((rows) => (Array.isArray(rows) ? rows : [])); } export const api = { projects: { - list: async () => { + list: async (params?: { limit?: number; offset?: number; q?: string }) => { type ProjectRow = { guid: string; name: string; @@ -117,7 +118,12 @@ export const api = { thought_count?: number; }; - const projects = await rsCall('/api/rs/public/projects', 'read', { + const filters: ResolveSpecFilter[] = []; + if (params?.q) { + filters.push({ column: 'name', operator: 'ilike', value: `%${params.q}%` }); + } + + const projectRows = await rsCall('/api/rs/public/projects', 'read', { options: { columns: ['guid', 'name', 'description', 'created_at', 'last_active_at'], computedColumns: [ @@ -126,11 +132,14 @@ export const api = { expression: 'COALESCE((SELECT COUNT(*) FROM public.thoughts t WHERE t.project_id = projects.guid), 0)' } ], + ...(filters.length ? { filters } : {}), sort: [{ column: 'created_at', direction: 'desc' }], - limit: 500 + limit: params?.limit ?? 100, + offset: params?.offset ?? 0 } }); + const projects = Array.isArray(projectRows) ? projectRows : []; return projects.map((project) => ({ id: project.guid, name: project.name, @@ -146,7 +155,7 @@ export const api = { }) }, thoughts: { - list: (params: { q?: string; project_id?: string; limit?: number; include_archived?: boolean }) => { + list: (params: { q?: string; project_id?: string; limit?: number; offset?: number; include_archived?: boolean }) => { const filters: ResolveSpecFilter[] = []; if (params.q) { filters.push({ column: 'content', operator: 'ilike', value: `%${params.q}%` }); @@ -160,10 +169,65 @@ export const api = { return rsReadMany('thoughts', { filters, limit: params.limit ?? 100, + ...(params.offset !== undefined ? { offset: params.offset } : {}), sort: [{ column: 'created_at', direction: 'desc' }] - }); + }).then((rows) => + rows.map((row) => ({ + ...row, + content: typeof row.content === 'string' ? row.content : '', + metadata: { + ...(row.metadata ?? {}), + people: row.metadata?.people ?? [], + action_items: row.metadata?.action_items ?? [], + dates_mentioned: row.metadata?.dates_mentioned ?? [], + topics: row.metadata?.topics ?? [], + type: row.metadata?.type ?? '', + source: row.metadata?.source ?? '', + metadata_status: row.metadata?.metadata_status ?? '' + } + })) + ); }, - get: (id: string) => rsCall(`/api/rs/public/thoughts/${id}`, 'read'), + links: async (thoughtID: string) => { + const numericID = Number.parseInt(thoughtID, 10); + if (Number.isNaN(numericID)) return []; + + const [outbound, inbound] = await Promise.all([ + rsReadMany('thought_links', { + filters: [{ column: 'from_id', operator: 'eq', value: numericID }], + limit: 200, + sort: [{ column: 'created_at', direction: 'desc' }] + }), + rsReadMany('thought_links', { + filters: [{ column: 'to_id', operator: 'eq', value: numericID }], + limit: 200, + sort: [{ column: 'created_at', direction: 'desc' }] + }) + ]); + + const byID = new Map(); + for (const link of outbound) byID.set(link.id, link); + for (const link of inbound) byID.set(link.id, link); + + return Array.from(byID.values()).sort( + (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime() + ); + }, + get: (id: string) => + rsCall(`/api/rs/public/thoughts/${id}`, 'read').then((row) => ({ + ...row, + content: typeof row.content === 'string' ? row.content : '', + metadata: { + ...(row.metadata ?? {}), + people: row.metadata?.people ?? [], + action_items: row.metadata?.action_items ?? [], + dates_mentioned: row.metadata?.dates_mentioned ?? [], + topics: row.metadata?.topics ?? [], + type: row.metadata?.type ?? '', + source: row.metadata?.source ?? '', + metadata_status: row.metadata?.metadata_status ?? '' + } + })), delete: (id: string) => rsCall(`/api/rs/public/thoughts/${id}`, 'delete'), archive: (id: string) => rsCall(`/api/rs/public/thoughts/${id}`, 'update', { @@ -209,6 +273,46 @@ export const api = { }); } }, + maintenance: { + tasks: () => + rsReadMany('maintenance_tasks', { + limit: 200, + sort: [{ column: 'next_due', direction: 'asc' }] + }), + logs: () => + rsReadMany('maintenance_logs', { + limit: 200, + sort: [{ column: 'completed_at', direction: 'desc' }] + }), + runBackfill: (input?: { + project?: string; + limit?: number; + include_archived?: boolean; + older_than_days?: number; + dry_run?: boolean; + }) => + post('/api/admin/actions/backfill', { + limit: input?.limit ?? 100, + project: input?.project, + include_archived: input?.include_archived ?? false, + older_than_days: input?.older_than_days ?? 0, + dry_run: input?.dry_run ?? false + }), + runRetryMetadata: (input?: { + project?: string; + limit?: number; + include_archived?: boolean; + older_than_days?: number; + dry_run?: boolean; + }) => + post('/api/admin/actions/retry-metadata', { + limit: input?.limit ?? 100, + project: input?.project, + include_archived: input?.include_archived ?? false, + older_than_days: input?.older_than_days ?? 1, + dry_run: input?.dry_run ?? false + }) + }, stats: async () => { type StatsThoughtRow = { metadata?: { diff --git a/ui/src/components/dashboard/AccessTable.svelte b/ui/src/components/dashboard/AccessTable.svelte index bd52a28..34037c2 100644 --- a/ui/src/components/dashboard/AccessTable.svelte +++ b/ui/src/components/dashboard/AccessTable.svelte @@ -16,6 +16,7 @@ Principal + IP Last accessed Last path Agent @@ -26,6 +27,7 @@ {#each entries as entry} {entry.key_id} + {entry.remote_addr || '—'} {formatDate(entry.last_accessed_at)} {entry.last_path} {entry.user_agent ?? '—'} diff --git a/ui/src/components/dashboard/ConnectionBreakdown.svelte b/ui/src/components/dashboard/ConnectionBreakdown.svelte new file mode 100644 index 0000000..2b4af49 --- /dev/null +++ b/ui/src/components/dashboard/ConnectionBreakdown.svelte @@ -0,0 +1,43 @@ + + +
+

{title}

+ {#if entries.length === 0} +
+ {emptyLabel} +
+ {:else} +
+
+ + + + + + + + + {#each entries as entry} + + + + + {/each} + +
ValueRequests
{entry.key}{entry.request_count}
+
+
+ {/if} +
diff --git a/ui/src/components/dashboard/DashboardPage.svelte b/ui/src/components/dashboard/DashboardPage.svelte index 68eb39a..1f1bffa 100644 --- a/ui/src/components/dashboard/DashboardPage.svelte +++ b/ui/src/components/dashboard/DashboardPage.svelte @@ -1,6 +1,7 @@ -
+

Connected users

{data.connected_count}

@@ -13,8 +13,26 @@

Known principals

{data.total_known}

+
+

Total requests

+

{data.metrics.total_requests}

+
+
+

Unique IPs

+

{data.metrics.unique_ips}

+
+
+

Unique agents

+

{data.metrics.unique_agents}

+

Version

{data.version}

+ +
+

Build date: {data.build_date}

+

Commit: {data.commit}

+

OAuth enabled: {data.oauth_enabled ? 'yes' : 'no'}

+
diff --git a/ui/src/components/learnings/LearningsPage.svelte b/ui/src/components/learnings/LearningsPage.svelte new file mode 100644 index 0000000..ca485e8 --- /dev/null +++ b/ui/src/components/learnings/LearningsPage.svelte @@ -0,0 +1,219 @@ + + +
+
+
+

Learnings

+

+ {#if gridTotal === null} + Server-backed grid + {:else} + {gridTotal} result{gridTotal !== 1 ? "s" : ""} + {/if} +

+
+
+ +
+
+ +
+ + +
+
diff --git a/ui/src/components/maintenance/MaintenancePage.svelte b/ui/src/components/maintenance/MaintenancePage.svelte new file mode 100644 index 0000000..1e167dc --- /dev/null +++ b/ui/src/components/maintenance/MaintenancePage.svelte @@ -0,0 +1,184 @@ + + +
+
+
+

Maintenance

+

Operational state and safe maintenance actions.

+
+ +
+ +
+
+ + + +
+ {#if actionError} +

{actionError}

+ {/if} + {#if actionMessage} +

{actionMessage}

+ {/if} +
+ + {#if loading} +
Loading…
+ {:else if error} +
{error}
+ {:else} +
+

Tasks ({tasks.length})

+ {#if tasks.length === 0} +
No maintenance tasks.
+ {:else} +
+ + + + + + + + + + + + {#each tasks as task} + + + + + + + + {/each} + +
TaskCategoryPriorityNext DueLast Completed
{task.name}{task.category || '—'}{task.priority || 'medium'}{formatDate(task.next_due)}{formatDate(task.last_completed)}
+
+ {/if} +
+ +
+

Recent Logs ({logs.length})

+ {#if logs.length === 0} +
No maintenance logs.
+ {:else} +
+ + + + + + + + + + + + {#each logs as log} + + + + + + + + {/each} + +
CompletedTask IDByCostNotes
{formatDate(log.completed_at)}{log.task_id}{log.performed_by || '—'}{formatCost(log.cost)}{log.notes || '—'}
+
+ {/if} +
+ {/if} +
diff --git a/ui/src/components/projects/ProjectsPage.svelte b/ui/src/components/projects/ProjectsPage.svelte index cbf37c5..055c2e6 100644 --- a/ui/src/components/projects/ProjectsPage.svelte +++ b/ui/src/components/projects/ProjectsPage.svelte @@ -1,6 +1,9 @@ diff --git a/ui/src/components/thoughts/ThoughtsPage.svelte b/ui/src/components/thoughts/ThoughtsPage.svelte index ea1895a..f88f8fb 100644 --- a/ui/src/components/thoughts/ThoughtsPage.svelte +++ b/ui/src/components/thoughts/ThoughtsPage.svelte @@ -1,165 +1,389 @@ -
+

Thoughts

-

{rows.length} result{rows.length !== 1 ? 's' : ''}

+

+ {#if gridTotal === null} + Server-backed grid + {:else} + {gridTotal} result{gridTotal !== 1 ? "s" : ""} + {/if} +

+ onclick={() => { + selectedThought = null; + relatedLinks = []; + relatedFiles = []; + }}>Refresh
- - {#if actionError}

{actionError}

{/if} - {#if loading} -
Loading…
- {:else if error} -
{error}
- {:else if rows.length === 0} -
No thoughts found.
- {:else} -
- - - - - - - - - - - - {#each rows as row} - - - - - - - - {/each} - -
ContentTypeStatusCreatedActions
-

{content(row)}

- {#if row.metadata.topics?.length} -

{row.metadata.topics.slice(0,3).join(', ')}

- {/if} -
- - {row.metadata.type || '—'} - - - {isArchived(row) ? 'archived' : (row.metadata.metadata_status || 'active')} - {formatDate(row.created_at)} -
- {#if !isArchived(row)} - - {/if} - -
-
+
+
+
- {/if} + + +
diff --git a/ui/src/gridTheme.ts b/ui/src/gridTheme.ts new file mode 100644 index 0000000..dbb6f22 --- /dev/null +++ b/ui/src/gridTheme.ts @@ -0,0 +1,18 @@ +import type { GridlerTheme } from '@warkypublic/svelix'; + +export const adminGridTheme: Partial = { + accentColor: '#22d3ee', + accentFg: '#06232b', + bgCell: '#020617', + bgHeader: '#0f172a', + bgHeaderHasFocus: '#164e63', + bgSearchResult: '#083344', + borderColor: '#1e293b', + foreground: '#cbd5e1', + fontFamily: 'Inter, system-ui, sans-serif', + fontSize: 13, + headerFontSize: 12, + lineHeight: 1.4, + cellHorizontalPadding: 10, + cellVerticalPadding: 8 +}; diff --git a/ui/src/types.ts b/ui/src/types.ts index 9348682..13704f3 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -2,10 +2,25 @@ export type AccessEntry = { key_id: string; last_accessed_at: string; last_path: string; + remote_addr: string; user_agent: string; request_count: number; }; +export type RequestAggregate = { + key: string; + request_count: number; +}; + +export type AccessMetrics = { + total_requests: number; + unique_principals: number; + unique_ips: number; + unique_agents: number; + top_ips: RequestAggregate[]; + top_agents: RequestAggregate[]; +}; + export type StatusResponse = { title: string; description: string; @@ -17,6 +32,7 @@ export type StatusResponse = { connected_window: string; oauth_enabled: boolean; entries: AccessEntry[]; + metrics: AccessMetrics; }; export type NavItem = { @@ -26,7 +42,7 @@ export type NavItem = { disabled?: boolean; }; -export type ShellPage = 'dashboard' | 'projects' | 'thoughts' | 'skills' | 'guardrails' | 'files'; +export type ShellPage = 'dashboard' | 'projects' | 'thoughts' | 'learnings' | 'skills' | 'guardrails' | 'files' | 'maintenance'; export type Project = { id: string; @@ -49,10 +65,21 @@ export type ThoughtMetadata = { source: string; metadata_status: string; metadata_error?: string; + attachments?: ThoughtAttachment[]; +}; + +export type ThoughtAttachment = { + file_id: string; + name: string; + media_type: string; + kind?: string; + size_bytes: number; + sha256?: string; }; export type Thought = { id: string; + guid?: string; content: string; metadata: ThoughtMetadata; project_id?: string; @@ -61,6 +88,14 @@ export type Thought = { updated_at: string; }; +export type ThoughtLink = { + id: number; + from_id: number; + to_id: number; + relation: string; + created_at: string; +}; + export type SearchResult = { id: string; content: string; @@ -109,3 +144,66 @@ export type ThoughtStats = { top_topics: { key: string; count: number }[]; top_people: { key: string; count: number }[]; }; + +export type Learning = { + id: string; + summary: string; + details: string; + category: string; + area: string; + status: string; + priority: string; + confidence: string; + action_required: boolean; + source_type?: string; + source_ref?: string; + tags?: string; + project_id?: string; + related_thought_id?: string; + related_skill_id?: string; + reviewed_at?: string; + reviewed_by?: string; + created_at: string; + updated_at: string; +}; + +export type MaintenanceTask = { + id: string; + name: string; + category?: string; + priority: string; + frequency_days?: number; + next_due?: string; + last_completed?: string; + notes?: string; + created_at: string; + updated_at: string; +}; + +export type MaintenanceLog = { + id: string; + task_id: string; + completed_at: string; + performed_by?: string; + cost?: number; + notes?: string; + next_action?: string; +}; + +export type BackfillResult = { + model: string; + scanned: number; + embedded: number; + skipped: number; + failed: number; + dry_run: boolean; +}; + +export type MetadataRetryResult = { + scanned: number; + retried: number; + updated: number; + skipped: number; + failed: number; + dry_run: boolean; +};