manigent-server 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manigent/_catalog/mcps/chess/README.md +38 -0
- manigent/_catalog/mcps/chess/mcp.json +9 -0
- manigent/_catalog/mcps/registry.json +9 -0
- manigent/_catalog/skills/brief/SKILL.md +339 -0
- manigent/_catalog/skills/broaden-options/SKILL.md +133 -0
- manigent/_catalog/skills/calendar/SKILL.md +44 -0
- manigent/_catalog/skills/calendar/starter_config.jsonc +25 -0
- manigent/_catalog/skills/gmail/SKILL.md +73 -0
- manigent/_catalog/skills/google-calendar/SKILL.md +44 -0
- manigent/_catalog/skills/google-drive/SKILL.md +42 -0
- manigent/_catalog/skills/google-drive-write/SKILL.md +45 -0
- manigent/_catalog/skills/google-drive-write/starter_config.jsonc +45 -0
- manigent/_catalog/skills/grill-me/SKILL.md +173 -0
- manigent/_catalog/skills/homekit/SKILL.md +37 -0
- manigent/_catalog/skills/http-api/SKILL.md +117 -0
- manigent/_catalog/skills/ib/SKILL.md +89 -0
- manigent/_catalog/skills/ib/starter_config.jsonc +35 -0
- manigent/_catalog/skills/mailbox/SKILL.md +47 -0
- manigent/_catalog/skills/mailbox/starter_config.jsonc +38 -0
- manigent/_catalog/skills/media/SKILL.md +93 -0
- manigent/_catalog/skills/media/starter_config.jsonc +63 -0
- manigent/_catalog/skills/osxphotos/SKILL.md +41 -0
- manigent/_catalog/skills/photo-story/SKILL.md +285 -0
- manigent/_catalog/skills/playwright-browser/SKILL.md +291 -0
- manigent/_catalog/skills/playwright-browser/starter_config.jsonc +44 -0
- manigent/_catalog/skills/playwright-save-skill/SKILL.md +149 -0
- manigent/_catalog/skills/registry.json +166 -0
- manigent/_catalog/skills/web-artifacts/SKILL.md +194 -0
- manigent/api/action_executor.py +404 -0
- manigent/api/action_validator.py +531 -0
- manigent/api/actions.py +221 -0
- manigent/api/client.py +558 -0
- manigent/api/control.py +806 -0
- manigent/api/edge_protocol.py +123 -0
- manigent/api/fs_protocol.py +450 -0
- manigent/api/host_protocol.py +298 -0
- manigent/api/managed_job_client.py +124 -0
- manigent/api/responses.py +342 -0
- manigent/api/retry_transport.py +178 -0
- manigent/api/turn_protocol.py +78 -0
- manigent/cli_lifecycle.py +467 -0
- manigent/cli_server.py +978 -0
- manigent/doctor.py +1167 -0
- manigent/hub_protocol/canonical.py +375 -0
- manigent/hub_protocol/codes.py +171 -0
- manigent/hub_protocol/constants.py +71 -0
- manigent/hub_protocol/envelope.py +218 -0
- manigent/hub_protocol/frames.py +788 -0
- manigent/hub_protocol/http.py +260 -0
- manigent/hub_protocol/ids.py +170 -0
- manigent/hub_protocol/keys.py +195 -0
- manigent/hub_protocol/text.py +75 -0
- manigent/llm/base.py +1072 -0
- manigent/llm/prompt_builder.py +2450 -0
- manigent/llm/triggers.py +68 -0
- manigent/models/agent.py +2905 -0
- manigent/models/agent_bundle.py +298 -0
- manigent/models/brief_tab.py +311 -0
- manigent/models/chat_message.py +231 -0
- manigent/models/chatroom.py +1108 -0
- manigent/models/context.py +1159 -0
- manigent/models/desk_label.py +836 -0
- manigent/models/desk_read_state.py +220 -0
- manigent/models/desk_sign_off.py +267 -0
- manigent/models/desk_sop.py +965 -0
- manigent/models/desk_todo.py +1603 -0
- manigent/models/desk_todo_link.py +404 -0
- manigent/models/file_cache.py +75 -0
- manigent/models/host.py +732 -0
- manigent/models/install_token.py +401 -0
- manigent/models/invitation_code.py +162 -0
- manigent/models/knowledge_pack.py +552 -0
- manigent/models/managed_job.py +295 -0
- manigent/models/model_config.py +564 -0
- manigent/models/model_defaults.py +238 -0
- manigent/models/onboarding_plan.py +161 -0
- manigent/models/participant.py +449 -0
- manigent/models/persistable.py +892 -0
- manigent/models/plan_markdown.py +1774 -0
- manigent/models/project.py +1343 -0
- manigent/models/project_plan.py +8104 -0
- manigent/models/project_report.py +145 -0
- manigent/models/provenance.py +261 -0
- manigent/models/provider_agents.py +101 -0
- manigent/models/public_request.py +108 -0
- manigent/models/reflection_schedule.py +191 -0
- manigent/models/scheduled_message.py +285 -0
- manigent/models/share_token.py +190 -0
- manigent/models/skill_version.py +31 -0
- manigent/models/sop_template.py +187 -0
- manigent/models/thinking_partner_plan.py +187 -0
- manigent/models/tunnel_binding.py +129 -0
- manigent/models/user.py +1276 -0
- manigent/models/work_tracker.py +510 -0
- manigent/runner/invocation_registry.py +187 -0
- manigent/server/agent_capabilities.py +119 -0
- manigent/server/agent_visibility.py +119 -0
- manigent/server/announcements.py +119 -0
- manigent/server/app.py +807 -0
- manigent/server/auth.py +355 -0
- manigent/server/batch_timeout.py +94 -0
- manigent/server/channels/access.py +194 -0
- manigent/server/channels/adapter.py +295 -0
- manigent/server/channels/config.py +693 -0
- manigent/server/channels/contacts.py +39 -0
- manigent/server/channels/email_adapter.py +436 -0
- manigent/server/channels/email_address.py +145 -0
- manigent/server/channels/email_form.py +242 -0
- manigent/server/channels/email_format.py +285 -0
- manigent/server/channels/email_mail.py +427 -0
- manigent/server/channels/email_mailgun.py +164 -0
- manigent/server/channels/email_text.py +280 -0
- manigent/server/channels/file_links.py +127 -0
- manigent/server/channels/gateway.py +2998 -0
- manigent/server/channels/hub_delivery.py +211 -0
- manigent/server/channels/hub_store.py +1730 -0
- manigent/server/channels/line.py +645 -0
- manigent/server/channels/line_format.py +589 -0
- manigent/server/channels/log_redaction.py +145 -0
- manigent/server/channels/store.py +3316 -0
- manigent/server/channels/telegram.py +422 -0
- manigent/server/channels/telegram_format.py +312 -0
- manigent/server/channels/webchat.py +261 -0
- manigent/server/channels/webchat_widget.js +1310 -0
- manigent/server/channels/whatsapp.py +707 -0
- manigent/server/channels/whatsapp_format.py +511 -0
- manigent/server/chat_search_index.py +1203 -0
- manigent/server/config.py +61 -0
- manigent/server/context.py +121 -0
- manigent/server/coordinator_rule.py +115 -0
- manigent/server/desk_todo_notify.py +300 -0
- manigent/server/edge_hub.py +577 -0
- manigent/server/email.py +354 -0
- manigent/server/errors.py +83 -0
- manigent/server/home_fs_relay.py +392 -0
- manigent/server/hub/adapter.py +1395 -0
- manigent/server/hub/agent_send_guard.py +196 -0
- manigent/server/hub/avatar.py +342 -0
- manigent/server/hub/browser.py +778 -0
- manigent/server/hub/client.py +859 -0
- manigent/server/hub/demand.py +49 -0
- manigent/server/hub/directory.py +385 -0
- manigent/server/hub/enroll.py +400 -0
- manigent/server/hub/http.py +134 -0
- manigent/server/hub/identity.py +61 -0
- manigent/server/hub/keys.py +588 -0
- manigent/server/hub/mirror.py +802 -0
- manigent/server/hub/operator.py +817 -0
- manigent/server/hub/retain.py +361 -0
- manigent/server/hub/room_bridge.py +741 -0
- manigent/server/hub/runtime.py +691 -0
- manigent/server/hub/trust.py +182 -0
- manigent/server/hub/views.py +316 -0
- manigent/server/idempotency.py +205 -0
- manigent/server/in_flight_restore.py +181 -0
- manigent/server/inspection.py +816 -0
- manigent/server/isolation_gate.py +516 -0
- manigent/server/isolation_notice.py +310 -0
- manigent/server/managed_job_events.py +242 -0
- manigent/server/mcp_registry.py +118 -0
- manigent/server/message_forward.py +78 -0
- manigent/server/message_scheduler.py +176 -0
- manigent/server/notification_assets.py +164 -0
- manigent/server/oauth_login/cli_state.py +120 -0
- manigent/server/oauth_login/config.py +90 -0
- manigent/server/oauth_login/errors.py +78 -0
- manigent/server/oauth_login/flow.py +223 -0
- manigent/server/oauth_login/handoff.py +63 -0
- manigent/server/oauth_login/providers.py +45 -0
- manigent/server/oauth_state.py +81 -0
- manigent/server/owner_isolation.py +466 -0
- manigent/server/participant_liveness.py +82 -0
- manigent/server/public_profile.py +149 -0
- manigent/server/public_requests/candidates.py +116 -0
- manigent/server/public_requests/dispatch.py +121 -0
- manigent/server/public_requests/ranking.py +153 -0
- manigent/server/recent_activity.py +250 -0
- manigent/server/reflection_completion_subscriber.py +151 -0
- manigent/server/reflection_scheduler.py +239 -0
- manigent/server/requester_key_migration.py +261 -0
- manigent/server/room_flow.py +139 -0
- manigent/server/routes/_batch.py +218 -0
- manigent/server/routes/_fd_routing.py +155 -0
- manigent/server/routes/_mentions.py +70 -0
- manigent/server/routes/agent_env.py +188 -0
- manigent/server/routes/agent_fs.py +450 -0
- manigent/server/routes/agents.py +1206 -0
- manigent/server/routes/announcements.py +79 -0
- manigent/server/routes/auth.py +387 -0
- manigent/server/routes/brief_tabs.py +404 -0
- manigent/server/routes/changelog.py +237 -0
- manigent/server/routes/channels.py +993 -0
- manigent/server/routes/chatrooms.py +871 -0
- manigent/server/routes/desk_labels.py +298 -0
- manigent/server/routes/desk_latest_messages.py +116 -0
- manigent/server/routes/desk_read_state.py +110 -0
- manigent/server/routes/desk_sign_off.py +88 -0
- manigent/server/routes/desk_sops.py +216 -0
- manigent/server/routes/desk_todos.py +692 -0
- manigent/server/routes/desk_tour.py +44 -0
- manigent/server/routes/files.py +572 -0
- manigent/server/routes/hosts.py +484 -0
- manigent/server/routes/hub.py +534 -0
- manigent/server/routes/hub_channels.py +234 -0
- manigent/server/routes/inbox.py +581 -0
- manigent/server/routes/install.ps1 +436 -0
- manigent/server/routes/install.py +324 -0
- manigent/server/routes/install.sh +566 -0
- manigent/server/routes/isolation_check.py +145 -0
- manigent/server/routes/knowledge_packs.py +477 -0
- manigent/server/routes/managed_jobs.py +196 -0
- manigent/server/routes/mcps.py +271 -0
- manigent/server/routes/messages.py +1972 -0
- manigent/server/routes/model_defaults.py +279 -0
- manigent/server/routes/oauth.py +237 -0
- manigent/server/routes/oauth_login.py +351 -0
- manigent/server/routes/owner_isolation.py +91 -0
- manigent/server/routes/project_plans.py +1521 -0
- manigent/server/routes/project_reports.py +235 -0
- manigent/server/routes/projects.py +2047 -0
- manigent/server/routes/public_agents.py +151 -0
- manigent/server/routes/public_requests.py +181 -0
- manigent/server/routes/reflection.py +93 -0
- manigent/server/routes/scheduled_messages.py +263 -0
- manigent/server/routes/search.py +94 -0
- manigent/server/routes/sharing.py +156 -0
- manigent/server/routes/skills.py +406 -0
- manigent/server/routes/templates.py +219 -0
- manigent/server/routes/turn_tokens.py +119 -0
- manigent/server/routes/webchat.py +406 -0
- manigent/server/routes/websocket.py +431 -0
- manigent/server/serving_only.py +200 -0
- manigent/server/signup_policy.py +120 -0
- manigent/server/skill_registry.py +164 -0
- manigent/server/socket_events.py +858 -0
- manigent/server/timeout_checker.py +446 -0
- manigent/server/tunnel_subscriber.py +1000 -0
- manigent/server/turn_tokens.py +285 -0
- manigent/server/ws_hub.py +556 -0
- manigent/server_cli.py +31 -0
- manigent/sync/changelog.py +885 -0
- manigent/sync/console_subscriber.py +222 -0
- manigent/sync/runloop.py +619 -0
- manigent/sync/runloop_manager.py +197 -0
- manigent/sync/subscriber.py +98 -0
- manigent/utils/agent_processes.py +276 -0
- manigent/utils/agent_storage.py +125 -0
- manigent/utils/email.py +563 -0
- manigent/utils/env_store.py +174 -0
- manigent/utils/extra_dirs.py +85 -0
- manigent/utils/file_io.py +485 -0
- manigent/utils/invoke_timeout.py +87 -0
- manigent/utils/jsonc.py +78 -0
- manigent/utils/knowledge_index.py +148 -0
- manigent/utils/nofollow_fs.py +279 -0
- manigent/utils/notification_center.py +67 -0
- manigent/utils/version.py +57 -0
- manigent/web/frontend/.design-sync-entry.tsx +74 -0
- manigent/web/frontend/.design-sync-provider.tsx +46 -0
- manigent/web/frontend/.eslintrc.cjs +183 -0
- manigent/web/frontend/.gitignore +6 -0
- manigent/web/frontend/app/page.tsx +39 -0
- manigent/web/frontend/hub-terms.html +192 -0
- manigent/web/frontend/index.html +15 -0
- manigent/web/frontend/landing.html +15 -0
- manigent/web/frontend/package-lock.json +8028 -0
- manigent/web/frontend/package.json +55 -0
- manigent/web/frontend/postcss.config.js +6 -0
- manigent/web/frontend/privacy.html +217 -0
- manigent/web/frontend/private-beta-announcement.html +19 -0
- manigent/web/frontend/public/apple-touch-icon.png +0 -0
- manigent/web/frontend/public/brand-hero-banner.png +0 -0
- manigent/web/frontend/public/brand-hero.png +0 -0
- manigent/web/frontend/public/favicon.png +0 -0
- manigent/web/frontend/public/favicon.svg +8 -0
- manigent/web/frontend/public/icon-transparent-sm.png +0 -0
- manigent/web/frontend/public/icon.png +0 -0
- manigent/web/frontend/public/manigent-mark.svg +7 -0
- manigent/web/frontend/public/vite.svg +5 -0
- manigent/web/frontend/public.html +15 -0
- manigent/web/frontend/src/App.activeAccountRedirect.test.tsx +236 -0
- manigent/web/frontend/src/App.hubHostDm.test.tsx +119 -0
- manigent/web/frontend/src/App.hubHostProject.test.tsx +142 -0
- manigent/web/frontend/src/App.hubRoutes.test.tsx +187 -0
- manigent/web/frontend/src/App.oauthAddCallback.test.tsx +263 -0
- manigent/web/frontend/src/App.tsx +246 -0
- manigent/web/frontend/src/api/attachments.ts +32 -0
- manigent/web/frontend/src/api/batchLoader.test.ts +212 -0
- manigent/web/frontend/src/api/batchLoader.ts +124 -0
- manigent/web/frontend/src/api/chatroomsBatch.test.tsx +392 -0
- manigent/web/frontend/src/api/client.ts +321 -0
- manigent/web/frontend/src/api/crossAccountAuth.test.ts +388 -0
- manigent/web/frontend/src/api/endpoints/agentEnv.ts +24 -0
- manigent/web/frontend/src/api/endpoints/agentFs.ts +125 -0
- manigent/web/frontend/src/api/endpoints/agentSearch.ts +19 -0
- manigent/web/frontend/src/api/endpoints/agents.ts +66 -0
- manigent/web/frontend/src/api/endpoints/announcements.ts +26 -0
- manigent/web/frontend/src/api/endpoints/auth.ts +361 -0
- manigent/web/frontend/src/api/endpoints/authLogin.test.ts +236 -0
- manigent/web/frontend/src/api/endpoints/batch.ts +64 -0
- manigent/web/frontend/src/api/endpoints/briefTabs.ts +81 -0
- manigent/web/frontend/src/api/endpoints/channels.ts +285 -0
- manigent/web/frontend/src/api/endpoints/computers.ts +212 -0
- manigent/web/frontend/src/api/endpoints/deskLabels.ts +151 -0
- manigent/web/frontend/src/api/endpoints/deskLatestMessages.ts +23 -0
- manigent/web/frontend/src/api/endpoints/deskReadState.ts +41 -0
- manigent/web/frontend/src/api/endpoints/deskSignOffs.ts +74 -0
- manigent/web/frontend/src/api/endpoints/deskSops.ts +53 -0
- manigent/web/frontend/src/api/endpoints/deskTodoAttachments.ts +69 -0
- manigent/web/frontend/src/api/endpoints/deskTodoProjects.test.ts +99 -0
- manigent/web/frontend/src/api/endpoints/deskTodoProjects.ts +114 -0
- manigent/web/frontend/src/api/endpoints/deskTodos.ts +164 -0
- manigent/web/frontend/src/api/endpoints/deskTour.ts +16 -0
- manigent/web/frontend/src/api/endpoints/dm.ts +137 -0
- manigent/web/frontend/src/api/endpoints/files.ts +55 -0
- manigent/web/frontend/src/api/endpoints/hub.contract.test.ts +94 -0
- manigent/web/frontend/src/api/endpoints/hub.keys.ts +260 -0
- manigent/web/frontend/src/api/endpoints/hub.test.ts +203 -0
- manigent/web/frontend/src/api/endpoints/hub.ts +654 -0
- manigent/web/frontend/src/api/endpoints/index.ts +15 -0
- manigent/web/frontend/src/api/endpoints/install.ts +74 -0
- manigent/web/frontend/src/api/endpoints/isolationCheck.ts +36 -0
- manigent/web/frontend/src/api/endpoints/knowledgePacks.ts +179 -0
- manigent/web/frontend/src/api/endpoints/mcps.ts +36 -0
- manigent/web/frontend/src/api/endpoints/messages.held.test.ts +43 -0
- manigent/web/frontend/src/api/endpoints/messages.ts +95 -0
- manigent/web/frontend/src/api/endpoints/modelConfigs.ts +232 -0
- manigent/web/frontend/src/api/endpoints/oauthApi.test.ts +110 -0
- manigent/web/frontend/src/api/endpoints/projectPlans.ts +608 -0
- manigent/web/frontend/src/api/endpoints/projectReports.ts +36 -0
- manigent/web/frontend/src/api/endpoints/projects.ts +124 -0
- manigent/web/frontend/src/api/endpoints/publicRequests.ts +75 -0
- manigent/web/frontend/src/api/endpoints/reflection.ts +32 -0
- manigent/web/frontend/src/api/endpoints/roomFlow.ts +14 -0
- manigent/web/frontend/src/api/endpoints/scheduledMessages.ts +60 -0
- manigent/web/frontend/src/api/endpoints/search.ts +27 -0
- manigent/web/frontend/src/api/endpoints/sharing.ts +42 -0
- manigent/web/frontend/src/api/endpoints/skills.ts +67 -0
- manigent/web/frontend/src/api/endpoints/templates.ts +72 -0
- manigent/web/frontend/src/api/loaders.ts +22 -0
- manigent/web/frontend/src/api/singleIssuer.test.ts +148 -0
- manigent/web/frontend/src/api/transientRetry.test.ts +176 -0
- manigent/web/frontend/src/api/transientRetry.ts +140 -0
- manigent/web/frontend/src/api/websocket.resync.test.ts +111 -0
- manigent/web/frontend/src/api/websocket.ts +335 -0
- manigent/web/frontend/src/components/agent/AgentNameLink.tsx +24 -0
- manigent/web/frontend/src/components/agent/AgentViewActions.test.tsx +41 -0
- manigent/web/frontend/src/components/agent/AgentViewActions.tsx +89 -0
- manigent/web/frontend/src/components/agent/CreateAgentPage.tsx +535 -0
- manigent/web/frontend/src/components/agent/JsonConfigEditor.tsx +33 -0
- manigent/web/frontend/src/components/agent/KnowledgePackList.tsx +103 -0
- manigent/web/frontend/src/components/agent/McpCatalog.tsx +261 -0
- manigent/web/frontend/src/components/agent/McpConfigModal.tsx +224 -0
- manigent/web/frontend/src/components/agent/PreConsentModal.tsx +172 -0
- manigent/web/frontend/src/components/agent/SkillCatalog.tsx +364 -0
- manigent/web/frontend/src/components/agent/SkillConfigModal.tsx +221 -0
- manigent/web/frontend/src/components/auth/AddAccountPage.test.tsx +181 -0
- manigent/web/frontend/src/components/auth/AddAccountPage.tsx +201 -0
- manigent/web/frontend/src/components/auth/AuthShell.tsx +462 -0
- manigent/web/frontend/src/components/auth/LoginPage.test.tsx +384 -0
- manigent/web/frontend/src/components/auth/LoginPage.tsx +129 -0
- manigent/web/frontend/src/components/auth/OAuthCallbackPage.test.tsx +185 -0
- manigent/web/frontend/src/components/auth/OAuthCallbackPage.tsx +154 -0
- manigent/web/frontend/src/components/auth/OAuthLinkedBanner.tsx +44 -0
- manigent/web/frontend/src/components/auth/ProviderButtons.tsx +94 -0
- manigent/web/frontend/src/components/auth/SignupPage.tsx +437 -0
- manigent/web/frontend/src/components/auth/StepProgress.tsx +60 -0
- manigent/web/frontend/src/components/auth/VerifyEmailPage.test.tsx +155 -0
- manigent/web/frontend/src/components/auth/VerifyEmailPage.tsx +211 -0
- manigent/web/frontend/src/components/auth/index.ts +3 -0
- manigent/web/frontend/src/components/auth/oauthErrors.test.ts +107 -0
- manigent/web/frontend/src/components/auth/oauthErrors.ts +82 -0
- manigent/web/frontend/src/components/auth/useUsernameCheck.test.tsx +68 -0
- manigent/web/frontend/src/components/auth/useUsernameCheck.ts +86 -0
- manigent/web/frontend/src/components/beta/BetaPage.tsx +21 -0
- manigent/web/frontend/src/components/beta/DayAtMyDesk.tsx +120 -0
- manigent/web/frontend/src/components/beta/Footer.tsx +82 -0
- manigent/web/frontend/src/components/beta/Hero.tsx +224 -0
- manigent/web/frontend/src/components/beta/Nav.tsx +51 -0
- manigent/web/frontend/src/components/beta/Pillars.tsx +172 -0
- manigent/web/frontend/src/components/beta/TwoModels.tsx +146 -0
- manigent/web/frontend/src/components/beta/constants.ts +3 -0
- manigent/web/frontend/src/components/briefing/BriefingFrame.test.tsx +208 -0
- manigent/web/frontend/src/components/briefing/BriefingFrame.tsx +151 -0
- manigent/web/frontend/src/components/briefing/BriefingPage.test.tsx +176 -0
- manigent/web/frontend/src/components/briefing/BriefingPage.tsx +157 -0
- manigent/web/frontend/src/components/briefing/BriefingPage.wsDelete.test.tsx +177 -0
- manigent/web/frontend/src/components/briefing/briefing.css +147 -0
- manigent/web/frontend/src/components/briefing/heightBootstrap.test.ts +121 -0
- manigent/web/frontend/src/components/briefing/heightBootstrap.ts +224 -0
- manigent/web/frontend/src/components/briefing/parseBriefMessage.test.ts +90 -0
- manigent/web/frontend/src/components/briefing/sampleBriefing.ts +148 -0
- manigent/web/frontend/src/components/channels/ChannelBadge.tsx +35 -0
- manigent/web/frontend/src/components/chatroom/ChannelThreadControls.tsx +127 -0
- manigent/web/frontend/src/components/chatroom/ChannelThreadNotice.test.tsx +207 -0
- manigent/web/frontend/src/components/chatroom/ChannelThreadNotice.tsx +80 -0
- manigent/web/frontend/src/components/chatroom/ChatroomView.channel.test.tsx +550 -0
- manigent/web/frontend/src/components/chatroom/ChatroomView.flow.test.tsx +186 -0
- manigent/web/frontend/src/components/chatroom/ChatroomView.isolation.test.tsx +70 -0
- manigent/web/frontend/src/components/chatroom/ChatroomView.tsx +450 -0
- manigent/web/frontend/src/components/chatroom/DelegationRow.test.tsx +170 -0
- manigent/web/frontend/src/components/chatroom/DelegationRow.tsx +217 -0
- manigent/web/frontend/src/components/chatroom/DeliveryStatusLine.test.tsx +33 -0
- manigent/web/frontend/src/components/chatroom/DeliveryStatusLine.tsx +46 -0
- manigent/web/frontend/src/components/chatroom/ForwardModal.test.tsx +109 -0
- manigent/web/frontend/src/components/chatroom/ForwardModal.tsx +304 -0
- manigent/web/frontend/src/components/chatroom/HeldNotice.tsx +77 -0
- manigent/web/frontend/src/components/chatroom/MentionDropdown.tsx +97 -0
- manigent/web/frontend/src/components/chatroom/MessageInput.tsx +408 -0
- manigent/web/frontend/src/components/chatroom/MessageItem.tsx +425 -0
- manigent/web/frontend/src/components/chatroom/MessageList.outside.test.tsx +98 -0
- manigent/web/frontend/src/components/chatroom/MessageList.test.tsx +245 -0
- manigent/web/frontend/src/components/chatroom/MessageList.tsx +406 -0
- manigent/web/frontend/src/components/chatroom/OfflineRemedyNote.test.tsx +211 -0
- manigent/web/frontend/src/components/chatroom/OfflineRemedyNote.tsx +182 -0
- manigent/web/frontend/src/components/chatroom/OutsideBadge.tsx +18 -0
- manigent/web/frontend/src/components/chatroom/RoomFlowControl.tsx +49 -0
- manigent/web/frontend/src/components/chatroom/RoomFlowStrip.test.tsx +47 -0
- manigent/web/frontend/src/components/chatroom/RoomFlowStrip.tsx +25 -0
- manigent/web/frontend/src/components/chatroom/SaveToPackModal.tsx +350 -0
- manigent/web/frontend/src/components/chatroom/StagedAttachments.test.tsx +56 -0
- manigent/web/frontend/src/components/chatroom/StagedAttachments.tsx +160 -0
- manigent/web/frontend/src/components/chatroom/SystemNote.tsx +18 -0
- manigent/web/frontend/src/components/chatroom/contactSender.test.tsx +130 -0
- manigent/web/frontend/src/components/chatroom/index.ts +3 -0
- manigent/web/frontend/src/components/chatroom/recipientStatus.test.ts +344 -0
- manigent/web/frontend/src/components/chatroom/recipientStatus.ts +197 -0
- manigent/web/frontend/src/components/common/AnnouncementBanners.test.tsx +221 -0
- manigent/web/frontend/src/components/common/AnnouncementBanners.tsx +180 -0
- manigent/web/frontend/src/components/common/Avatar.test.tsx +50 -0
- manigent/web/frontend/src/components/common/Avatar.tsx +102 -0
- manigent/web/frontend/src/components/common/Badge.tsx +30 -0
- manigent/web/frontend/src/components/common/BrandName.tsx +16 -0
- manigent/web/frontend/src/components/common/ClearHistoryButton.tsx +74 -0
- manigent/web/frontend/src/components/common/CollapsibleHeader.tsx +64 -0
- manigent/web/frontend/src/components/common/ConnectionStatus.test.tsx +56 -0
- manigent/web/frontend/src/components/common/ConnectionStatus.tsx +28 -0
- manigent/web/frontend/src/components/common/DropOverlay.tsx +27 -0
- manigent/web/frontend/src/components/common/InlineEditableTitle.test.tsx +369 -0
- manigent/web/frontend/src/components/common/InlineEditableTitle.tsx +287 -0
- manigent/web/frontend/src/components/common/IsolationUnavailablePanel.tsx +19 -0
- manigent/web/frontend/src/components/common/Loading.tsx +22 -0
- manigent/web/frontend/src/components/common/ModelSelector.tsx +248 -0
- manigent/web/frontend/src/components/common/Overlay.tsx +15 -0
- manigent/web/frontend/src/components/common/ResizeHandle.tsx +52 -0
- manigent/web/frontend/src/components/common/TagAutocompleteInput.test.tsx +92 -0
- manigent/web/frontend/src/components/common/TagAutocompleteInput.tsx +246 -0
- manigent/web/frontend/src/components/common/TagPillInput.tsx +181 -0
- manigent/web/frontend/src/components/common/TeamAndAgentPicker.tsx +83 -0
- manigent/web/frontend/src/components/common/Timestamp.tsx +61 -0
- manigent/web/frontend/src/components/common/VerifiedBadge.tsx +20 -0
- manigent/web/frontend/src/components/common/index.ts +8 -0
- manigent/web/frontend/src/components/composer/PhPopover.tsx +239 -0
- manigent/web/frontend/src/components/composer/PromptEditor.test.tsx +472 -0
- manigent/web/frontend/src/components/composer/PromptEditor.tsx +540 -0
- manigent/web/frontend/src/components/composer/caret.ts +159 -0
- manigent/web/frontend/src/components/composer/placeholders.test.ts +352 -0
- manigent/web/frontend/src/components/composer/placeholders.ts +354 -0
- manigent/web/frontend/src/components/composer/promptEditor.css +161 -0
- manigent/web/frontend/src/components/computer/ComputerPage.test.tsx +452 -0
- manigent/web/frontend/src/components/computer/ComputerPage.tsx +811 -0
- manigent/web/frontend/src/components/computer/ConnectComputerModal.tsx +135 -0
- manigent/web/frontend/src/components/computer/CopyCommand.tsx +77 -0
- manigent/web/frontend/src/components/computer/DisconnectComputerModal.tsx +121 -0
- manigent/web/frontend/src/components/computer/SoftwareCard.tsx +145 -0
- manigent/web/frontend/src/components/computer/TerminalCard.test.tsx +114 -0
- manigent/web/frontend/src/components/computer/TerminalCard.tsx +131 -0
- manigent/web/frontend/src/components/computer/TerminalPanel.tsx +226 -0
- manigent/web/frontend/src/components/computer/TrustPanel.test.tsx +58 -0
- manigent/web/frontend/src/components/computer/TrustPanel.tsx +143 -0
- manigent/web/frontend/src/components/computer/computerCopy.test.ts +322 -0
- manigent/web/frontend/src/components/computer/computerCopy.ts +413 -0
- manigent/web/frontend/src/components/computer/sections.ts +21 -0
- manigent/web/frontend/src/components/computer/terminalProtocol.test.ts +135 -0
- manigent/web/frontend/src/components/computer/terminalProtocol.ts +170 -0
- manigent/web/frontend/src/components/context/ContextGuide.test.tsx +26 -0
- manigent/web/frontend/src/components/context/ContextGuide.tsx +144 -0
- manigent/web/frontend/src/components/context/ContextLanding.tsx +46 -0
- manigent/web/frontend/src/components/desk/CardBand.a11y.test.tsx +138 -0
- manigent/web/frontend/src/components/desk/CardBand.tsx +132 -0
- manigent/web/frontend/src/components/desk/DashTile.tsx +81 -0
- manigent/web/frontend/src/components/desk/DeskAgentPicker.retained.test.tsx +79 -0
- manigent/web/frontend/src/components/desk/DeskAgentPicker.tsx +151 -0
- manigent/web/frontend/src/components/desk/DeskComposer.detachTodo.test.tsx +233 -0
- manigent/web/frontend/src/components/desk/DeskComposer.dispatch.test.tsx +208 -0
- manigent/web/frontend/src/components/desk/DeskComposer.loadTodoDraft.test.tsx +242 -0
- manigent/web/frontend/src/components/desk/DeskComposer.restage.test.tsx +258 -0
- manigent/web/frontend/src/components/desk/DeskComposer.saveTodo.test.tsx +265 -0
- manigent/web/frontend/src/components/desk/DeskComposer.tsx +828 -0
- manigent/web/frontend/src/components/desk/DeskPage.crossHighlight.test.tsx +427 -0
- manigent/web/frontend/src/components/desk/DeskPage.link.test.tsx +271 -0
- manigent/web/frontend/src/components/desk/DeskPage.tsx +1066 -0
- manigent/web/frontend/src/components/desk/DeskPage.welcomeTour.test.tsx +262 -0
- manigent/web/frontend/src/components/desk/DeskSideRail.tsx +100 -0
- manigent/web/frontend/src/components/desk/DeskWelcomeTour.tsx +513 -0
- manigent/web/frontend/src/components/desk/DeskZeroState.tsx +45 -0
- manigent/web/frontend/src/components/desk/LabelChip.tsx +147 -0
- manigent/web/frontend/src/components/desk/LabelFilter.tsx +474 -0
- manigent/web/frontend/src/components/desk/LabelInput.test.tsx +439 -0
- manigent/web/frontend/src/components/desk/LabelInput.tsx +477 -0
- manigent/web/frontend/src/components/desk/LabelManageSheet.test.tsx +384 -0
- manigent/web/frontend/src/components/desk/LabelManageSheet.tsx +498 -0
- manigent/web/frontend/src/components/desk/PhaseRail.test.tsx +170 -0
- manigent/web/frontend/src/components/desk/PhaseRail.tsx +120 -0
- manigent/web/frontend/src/components/desk/PlanBand.test.tsx +655 -0
- manigent/web/frontend/src/components/desk/PlanBand.tsx +216 -0
- manigent/web/frontend/src/components/desk/SaveTodoModal.tsx +126 -0
- manigent/web/frontend/src/components/desk/SopEditor.tsx +280 -0
- manigent/web/frontend/src/components/desk/SopItem.tsx +171 -0
- manigent/web/frontend/src/components/desk/SopRail.test.tsx +517 -0
- manigent/web/frontend/src/components/desk/SopRail.tsx +250 -0
- manigent/web/frontend/src/components/desk/TicketChip.tsx +51 -0
- manigent/web/frontend/src/components/desk/TodoItem.attachments.test.tsx +96 -0
- manigent/web/frontend/src/components/desk/TodoItem.menu.test.tsx +142 -0
- manigent/web/frontend/src/components/desk/TodoItem.tsx +573 -0
- manigent/web/frontend/src/components/desk/TodoRail.longpress.test.tsx +396 -0
- manigent/web/frontend/src/components/desk/TodoRail.test.tsx +1155 -0
- manigent/web/frontend/src/components/desk/TodoRail.tsx +918 -0
- manigent/web/frontend/src/components/desk/TodoRowMenu.tsx +147 -0
- manigent/web/frontend/src/components/desk/UpdateCard.bands.test.tsx +308 -0
- manigent/web/frontend/src/components/desk/UpdateCard.sendBack.test.tsx +444 -0
- manigent/web/frontend/src/components/desk/UpdateCard.tsx +943 -0
- manigent/web/frontend/src/components/desk/UpdateCard.untrusted.test.tsx +161 -0
- manigent/web/frontend/src/components/desk/desk.css +1921 -0
- manigent/web/frontend/src/components/desk/desk.css.test.ts +582 -0
- manigent/web/frontend/src/components/desk/deskBands.test.ts +389 -0
- manigent/web/frontend/src/components/desk/deskBands.ts +226 -0
- manigent/web/frontend/src/components/desk/m16Surfaces.test.ts +134 -0
- manigent/web/frontend/src/components/desk/sopSyntax.ts +73 -0
- manigent/web/frontend/src/components/discovery/AgentDirectory.tsx +71 -0
- manigent/web/frontend/src/components/discovery/AgentFilterBar.tsx +46 -0
- manigent/web/frontend/src/components/discovery/AgentSearchBar.tsx +56 -0
- manigent/web/frontend/src/components/dm/AgentSummaryCard.tsx +154 -0
- manigent/web/frontend/src/components/dm/ComposeFields.tsx +145 -0
- manigent/web/frontend/src/components/dm/DMChatView.flow.test.tsx +374 -0
- manigent/web/frontend/src/components/dm/DMChatView.tsx +841 -0
- manigent/web/frontend/src/components/dm/DMZeroState.tsx +204 -0
- manigent/web/frontend/src/components/dm/DmComposeModal.tsx +301 -0
- manigent/web/frontend/src/components/dm/PersonalizeModal.tsx +366 -0
- manigent/web/frontend/src/components/dm/index.ts +1 -0
- manigent/web/frontend/src/components/files/FileItem.tsx +138 -0
- manigent/web/frontend/src/components/files/FileList.outside.test.tsx +29 -0
- manigent/web/frontend/src/components/files/FileList.tsx +48 -0
- manigent/web/frontend/src/components/files/FilePreview.test.tsx +75 -0
- manigent/web/frontend/src/components/files/FilePreview.tsx +325 -0
- manigent/web/frontend/src/components/files/FileViewerModal.test.tsx +157 -0
- manigent/web/frontend/src/components/files/FileViewerModal.tsx +261 -0
- manigent/web/frontend/src/components/files/HighlightedCode.tsx +39 -0
- manigent/web/frontend/src/components/files/SaveToAgentPanel.test.tsx +156 -0
- manigent/web/frontend/src/components/files/SaveToAgentPanel.tsx +545 -0
- manigent/web/frontend/src/components/files/index.ts +3 -0
- manigent/web/frontend/src/components/hero/AgentAvatar.tsx +66 -0
- manigent/web/frontend/src/components/hero/Button.tsx +66 -0
- manigent/web/frontend/src/components/hero/Hero.tsx +133 -0
- manigent/web/frontend/src/components/homefs/HomeFolderBrowser.test.tsx +393 -0
- manigent/web/frontend/src/components/homefs/HomeFolderBrowser.tsx +792 -0
- manigent/web/frontend/src/components/hub/BridgeDeliveryLine.test.tsx +40 -0
- manigent/web/frontend/src/components/hub/BridgeDeliveryLine.tsx +36 -0
- manigent/web/frontend/src/components/hub/HubAvatar.test.tsx +93 -0
- manigent/web/frontend/src/components/hub/HubAvatar.tsx +33 -0
- manigent/web/frontend/src/components/hub/HubChatView.test.tsx +695 -0
- manigent/web/frontend/src/components/hub/HubChatView.tsx +504 -0
- manigent/web/frontend/src/components/hub/HubContactLabel.test.tsx +92 -0
- manigent/web/frontend/src/components/hub/HubContactLabel.tsx +47 -0
- manigent/web/frontend/src/components/hub/HubDeliveryLine.tsx +49 -0
- manigent/web/frontend/src/components/hub/HubDisclosure.tsx +49 -0
- manigent/web/frontend/src/components/hub/HubEnrollmentDialog.test.tsx +144 -0
- manigent/web/frontend/src/components/hub/HubEnrollmentDialog.tsx +244 -0
- manigent/web/frontend/src/components/hub/HubMessageBody.test.tsx +26 -0
- manigent/web/frontend/src/components/hub/HubMessageBody.tsx +14 -0
- manigent/web/frontend/src/components/hub/HubOfflineConfirm.tsx +45 -0
- manigent/web/frontend/src/components/hub/HubThreadBanner.tsx +33 -0
- manigent/web/frontend/src/components/hub/HubThreadGuard.tsx +59 -0
- manigent/web/frontend/src/components/hub/RemoteAgentCard.test.tsx +50 -0
- manigent/web/frontend/src/components/hub/RemoteAgentCard.tsx +174 -0
- manigent/web/frontend/src/components/hub/RemoteAgentSection.test.tsx +446 -0
- manigent/web/frontend/src/components/hub/RemoteAgentSection.tsx +219 -0
- manigent/web/frontend/src/components/hub/ReportDialog.test.tsx +96 -0
- manigent/web/frontend/src/components/hub/ReportDialog.tsx +162 -0
- manigent/web/frontend/src/components/hub/RetainButton.test.tsx +103 -0
- manigent/web/frontend/src/components/hub/RetainButton.tsx +114 -0
- manigent/web/frontend/src/components/hub/RetainDialog.test.tsx +68 -0
- manigent/web/frontend/src/components/hub/RetainDialog.tsx +115 -0
- manigent/web/frontend/src/components/hub/RetainedList.test.tsx +94 -0
- manigent/web/frontend/src/components/hub/RetainedList.tsx +32 -0
- manigent/web/frontend/src/components/hub/RetainedRow.tsx +129 -0
- manigent/web/frontend/src/components/hub/StandInChip.test.tsx +37 -0
- manigent/web/frontend/src/components/hub/StandInChip.tsx +43 -0
- manigent/web/frontend/src/components/hub/UnusualNameChip.test.tsx +81 -0
- manigent/web/frontend/src/components/hub/UnusualNameChip.tsx +22 -0
- manigent/web/frontend/src/components/hub/hubLint.test.ts +265 -0
- manigent/web/frontend/src/components/landing/ComparisonSection.tsx +92 -0
- manigent/web/frontend/src/components/landing/ConsoleSection.tsx +402 -0
- manigent/web/frontend/src/components/landing/CrewSection.tsx +224 -0
- manigent/web/frontend/src/components/landing/FAQ.tsx +68 -0
- manigent/web/frontend/src/components/landing/Footer.tsx +58 -0
- manigent/web/frontend/src/components/landing/HowItWorks.tsx +117 -0
- manigent/web/frontend/src/components/landing/KnowledgeSection.tsx +91 -0
- manigent/web/frontend/src/components/landing/LandingPage.test.tsx +134 -0
- manigent/web/frontend/src/components/landing/LearningSection.tsx +486 -0
- manigent/web/frontend/src/components/landing/Nav.tsx +68 -0
- manigent/web/frontend/src/components/landing/Pricing.tsx +263 -0
- manigent/web/frontend/src/components/landing/Section.tsx +98 -0
- manigent/web/frontend/src/components/landing/StaffingSection.tsx +122 -0
- manigent/web/frontend/src/components/landing/WhyNowSection.tsx +92 -0
- manigent/web/frontend/src/components/landing/WhyTeamSection.tsx +73 -0
- manigent/web/frontend/src/components/landing/earlyAccess.ts +23 -0
- manigent/web/frontend/src/components/layout/AppLayout.tsx +77 -0
- manigent/web/frontend/src/components/layout/Sidebar.tsx +172 -0
- manigent/web/frontend/src/components/layout/index.ts +2 -0
- manigent/web/frontend/src/components/project/ActivityFeed.tsx +247 -0
- manigent/web/frontend/src/components/project/CreateProjectPage.tsx +543 -0
- manigent/web/frontend/src/components/project/DeleteProjectButton.tsx +39 -0
- manigent/web/frontend/src/components/project/ProjectPlanTab.phase.test.tsx +107 -0
- manigent/web/frontend/src/components/project/ProjectPlanTab.test.tsx +994 -0
- manigent/web/frontend/src/components/project/ProjectPlanTab.tsx +166 -0
- manigent/web/frontend/src/components/project/ProjectReportTab.tsx +139 -0
- manigent/web/frontend/src/components/project/ProjectView.tsx +390 -0
- manigent/web/frontend/src/components/project/index.ts +2 -0
- manigent/web/frontend/src/components/project/plan/PlanAcceptance.test.tsx +460 -0
- manigent/web/frontend/src/components/project/plan/PlanAcceptance.tsx +111 -0
- manigent/web/frontend/src/components/project/plan/PlanBlockEdit.tsx +111 -0
- manigent/web/frontend/src/components/project/plan/PlanBlockEditing.test.tsx +1132 -0
- manigent/web/frontend/src/components/project/plan/PlanBody.test.tsx +495 -0
- manigent/web/frontend/src/components/project/plan/PlanBody.tsx +992 -0
- manigent/web/frontend/src/components/project/plan/PlanCollide.tsx +254 -0
- manigent/web/frontend/src/components/project/plan/PlanCommentList.test.tsx +420 -0
- manigent/web/frontend/src/components/project/plan/PlanCommentList.tsx +391 -0
- manigent/web/frontend/src/components/project/plan/PlanDecisionNudge.test.tsx +226 -0
- manigent/web/frontend/src/components/project/plan/PlanDecisionNudge.tsx +128 -0
- manigent/web/frontend/src/components/project/plan/PlanDestructiveAccept.test.tsx +209 -0
- manigent/web/frontend/src/components/project/plan/PlanDiff.test.tsx +126 -0
- manigent/web/frontend/src/components/project/plan/PlanDiff.tsx +131 -0
- manigent/web/frontend/src/components/project/plan/PlanDiffSeries.test.tsx +500 -0
- manigent/web/frontend/src/components/project/plan/PlanDiffSeries.tsx +125 -0
- manigent/web/frontend/src/components/project/plan/PlanEditor.mounts.test.tsx +504 -0
- manigent/web/frontend/src/components/project/plan/PlanEditor.tsx +2159 -0
- manigent/web/frontend/src/components/project/plan/PlanHistory.tsx +69 -0
- manigent/web/frontend/src/components/project/plan/PlanInlineNotes.test.tsx +2398 -0
- manigent/web/frontend/src/components/project/plan/PlanNoteBase.test.tsx +215 -0
- manigent/web/frontend/src/components/project/plan/PlanNoteRow.test.tsx +562 -0
- manigent/web/frontend/src/components/project/plan/PlanNoteRow.tsx +908 -0
- manigent/web/frontend/src/components/project/plan/PlanQuotePop.tsx +254 -0
- manigent/web/frontend/src/components/project/plan/PlanStaleReconcile.test.tsx +244 -0
- manigent/web/frontend/src/components/project/plan/PlanSubmitWindow.test.tsx +281 -0
- manigent/web/frontend/src/components/project/plan/PlanTray.tsx +184 -0
- manigent/web/frontend/src/components/project/plan/PlanTrayMirror.test.tsx +242 -0
- manigent/web/frontend/src/components/project/plan/plan.css +1151 -0
- manigent/web/frontend/src/components/project/plan/planAnchor.test.tsx +459 -0
- manigent/web/frontend/src/components/project/plan/planAnchor.tsx +277 -0
- manigent/web/frontend/src/components/project/plan/planBlocks.test.tsx +470 -0
- manigent/web/frontend/src/components/project/plan/planBlocks.ts +242 -0
- manigent/web/frontend/src/components/project/plan/planContrast.test.ts +229 -0
- manigent/web/frontend/src/components/project/plan/planDiffs.test.ts +455 -0
- manigent/web/frontend/src/components/project/plan/planDiffs.ts +358 -0
- manigent/web/frontend/src/components/project/plan/planTestServer.ts +741 -0
- manigent/web/frontend/src/components/project/plan/planTransport.test.ts +160 -0
- manigent/web/frontend/src/components/project/plan/planTrayLogic.test.ts +1201 -0
- manigent/web/frontend/src/components/project/plan/planTrayLogic.ts +1164 -0
- manigent/web/frontend/src/components/publicAgents/PublicAgentsAccount.tsx +162 -0
- manigent/web/frontend/src/components/publicAgents/api.ts +55 -0
- manigent/web/frontend/src/components/publicAgents/parts.tsx +54 -0
- manigent/web/frontend/src/components/publicAgents/publicAgents.css +53 -0
- manigent/web/frontend/src/components/publicAgents/publicAgents.test.tsx +86 -0
- manigent/web/frontend/src/components/publicAgents/widget.test.ts +148 -0
- manigent/web/frontend/src/components/publicAgents/widget.ts +40 -0
- manigent/web/frontend/src/components/publicRequests/AgentDrawer.tsx +101 -0
- manigent/web/frontend/src/components/publicRequests/AgentPanel.tsx +173 -0
- manigent/web/frontend/src/components/publicRequests/AgentRankList.tsx +61 -0
- manigent/web/frontend/src/components/publicRequests/MatchRetainButton.tsx +67 -0
- manigent/web/frontend/src/components/publicRequests/ProgressBoard.tsx +74 -0
- manigent/web/frontend/src/components/publicRequests/PublicRequestsPage.tsx +57 -0
- manigent/web/frontend/src/components/publicRequests/RequestHome.tsx +99 -0
- manigent/web/frontend/src/components/publicRequests/RequestRoom.tsx +126 -0
- manigent/web/frontend/src/components/publicRequests/agentView.ts +65 -0
- manigent/web/frontend/src/components/publicRequests/publicRequests.css +209 -0
- manigent/web/frontend/src/components/publicRequests/publicRequests.test.tsx +172 -0
- manigent/web/frontend/src/components/publicRequests/requestCopy.ts +121 -0
- manigent/web/frontend/src/components/sample-request/SampleIdeaList.test.tsx +190 -0
- manigent/web/frontend/src/components/sample-request/SampleIdeaList.tsx +180 -0
- manigent/web/frontend/src/components/sample-request/SampleRequestBody.tsx +276 -0
- manigent/web/frontend/src/components/sample-request/assistantStarterSamples.ts +62 -0
- manigent/web/frontend/src/components/sample-request/sampleIdeaList.css +106 -0
- manigent/web/frontend/src/components/scheduled/CronPicker.tsx +80 -0
- manigent/web/frontend/src/components/scheduled/ScheduleMessageModal.tsx +341 -0
- manigent/web/frontend/src/components/scheduled/ScheduledMessagesList.tsx +157 -0
- manigent/web/frontend/src/components/settings/AgentSettingsPage.tsx +366 -0
- manigent/web/frontend/src/components/settings/ConnectedAccounts.tsx +277 -0
- manigent/web/frontend/src/components/settings/DefaultModelSection.test.tsx +96 -0
- manigent/web/frontend/src/components/settings/DefaultModelSection.tsx +212 -0
- manigent/web/frontend/src/components/settings/DisconnectConfirmModal.tsx +93 -0
- manigent/web/frontend/src/components/settings/FrontDeskSettingsPage.isolation.test.tsx +66 -0
- manigent/web/frontend/src/components/settings/FrontDeskSettingsPage.tsx +134 -0
- manigent/web/frontend/src/components/settings/InvitableAgentsPicker.tsx +182 -0
- manigent/web/frontend/src/components/settings/KnowledgePacksSection.tsx +867 -0
- manigent/web/frontend/src/components/settings/ModelConfigDialog.catalogDriven.test.tsx +79 -0
- manigent/web/frontend/src/components/settings/ModelConfigDialog.tsx +343 -0
- manigent/web/frontend/src/components/settings/ModelConfigSection.smartRouting.test.tsx +109 -0
- manigent/web/frontend/src/components/settings/ModelConfigSection.tsx +599 -0
- manigent/web/frontend/src/components/settings/ReflectionScheduleSection.tsx +170 -0
- manigent/web/frontend/src/components/settings/UserSettingsPage.tsx +168 -0
- manigent/web/frontend/src/components/settings/agent/AgentStatusStrip.tsx +71 -0
- manigent/web/frontend/src/components/settings/agent/BlockedDeploymentsCard.test.tsx +81 -0
- manigent/web/frontend/src/components/settings/agent/BlockedDeploymentsCard.tsx +62 -0
- manigent/web/frontend/src/components/settings/agent/ChannelIsolationLine.tsx +21 -0
- manigent/web/frontend/src/components/settings/agent/ChannelsSection.test.tsx +879 -0
- manigent/web/frontend/src/components/settings/agent/ChannelsSection.tsx +1124 -0
- manigent/web/frontend/src/components/settings/agent/EnvVarsCard.test.ts +15 -0
- manigent/web/frontend/src/components/settings/agent/EnvVarsCard.tsx +301 -0
- manigent/web/frontend/src/components/settings/agent/ExtraDirsList.tsx +85 -0
- manigent/web/frontend/src/components/settings/agent/GeneralSection.tsx +88 -0
- manigent/web/frontend/src/components/settings/agent/HostDisclaimerSlot.test.tsx +32 -0
- manigent/web/frontend/src/components/settings/agent/HostDisclaimerSlot.tsx +30 -0
- manigent/web/frontend/src/components/settings/agent/HubChannelCard.test.tsx +449 -0
- manigent/web/frontend/src/components/settings/agent/HubChannelCard.tsx +312 -0
- manigent/web/frontend/src/components/settings/agent/HubInvitableAgentsCard.test.tsx +83 -0
- manigent/web/frontend/src/components/settings/agent/HubInvitableAgentsCard.tsx +64 -0
- manigent/web/frontend/src/components/settings/agent/HubSendUsageCard.test.tsx +69 -0
- manigent/web/frontend/src/components/settings/agent/HubSendUsageCard.tsx +42 -0
- manigent/web/frontend/src/components/settings/agent/HubTurnOffDialog.tsx +88 -0
- manigent/web/frontend/src/components/settings/agent/IsolationRecheckButton.test.tsx +26 -0
- manigent/web/frontend/src/components/settings/agent/IsolationRecheckButton.tsx +68 -0
- manigent/web/frontend/src/components/settings/agent/KnowledgeSection.tsx +68 -0
- manigent/web/frontend/src/components/settings/agent/LineChannelCard.test.tsx +428 -0
- manigent/web/frontend/src/components/settings/agent/LineChannelCard.tsx +343 -0
- manigent/web/frontend/src/components/settings/agent/RuntimeSection.test.tsx +164 -0
- manigent/web/frontend/src/components/settings/agent/RuntimeSection.tsx +118 -0
- manigent/web/frontend/src/components/settings/agent/StorageSection.tsx +78 -0
- manigent/web/frontend/src/components/settings/agent/WebChatChannelCard.test.tsx +253 -0
- manigent/web/frontend/src/components/settings/agent/WebChatChannelCard.tsx +401 -0
- manigent/web/frontend/src/components/settings/agent/WhatsAppChannelCard.test.tsx +462 -0
- manigent/web/frontend/src/components/settings/agent/WhatsAppChannelCard.tsx +289 -0
- manigent/web/frontend/src/components/settings/agent/agentSettings.test.tsx +109 -0
- manigent/web/frontend/src/components/settings/agent/channelSetupGuides.test.tsx +162 -0
- manigent/web/frontend/src/components/settings/agent/channelSetupGuides.tsx +119 -0
- manigent/web/frontend/src/components/settings/agent/channelsShared.ts +83 -0
- manigent/web/frontend/src/components/settings/agent/fields.tsx +67 -0
- manigent/web/frontend/src/components/settings/agent/sections.ts +36 -0
- manigent/web/frontend/src/components/settings/agent/useAgentSettingsDraft.ts +129 -0
- manigent/web/frontend/src/components/settings/agent/useInstallSelection.ts +38 -0
- manigent/web/frontend/src/components/settings/primitives.tsx +98 -0
- manigent/web/frontend/src/components/shared/ExpandToggle.tsx +45 -0
- manigent/web/frontend/src/components/shared/MarkdownBody.tsx +218 -0
- manigent/web/frontend/src/components/shared/MarkdownBody.untrusted.test.tsx +97 -0
- manigent/web/frontend/src/components/shared/RenderBody.tsx +90 -0
- manigent/web/frontend/src/components/shared/rehypeHideComments.test.tsx +115 -0
- manigent/web/frontend/src/components/shared/rehypeHideComments.ts +124 -0
- manigent/web/frontend/src/components/sharing/AcceptSharePage.tsx +141 -0
- manigent/web/frontend/src/components/sharing/ShareLandingPage.tsx +89 -0
- manigent/web/frontend/src/components/sidebar/AccountChip.test.tsx +337 -0
- manigent/web/frontend/src/components/sidebar/AccountChip.tsx +407 -0
- manigent/web/frontend/src/components/sidebar/ActiveWorkDot.tsx +9 -0
- manigent/web/frontend/src/components/sidebar/AgentProfile.tsx +44 -0
- manigent/web/frontend/src/components/sidebar/AgentRow.tsx +71 -0
- manigent/web/frontend/src/components/sidebar/AgentsPanel.retained.test.tsx +105 -0
- manigent/web/frontend/src/components/sidebar/AgentsPanel.tsx +231 -0
- manigent/web/frontend/src/components/sidebar/AssistantHero.tsx +48 -0
- manigent/web/frontend/src/components/sidebar/ChatroomItem.tsx +48 -0
- manigent/web/frontend/src/components/sidebar/CommandPalette.channelThreads.test.tsx +76 -0
- manigent/web/frontend/src/components/sidebar/CommandPalette.reopen.test.tsx +206 -0
- manigent/web/frontend/src/components/sidebar/CommandPalette.test.tsx +725 -0
- manigent/web/frontend/src/components/sidebar/CommandPalette.tsx +774 -0
- manigent/web/frontend/src/components/sidebar/ComputersPanel.tsx +144 -0
- manigent/web/frontend/src/components/sidebar/DmIcon.tsx +46 -0
- manigent/web/frontend/src/components/sidebar/DmsPanel.tsx +226 -0
- manigent/web/frontend/src/components/sidebar/FrontDeskPanel.channels.test.tsx +582 -0
- manigent/web/frontend/src/components/sidebar/FrontDeskPanel.test.tsx +151 -0
- manigent/web/frontend/src/components/sidebar/FrontDeskPanel.tsx +454 -0
- manigent/web/frontend/src/components/sidebar/HotkeysHelp.tsx +101 -0
- manigent/web/frontend/src/components/sidebar/MessageHitRow.test.tsx +84 -0
- manigent/web/frontend/src/components/sidebar/MessageHitRow.tsx +57 -0
- manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.crossHighlight.test.tsx +247 -0
- manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.delete.test.tsx +273 -0
- manigent/web/frontend/src/components/sidebar/MyDeskRailPanel.tsx +351 -0
- manigent/web/frontend/src/components/sidebar/PanelShell.tsx +48 -0
- manigent/web/frontend/src/components/sidebar/ProjectItem.tsx +161 -0
- manigent/web/frontend/src/components/sidebar/ProjectRow.tsx +151 -0
- manigent/web/frontend/src/components/sidebar/ProjectsPanel.tsx +80 -0
- manigent/web/frontend/src/components/sidebar/PublicPanel.counterpart.test.tsx +85 -0
- manigent/web/frontend/src/components/sidebar/PublicPanel.hub.test.tsx +147 -0
- manigent/web/frontend/src/components/sidebar/PublicPanel.tsx +271 -0
- manigent/web/frontend/src/components/sidebar/RecencyHeader.tsx +17 -0
- manigent/web/frontend/src/components/sidebar/ScheduledPanel.tsx +58 -0
- manigent/web/frontend/src/components/sidebar/SidebarAvatar.tsx +128 -0
- manigent/web/frontend/src/components/sidebar/SidebarRail.tsx +163 -0
- manigent/web/frontend/src/components/sidebar/TeamGroup.tsx +67 -0
- manigent/web/frontend/src/components/sidebar/dmPrefill.test.ts +69 -0
- manigent/web/frontend/src/components/sidebar/dmPrefill.ts +80 -0
- manigent/web/frontend/src/components/sidebar/index.ts +18 -0
- manigent/web/frontend/src/components/sidebar/railShortcuts.ts +18 -0
- manigent/web/frontend/src/components/today/lib.test.ts +70 -0
- manigent/web/frontend/src/components/today/lib.ts +296 -0
- manigent/web/frontend/src/components/today/today.css +113 -0
- manigent/web/frontend/src/components/welcome/RunnerSetup.css +253 -0
- manigent/web/frontend/src/components/welcome/RunnerSetup.tsx +148 -0
- manigent/web/frontend/src/components/welcome/WelcomePage.css +383 -0
- manigent/web/frontend/src/components/welcome/WelcomePage.tsx +329 -0
- manigent/web/frontend/src/components/welcome/welcomeCopy.test.ts +382 -0
- manigent/web/frontend/src/components/welcome/welcomeCopy.ts +313 -0
- manigent/web/frontend/src/config/llmProviders.test.ts +161 -0
- manigent/web/frontend/src/config/llmProviders.ts +109 -0
- manigent/web/frontend/src/config/providerIds.fixture.json +13 -0
- manigent/web/frontend/src/hooks/accountNavigation.test.tsx +138 -0
- manigent/web/frontend/src/hooks/index.ts +10 -0
- manigent/web/frontend/src/hooks/useActiveWorkBootstrap.ts +86 -0
- manigent/web/frontend/src/hooks/useAgentAccess.test.tsx +30 -0
- manigent/web/frontend/src/hooks/useAgentAccess.ts +204 -0
- manigent/web/frontend/src/hooks/useAgentSampleRequests.ts +27 -0
- manigent/web/frontend/src/hooks/useAgentSearch.ts +10 -0
- manigent/web/frontend/src/hooks/useAgents.ts +46 -0
- manigent/web/frontend/src/hooks/useAnnouncements.test.ts +112 -0
- manigent/web/frontend/src/hooks/useAnnouncements.ts +100 -0
- manigent/web/frontend/src/hooks/useAssistantAgent.ts +24 -0
- manigent/web/frontend/src/hooks/useAutoResizeTextarea.ts +18 -0
- manigent/web/frontend/src/hooks/useBridgeDeliveries.test.tsx +79 -0
- manigent/web/frontend/src/hooks/useBriefTabs.ts +122 -0
- manigent/web/frontend/src/hooks/useChannelThreads.ts +69 -0
- manigent/web/frontend/src/hooks/useComposeForm.ts +80 -0
- manigent/web/frontend/src/hooks/useComputers.ts +123 -0
- manigent/web/frontend/src/hooks/useContextFirstItem.ts +118 -0
- manigent/web/frontend/src/hooks/useCreateProject.ts +78 -0
- manigent/web/frontend/src/hooks/useDebouncedValue.ts +16 -0
- manigent/web/frontend/src/hooks/useDeleteProject.ts +27 -0
- manigent/web/frontend/src/hooks/useDeskDispatch.test.tsx +170 -0
- manigent/web/frontend/src/hooks/useDeskDispatch.ts +135 -0
- manigent/web/frontend/src/hooks/useDeskReadState.test.tsx +85 -0
- manigent/web/frontend/src/hooks/useDeskReadState.ts +150 -0
- manigent/web/frontend/src/hooks/useDeskRecentlyDone.test.tsx +222 -0
- manigent/web/frontend/src/hooks/useDeskRecentlyDone.ts +225 -0
- manigent/web/frontend/src/hooks/useDeskTour.ts +37 -0
- manigent/web/frontend/src/hooks/useDeskUpdates.channelThreads.test.tsx +91 -0
- manigent/web/frontend/src/hooks/useDeskUpdates.hubThreads.test.tsx +93 -0
- manigent/web/frontend/src/hooks/useDeskUpdates.ts +353 -0
- manigent/web/frontend/src/hooks/useDisconnectIntegration.ts +23 -0
- manigent/web/frontend/src/hooks/useDmThreads.ts +99 -0
- manigent/web/frontend/src/hooks/useFileDropzone.ts +93 -0
- manigent/web/frontend/src/hooks/useFileUpload.ts +38 -0
- manigent/web/frontend/src/hooks/useFiles.ts +98 -0
- manigent/web/frontend/src/hooks/useHub.test.tsx +237 -0
- manigent/web/frontend/src/hooks/useHub.ts +502 -0
- manigent/web/frontend/src/hooks/useInstall.ts +82 -0
- manigent/web/frontend/src/hooks/useLabels.test.tsx +294 -0
- manigent/web/frontend/src/hooks/useLabels.ts +287 -0
- manigent/web/frontend/src/hooks/useLogout.ts +49 -0
- manigent/web/frontend/src/hooks/useMentionDetection.ts +90 -0
- manigent/web/frontend/src/hooks/useMessageAttachments.deskLimit.test.ts +264 -0
- manigent/web/frontend/src/hooks/useMessageAttachments.ts +283 -0
- manigent/web/frontend/src/hooks/useMessageDeepLink.test.tsx +70 -0
- manigent/web/frontend/src/hooks/useMessageDeepLink.ts +32 -0
- manigent/web/frontend/src/hooks/useMessageSearch.ts +78 -0
- manigent/web/frontend/src/hooks/useModelConfigs.ts +178 -0
- manigent/web/frontend/src/hooks/useMyAgents.ts +18 -0
- manigent/web/frontend/src/hooks/usePointerReorder.ts +270 -0
- manigent/web/frontend/src/hooks/usePrefersReducedMotion.ts +18 -0
- manigent/web/frontend/src/hooks/useProjectPlan.ts +36 -0
- manigent/web/frontend/src/hooks/useProjectReport.ts +19 -0
- manigent/web/frontend/src/hooks/useProjects.ts +104 -0
- manigent/web/frontend/src/hooks/useProjectsWithChatrooms.ts +97 -0
- manigent/web/frontend/src/hooks/usePublicRequests.ts +89 -0
- manigent/web/frontend/src/hooks/useRecentAgents.ts +38 -0
- manigent/web/frontend/src/hooks/useRenameProject.test.tsx +185 -0
- manigent/web/frontend/src/hooks/useRenameProject.ts +192 -0
- manigent/web/frontend/src/hooks/useRoomTrust.ts +56 -0
- manigent/web/frontend/src/hooks/useSharing.ts +46 -0
- manigent/web/frontend/src/hooks/useSopRecents.ts +55 -0
- manigent/web/frontend/src/hooks/useSopSchedules.ts +25 -0
- manigent/web/frontend/src/hooks/useSops.ts +99 -0
- manigent/web/frontend/src/hooks/useSwitchAccount.ts +41 -0
- manigent/web/frontend/src/hooks/useTeamGroups.ts +48 -0
- manigent/web/frontend/src/hooks/useThreadOutbox.ts +68 -0
- manigent/web/frontend/src/hooks/useTodos.add.test.tsx +89 -0
- manigent/web/frontend/src/hooks/useTodos.linkProject.test.tsx +115 -0
- manigent/web/frontend/src/hooks/useTodos.saveDraft.test.tsx +188 -0
- manigent/web/frontend/src/hooks/useTodos.ts +396 -0
- manigent/web/frontend/src/hooks/useUntrustedBodies.test.tsx +87 -0
- manigent/web/frontend/src/hooks/useUntrustedBodies.ts +51 -0
- manigent/web/frontend/src/hooks/useWebSocket.briefTab.test.tsx +148 -0
- manigent/web/frontend/src/hooks/useWebSocket.channelThreads.test.tsx +82 -0
- manigent/web/frontend/src/hooks/useWebSocket.hostSync.test.tsx +92 -0
- manigent/web/frontend/src/hooks/useWebSocket.hub.test.tsx +114 -0
- manigent/web/frontend/src/hooks/useWebSocket.labelSync.test.tsx +168 -0
- manigent/web/frontend/src/hooks/useWebSocket.plan.test.tsx +168 -0
- manigent/web/frontend/src/hooks/useWebSocket.reconnect.test.tsx +166 -0
- manigent/web/frontend/src/hooks/useWebSocket.runnerVersions.test.tsx +167 -0
- manigent/web/frontend/src/hooks/useWebSocket.ts +495 -0
- manigent/web/frontend/src/hooks/useWindowFileDropGuard.ts +33 -0
- manigent/web/frontend/src/landing.tsx +11 -0
- manigent/web/frontend/src/main.tsx +17 -0
- manigent/web/frontend/src/private-beta.tsx +10 -0
- manigent/web/frontend/src/public.tsx +42 -0
- manigent/web/frontend/src/queryClient.ts +16 -0
- manigent/web/frontend/src/stores/activeWorkStore.ts +88 -0
- manigent/web/frontend/src/stores/announcementStore.test.ts +217 -0
- manigent/web/frontend/src/stores/announcementStore.ts +169 -0
- manigent/web/frontend/src/stores/authStore.multiSession.test.ts +304 -0
- manigent/web/frontend/src/stores/authStore.test.ts +235 -0
- manigent/web/frontend/src/stores/authStore.ts +693 -0
- manigent/web/frontend/src/stores/deskLabelViewStore.test.ts +162 -0
- manigent/web/frontend/src/stores/deskLabelViewStore.ts +234 -0
- manigent/web/frontend/src/stores/deskLinkStore.ts +54 -0
- manigent/web/frontend/src/stores/deskStore.test.ts +53 -0
- manigent/web/frontend/src/stores/deskStore.ts +93 -0
- manigent/web/frontend/src/stores/heldPostsStore.ts +78 -0
- manigent/web/frontend/src/stores/mcpAuthStore.ts +52 -0
- manigent/web/frontend/src/stores/modelSelectionStore.ts +33 -0
- manigent/web/frontend/src/stores/perUserStores.ts +79 -0
- manigent/web/frontend/src/stores/runnerVersionStore.test.ts +81 -0
- manigent/web/frontend/src/stores/runnerVersionStore.ts +60 -0
- manigent/web/frontend/src/stores/skillAuthStore.ts +54 -0
- manigent/web/frontend/src/stores/switchReset.test.ts +236 -0
- manigent/web/frontend/src/stores/tokenExp.ts +32 -0
- manigent/web/frontend/src/stores/uiStore.ts +186 -0
- manigent/web/frontend/src/stores/unreadStore.ts +204 -0
- manigent/web/frontend/src/stores/unsavedGuard.ts +22 -0
- manigent/web/frontend/src/styles/design/fonts/JetBrainsMono-Latin.woff2 +0 -0
- manigent/web/frontend/src/styles/design/fonts/Nunito-Latin.woff2 +0 -0
- manigent/web/frontend/src/styles/design/fonts/Nunito-LatinExt.woff2 +0 -0
- manigent/web/frontend/src/styles/design/tokens.css +160 -0
- manigent/web/frontend/src/styles/homepage.css +242 -0
- manigent/web/frontend/src/styles/index.css +126 -0
- manigent/web/frontend/src/styles/landing.css +52 -0
- manigent/web/frontend/src/test/authFixtures.ts +154 -0
- manigent/web/frontend/src/test/fetchHarness.ts +203 -0
- manigent/web/frontend/src/test/fixtures/channelThreads.smoke.json +42 -0
- manigent/web/frontend/src/test/hubFixtures.ts +212 -0
- manigent/web/frontend/src/test/queryWrapper.tsx +9 -0
- manigent/web/frontend/src/test/suiteHygiene.test.ts +212 -0
- manigent/web/frontend/src/types/channelThreadName.test.ts +16 -0
- manigent/web/frontend/src/types/index.ts +903 -0
- manigent/web/frontend/src/utils/agentAccessCopy.test.ts +53 -0
- manigent/web/frontend/src/utils/agentAccessCopy.ts +231 -0
- manigent/web/frontend/src/utils/agentDisplay.ts +81 -0
- manigent/web/frontend/src/utils/briefSchedule.ts +25 -0
- manigent/web/frontend/src/utils/channelThreads.test.ts +109 -0
- manigent/web/frontend/src/utils/channelThreads.ts +179 -0
- manigent/web/frontend/src/utils/chatroomDisplay.ts +11 -0
- manigent/web/frontend/src/utils/deskLinks.test.ts +155 -0
- manigent/web/frontend/src/utils/deskLinks.ts +115 -0
- manigent/web/frontend/src/utils/deskRecipient.ts +29 -0
- manigent/web/frontend/src/utils/formatDate.ts +102 -0
- manigent/web/frontend/src/utils/frontDesk.ts +65 -0
- manigent/web/frontend/src/utils/hubChannelView.test.ts +178 -0
- manigent/web/frontend/src/utils/hubChannelView.ts +307 -0
- manigent/web/frontend/src/utils/hubDelivery.test.ts +105 -0
- manigent/web/frontend/src/utils/hubDelivery.ts +137 -0
- manigent/web/frontend/src/utils/hubDisplay.test.ts +157 -0
- manigent/web/frontend/src/utils/hubDisplay.ts +221 -0
- manigent/web/frontend/src/utils/hubThread.test.ts +184 -0
- manigent/web/frontend/src/utils/hubThread.ts +247 -0
- manigent/web/frontend/src/utils/labelErrors.test.ts +127 -0
- manigent/web/frontend/src/utils/labelErrors.ts +123 -0
- manigent/web/frontend/src/utils/labelView.test.ts +846 -0
- manigent/web/frontend/src/utils/labelView.ts +760 -0
- manigent/web/frontend/src/utils/legalUrls.ts +11 -0
- manigent/web/frontend/src/utils/messageSender.test.ts +32 -0
- manigent/web/frontend/src/utils/messageSender.ts +29 -0
- manigent/web/frontend/src/utils/oauthScopeCopy.ts +73 -0
- manigent/web/frontend/src/utils/personalize.ts +49 -0
- manigent/web/frontend/src/utils/reachableAgents.test.ts +36 -0
- manigent/web/frontend/src/utils/reachableAgents.ts +24 -0
- manigent/web/frontend/src/utils/recencyBuckets.ts +61 -0
- manigent/web/frontend/src/utils/roomTrust.test.ts +94 -0
- manigent/web/frontend/src/utils/roomTrust.ts +111 -0
- manigent/web/frontend/src/utils/snippetHighlight.test.ts +63 -0
- manigent/web/frontend/src/utils/snippetHighlight.ts +42 -0
- manigent/web/frontend/src/utils/sopRecipient.ts +37 -0
- manigent/web/frontend/src/utils/sopRequest.test.ts +91 -0
- manigent/web/frontend/src/utils/sopRequest.ts +68 -0
- manigent/web/frontend/src/utils/sopSchedule.ts +26 -0
- manigent/web/frontend/src/utils/sopSection.test.ts +148 -0
- manigent/web/frontend/src/utils/sopSection.ts +143 -0
- manigent/web/frontend/src/utils/textSearch.test.ts +60 -0
- manigent/web/frontend/src/utils/textSearch.ts +43 -0
- manigent/web/frontend/src/utils/thoughtPartner.ts +26 -0
- manigent/web/frontend/src/utils/threadName.test.ts +131 -0
- manigent/web/frontend/src/utils/threadName.ts +122 -0
- manigent/web/frontend/src/utils/ticketView.test.ts +296 -0
- manigent/web/frontend/src/utils/ticketView.ts +175 -0
- manigent/web/frontend/src/utils/todoAttachments.desiredState.test.ts +180 -0
- manigent/web/frontend/src/utils/todoAttachments.ts +126 -0
- manigent/web/frontend/src/utils/todoDraft.test.ts +84 -0
- manigent/web/frontend/src/utils/todoDraft.ts +46 -0
- manigent/web/frontend/src/utils/todoSearch.test.ts +82 -0
- manigent/web/frontend/src/utils/todoSearch.ts +43 -0
- manigent/web/frontend/src/utils/versionMatch.test.ts +295 -0
- manigent/web/frontend/src/utils/versionMatch.ts +245 -0
- manigent/web/frontend/src/vite-env.d.ts +1 -0
- manigent/web/frontend/tailwind.config.js +86 -0
- manigent/web/frontend/tos.html +188 -0
- manigent/web/frontend/tsconfig.dts.json +25 -0
- manigent/web/frontend/tsconfig.json +26 -0
- manigent/web/frontend/tsconfig.node.json +11 -0
- manigent/web/frontend/vite.config.ts +41 -0
- manigent/web/frontend/vitest.config.ts +16 -0
- manigent/ws_edge/app.py +514 -0
- manigent/ws_edge/auth.py +104 -0
- manigent/ws_edge/link.py +282 -0
- manigent/ws_edge/registry.py +206 -0
- manigent/ws_edge/terminal_relay.py +269 -0
- manigent_server-0.1.0.dist-info/METADATA +138 -0
- manigent_server-0.1.0.dist-info/RECORD +1004 -0
- manigent_server-0.1.0.dist-info/WHEEL +4 -0
- manigent_server-0.1.0.dist-info/entry_points.txt +2 -0
- manigent_server-0.1.0.dist-info/licenses/LICENSE +68 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import type { DeskLabel } from '../api/endpoints/deskLabels'
|
|
3
|
+
import type { DeskTodo } from '../api/endpoints/deskTodos'
|
|
4
|
+
import {
|
|
5
|
+
CHIP_CAP,
|
|
6
|
+
RESIDUAL_KEY,
|
|
7
|
+
chipsFor,
|
|
8
|
+
cycleColor,
|
|
9
|
+
diagnoseRegistry,
|
|
10
|
+
displayName,
|
|
11
|
+
effectiveGroup,
|
|
12
|
+
groupTodos,
|
|
13
|
+
labelCounts,
|
|
14
|
+
matches,
|
|
15
|
+
nextColor,
|
|
16
|
+
normalizeSlug,
|
|
17
|
+
reconcileView,
|
|
18
|
+
registryWritable,
|
|
19
|
+
resolveLabel,
|
|
20
|
+
toRegistry,
|
|
21
|
+
unknownAxisReason,
|
|
22
|
+
unregisteredSlugs,
|
|
23
|
+
type LabelView,
|
|
24
|
+
type RegistryState,
|
|
25
|
+
} from './labelView'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* THE PORT OF THE MOCK'S BEHAVIOUR CHECKS.
|
|
29
|
+
*
|
|
30
|
+
* These are the acceptance target for the whole feature, and they run as unit
|
|
31
|
+
* tests rather than DOM tests only because the projection layer was extracted
|
|
32
|
+
* pure — which was the point of extracting it.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** ONE KIND. The `kind` parameter is gone with the state axis, and the fixture
|
|
36
|
+
* below keeps all nine rows rather than dropping four — which is the point of
|
|
37
|
+
* the edit rather than a convenience. An owner who minted Next / Wait For /
|
|
38
|
+
* Scheduled / Some Day still HAS those rows: nothing on disk is deleted, the
|
|
39
|
+
* server coerces a stored `state` row to a context on read, and the browser
|
|
40
|
+
* must render them as ordinary contexts. This fixture is that owner. */
|
|
41
|
+
const L = (slug: string, color: DeskLabel['color'], name = slug): DeskLabel => ({
|
|
42
|
+
slug,
|
|
43
|
+
name,
|
|
44
|
+
kind: 'context',
|
|
45
|
+
color,
|
|
46
|
+
created_at: '1970-01-01T00:00:00+00:00',
|
|
47
|
+
updated_at: '1970-01-01T00:00:00+00:00',
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
/** The mock's nine-label fixture, in its array order. The last four arrived as
|
|
51
|
+
* `kind: "state"` before the retirement and arrive as contexts now. */
|
|
52
|
+
const LABELS: DeskLabel[] = [
|
|
53
|
+
L('office', 'blue'),
|
|
54
|
+
L('home', 'green'),
|
|
55
|
+
L('phone', 'teal'),
|
|
56
|
+
L('computer', 'pink'),
|
|
57
|
+
L('errands', 'plum'),
|
|
58
|
+
L('next', 'indigo', 'Next'),
|
|
59
|
+
L('wait-for', 'amber', 'Wait For'),
|
|
60
|
+
L('scheduled', 'blue', 'Scheduled'),
|
|
61
|
+
L('someday', 'slate', 'Some Day'),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
let seq = 0
|
|
65
|
+
const T = (labels: string[], over: Partial<DeskTodo> = {}): DeskTodo => ({
|
|
66
|
+
id: `t-${++seq}`,
|
|
67
|
+
owner_user_id: 'u1',
|
|
68
|
+
text: `task ${seq}`,
|
|
69
|
+
// `status` is GONE from the wire, replaced by two independent fields. All
|
|
70
|
+
// four are typed REQUIRED, which is why every fixture in the suite moved at
|
|
71
|
+
// once rather than one at a time.
|
|
72
|
+
state: 'new',
|
|
73
|
+
archived: false,
|
|
74
|
+
association_count: 0,
|
|
75
|
+
project_ids: [],
|
|
76
|
+
created_at: '',
|
|
77
|
+
updated_at: '',
|
|
78
|
+
attachments: [],
|
|
79
|
+
labels,
|
|
80
|
+
...over,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const reg = (state: RegistryState = 'ready', labels = LABELS) => toRegistry(labels, state)
|
|
84
|
+
const view = (over: Partial<LabelView> = {}): LabelView => ({
|
|
85
|
+
group: null,
|
|
86
|
+
filter: [],
|
|
87
|
+
matchAll: false,
|
|
88
|
+
states: [],
|
|
89
|
+
...over,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('resolveLabel — the one rule the feature hangs on', () => {
|
|
93
|
+
it('returns the registry row when the registry is held and knows the slug', () => {
|
|
94
|
+
// RE-POINTED, not adjusted: `next` was the fixture's canonical STATE row.
|
|
95
|
+
// After the retirement the registry returns it as a context, and the row
|
|
96
|
+
// itself — slug, name, colour, position — is otherwise untouched. That is
|
|
97
|
+
// the whole shape of the retirement in one assertion.
|
|
98
|
+
const l = resolveLabel('next', reg())
|
|
99
|
+
expect(l).toEqual({
|
|
100
|
+
slug: 'next',
|
|
101
|
+
name: 'Next',
|
|
102
|
+
kind: 'context',
|
|
103
|
+
color: 'indigo',
|
|
104
|
+
registered: true,
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('an unknown slug on a held registry is a CONTEXT and is NOT registered', () => {
|
|
109
|
+
// R2: accepted on the wire, never silent. Dashed, colourless, "+ add it".
|
|
110
|
+
expect(resolveLabel('offce', reg())).toEqual({
|
|
111
|
+
slug: 'offce',
|
|
112
|
+
name: 'offce',
|
|
113
|
+
kind: 'context',
|
|
114
|
+
color: null,
|
|
115
|
+
registered: false,
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it.each(['cold', 'unavailable'] as RegistryState[])(
|
|
120
|
+
'off-ready (%s) every slug is kind:unknown and registered:TRUE',
|
|
121
|
+
(state) => {
|
|
122
|
+
// registered:true is the whole chip fix. We do not KNOW it isn't
|
|
123
|
+
// registered, so we must not say it isn't — otherwise the "not in your
|
|
124
|
+
// list" treatment lands on the owner's entire vocabulary at once.
|
|
125
|
+
for (const slug of ['office', 'next', 'offce']) {
|
|
126
|
+
const l = resolveLabel(slug, reg(state))
|
|
127
|
+
expect(l.kind).toBe('unknown')
|
|
128
|
+
expect(l.registered).toBe(true)
|
|
129
|
+
expect(l.color).toBeNull()
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
it('provisional resolves from the cached list, kinds intact', () => {
|
|
135
|
+
// kind is the registry's most stable field; a cached list groups
|
|
136
|
+
// correctly, not merely plausibly. There is one kind left, so the assertion
|
|
137
|
+
// is weaker than it was — but a cached row still resolving as REGISTERED is
|
|
138
|
+
// the half that was ever load-bearing.
|
|
139
|
+
expect(resolveLabel('next', reg('provisional')).kind).toBe('context')
|
|
140
|
+
expect(resolveLabel('next', reg('provisional')).registered).toBe(true)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('an unknown label gets NO sigil — a sigil is a claim about the axis', () => {
|
|
144
|
+
expect(displayName(resolveLabel('office', reg()))).toBe('@office')
|
|
145
|
+
// WAS `!Next`. The `!` sigil retired with the axis it named: it existed to
|
|
146
|
+
// say "this is the other kind, press ! to reach it", and there is no other
|
|
147
|
+
// kind and no second key. One kind, one sigil.
|
|
148
|
+
expect(displayName(resolveLabel('next', reg()))).toBe('@Next')
|
|
149
|
+
expect(displayName(resolveLabel('office', reg('unavailable')))).toBe('office')
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe('matches — a flat OR over the chosen slugs, or AND under matchAll', () => {
|
|
154
|
+
const office = T(['office', 'next'])
|
|
155
|
+
const home = T(['home', 'next'])
|
|
156
|
+
const both = T(['office', 'home', 'next'])
|
|
157
|
+
const officeWaiting = T(['office', 'wait-for'])
|
|
158
|
+
const bare = T([])
|
|
159
|
+
|
|
160
|
+
it('an empty filter passes everything', () => {
|
|
161
|
+
expect(matches(bare, view(), reg())).toBe(true)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('ORs across the chosen slugs', () => {
|
|
165
|
+
const v = view({ filter: ['office', 'home'] })
|
|
166
|
+
expect(matches(office, v, reg())).toBe(true)
|
|
167
|
+
expect(matches(home, v, reg())).toBe(true)
|
|
168
|
+
expect(matches(bare, v, reg())).toBe(false)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('two slugs that used to be different KINDS now simply OR', () => {
|
|
172
|
+
// THIS ASSERTION TESTED AN AXIS THAT NO LONGER EXISTS. It read "ANDs across
|
|
173
|
+
// kinds — a context filter and a state filter both bind", and it was the
|
|
174
|
+
// behaviour `kind` existed to produce. With one kind the refinement is gone
|
|
175
|
+
// and the correct answer is a flat OR — which is what is asserted here
|
|
176
|
+
// rather than deleted, because "these two no longer AND" is exactly the
|
|
177
|
+
// fact a reader six weeks from now needs.
|
|
178
|
+
//
|
|
179
|
+
// What REPLACED the narrowing it used to buy is the state filter, which is
|
|
180
|
+
// a different axis entirely and is tested in `ticketView.test.ts`
|
|
181
|
+
// ('the state term ANDs with the label filter').
|
|
182
|
+
const v = view({ filter: ['office', 'next'] })
|
|
183
|
+
expect(matches(office, v, reg())).toBe(true)
|
|
184
|
+
// carries @office; under the old kinds its `!wait-for` rejected it
|
|
185
|
+
expect(matches(officeWaiting, v, reg())).toBe(true)
|
|
186
|
+
// carries @next but not @office — an OR, so it passes
|
|
187
|
+
expect(matches(home, v, reg())).toBe(true)
|
|
188
|
+
expect(matches(bare, v, reg())).toBe(false)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('matchAll flips the WHOLE filter to strict AND', () => {
|
|
192
|
+
// Was "ONLY the context half". There is no other half.
|
|
193
|
+
const v = view({ filter: ['office', 'home'], matchAll: true })
|
|
194
|
+
expect(matches(both, v, reg())).toBe(true)
|
|
195
|
+
expect(matches(office, v, reg())).toBe(false)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('matchAll now binds two slugs that used to be exempt from it', () => {
|
|
199
|
+
// The deleted assertion here was "matchAll leaves the state half as OR —
|
|
200
|
+
// AND across states is unsatisfiable", which was true because a to-do
|
|
201
|
+
// carried at most one state. `next` and `wait-for` are ordinary contexts
|
|
202
|
+
// now, a to-do may carry both, and AND over them is satisfiable — so the
|
|
203
|
+
// exemption is not merely unnecessary, it would be wrong.
|
|
204
|
+
//
|
|
205
|
+
// The unsatisfiable-AND argument DID survive, on the axis that still has
|
|
206
|
+
// the property: a to-do has exactly one ticket state, which is why the
|
|
207
|
+
// state filter has no ALL/AND toggle. Asserted in `ticketView.test.ts`.
|
|
208
|
+
const v = view({ filter: ['next', 'wait-for'], matchAll: true })
|
|
209
|
+
expect(matches(officeWaiting, v, reg())).toBe(false)
|
|
210
|
+
expect(matches(T(['next', 'wait-for']), v, reg())).toBe(true)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('20/5 — the fixture-scale check the mock grades on, on one axis now', () => {
|
|
214
|
+
// 20 items carry @computer; 5 of those also carry @wait-for.
|
|
215
|
+
const plate = [
|
|
216
|
+
...Array.from({ length: 15 }, () => T(['computer', 'next'])),
|
|
217
|
+
...Array.from({ length: 5 }, () => T(['computer', 'wait-for'])),
|
|
218
|
+
...Array.from({ length: 10 }, () => T(['home', 'next'])),
|
|
219
|
+
]
|
|
220
|
+
const ctxOnly = plate.filter((t) => matches(t, view({ filter: ['computer'] }), reg()))
|
|
221
|
+
expect(ctxOnly).toHaveLength(20)
|
|
222
|
+
// WAS 5, by ANDing across the two kinds. One kind means a flat OR, so the
|
|
223
|
+
// second slug can only ever WIDEN — here to the 20 carrying @computer, all
|
|
224
|
+
// five @wait-for rows already being among them. The way back to 5 is
|
|
225
|
+
// matchAll, asserted below, which is a control the owner can actually see.
|
|
226
|
+
const across = plate.filter((t) =>
|
|
227
|
+
matches(t, view({ filter: ['computer', 'wait-for'] }), reg()),
|
|
228
|
+
)
|
|
229
|
+
expect(across).toHaveLength(20)
|
|
230
|
+
// …and the widening is real where the sets differ: @home ∪ @wait-for.
|
|
231
|
+
const wider = plate.filter((t) => matches(t, view({ filter: ['home', 'wait-for'] }), reg()))
|
|
232
|
+
expect(wider).toHaveLength(15)
|
|
233
|
+
const strict = plate.filter((t) =>
|
|
234
|
+
matches(t, view({ filter: ['computer', 'wait-for'], matchAll: true }), reg()),
|
|
235
|
+
)
|
|
236
|
+
expect(strict).toHaveLength(5)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('off-ready it degrades to a FLAT OR — and does NOT become pass-everything', () => {
|
|
240
|
+
// The negative is the important half: with kind unreadable both partitions
|
|
241
|
+
// come back empty, every predicate passes, and the filter silently stops
|
|
242
|
+
// filtering while the chip still reads "Filter 2".
|
|
243
|
+
const v = view({ filter: ['office', 'next'] })
|
|
244
|
+
expect(matches(office, v, reg('unavailable'))).toBe(true)
|
|
245
|
+
expect(matches(officeWaiting, v, reg('unavailable'))).toBe(true)
|
|
246
|
+
expect(matches(bare, v, reg('unavailable'))).toBe(false)
|
|
247
|
+
expect(matches(T(['home']), v, reg('unavailable'))).toBe(false)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('off-ready ignores matchAll — there is no context partition to strengthen', () => {
|
|
251
|
+
const v = view({ filter: ['office', 'home'], matchAll: true })
|
|
252
|
+
expect(matches(office, v, reg('cold'))).toBe(true)
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
describe('chipsFor — order, pinning and the +N cap', () => {
|
|
257
|
+
it('registered contexts in REGISTRY order', () => {
|
|
258
|
+
// WAS "state first, then registered contexts": the state arm of the
|
|
259
|
+
// ordering is gone, so a row is one run in registry array position. `next`
|
|
260
|
+
// sits at index 5 and now sorts AFTER `office` (0) and `errands` (4) rather
|
|
261
|
+
// than jumping the queue for being a state.
|
|
262
|
+
//
|
|
263
|
+
// The ticket chip did not take over that leading position through this
|
|
264
|
+
// function — it cannot reach it. `TicketChip` imports neither this module
|
|
265
|
+
// nor `LabelChip`, so it is never a member of `show`, is never counted
|
|
266
|
+
// toward CHIP_CAP, and never falls under `+N`. Its leading position is
|
|
267
|
+
// rendered by `TodoItem`, outside this array.
|
|
268
|
+
const t = T(['errands', 'office', 'next'])
|
|
269
|
+
const { show } = chipsFor(t, view(), reg())
|
|
270
|
+
expect(show.map((l) => l.slug)).toEqual(['office', 'errands', 'next'])
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('unregistered contexts sort LAST so the row keeps its colour rhythm', () => {
|
|
274
|
+
const t = T(['offce', 'office', 'next'])
|
|
275
|
+
const { show } = chipsFor(t, view(), reg())
|
|
276
|
+
expect(show.map((l) => l.slug)).toEqual(['office', 'next', 'offce'])
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('a filtered label PINS to the front — a filter never hides why a row matched', () => {
|
|
280
|
+
const t = T(['office', 'computer', 'next'])
|
|
281
|
+
const { show } = chipsFor(t, view({ filter: ['computer'] }), reg())
|
|
282
|
+
expect(show[0].slug).toBe('computer')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('over the cap it shows CAP-1 and reports the rest', () => {
|
|
286
|
+
// "Sort out the standing desk cable mess" — five labels, the mock's
|
|
287
|
+
// overflow row.
|
|
288
|
+
const t = T(['office', 'home', 'errands', 'computer', 'someday'])
|
|
289
|
+
const { show, more } = chipsFor(t, view(), reg())
|
|
290
|
+
expect(show).toHaveLength(CHIP_CAP - 1)
|
|
291
|
+
expect(more).toBe(5 - (CHIP_CAP - 1))
|
|
292
|
+
// `someday` led this row for being a state; it is index 8 in the registry
|
|
293
|
+
// now, so the two survivors are the first two in array order. The CAP
|
|
294
|
+
// arithmetic itself is untouched — deliberately, because the ticket chip
|
|
295
|
+
// must never change it.
|
|
296
|
+
expect(show.map((l) => l.slug)).toEqual(['office', 'home'])
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('exactly CAP chips do NOT overflow', () => {
|
|
300
|
+
const { show, more } = chipsFor(T(['office', 'home', 'next']), view(), reg())
|
|
301
|
+
expect(show).toHaveLength(3)
|
|
302
|
+
expect(more).toBe(0)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('a pinned chip survives the cap — it is never truncated away', () => {
|
|
306
|
+
const t = T(['office', 'home', 'errands', 'computer', 'someday'])
|
|
307
|
+
const { show, more } = chipsFor(t, view({ filter: ['computer'] }), reg())
|
|
308
|
+
expect(show[0].slug).toBe('computer')
|
|
309
|
+
expect(more).toBe(5 - (CHIP_CAP - 1))
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('a ticket state is NOT reachable from here, at any state', () => {
|
|
313
|
+
// The containment criterion, asserted where it would break first. A row
|
|
314
|
+
// reading Working with no labels yields NO chips and NO overflow: the chip
|
|
315
|
+
// it renders is produced by `TicketChip` outside this path entirely, so
|
|
316
|
+
// nothing here can count it, cap it, or hide it under `+N`.
|
|
317
|
+
const working = T([], { state: 'working', association_count: 3 })
|
|
318
|
+
const { show, more } = chipsFor(working, view(), reg())
|
|
319
|
+
expect(show).toEqual([])
|
|
320
|
+
expect(more).toBe(0)
|
|
321
|
+
// And a row AT the cap stays at the cap: the ticket chip does not consume
|
|
322
|
+
// one of the three slots.
|
|
323
|
+
const full = T(['office', 'home', 'errands'], { state: 'completed' })
|
|
324
|
+
expect(chipsFor(full, view(), reg()).show).toHaveLength(CHIP_CAP)
|
|
325
|
+
expect(chipsFor(full, view(), reg()).more).toBe(0)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it('expanded shows everything and reports no overflow', () => {
|
|
329
|
+
const t = T(['office', 'home', 'errands', 'computer', 'someday'])
|
|
330
|
+
const { show, more } = chipsFor(t, view(), reg(), true)
|
|
331
|
+
expect(show).toHaveLength(5)
|
|
332
|
+
expect(more).toBe(0)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('off-ready the row keeps STORED order and chips do not vanish', () => {
|
|
336
|
+
// Without the off-ready branch the kind filters match neither value and
|
|
337
|
+
// every chip silently disappears from the row.
|
|
338
|
+
const t = T(['errands', 'office', 'next'])
|
|
339
|
+
const { show } = chipsFor(t, view(), reg('unavailable'))
|
|
340
|
+
expect(show.map((l) => l.slug)).toEqual(['errands', 'office', 'next'])
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
it('off-ready still pins the filtered chip first', () => {
|
|
344
|
+
const t = T(['errands', 'office', 'next'])
|
|
345
|
+
const { show } = chipsFor(t, view({ filter: ['next'] }), reg('unavailable'))
|
|
346
|
+
expect(show[0].slug).toBe('next')
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('a row with no labels yields no chips', () => {
|
|
350
|
+
expect(chipsFor(T([]), view(), reg()).show).toEqual([])
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('a row whose labels key is absent entirely does not throw', () => {
|
|
354
|
+
const legacy = { ...T([]), labels: undefined }
|
|
355
|
+
expect(chipsFor(legacy, view(), reg()).show).toEqual([])
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
describe('groupTodos — duplication, order and the residual', () => {
|
|
360
|
+
const plate = [
|
|
361
|
+
T(['office', 'next']),
|
|
362
|
+
T(['office', 'home', 'next']),
|
|
363
|
+
T(['home', 'wait-for']),
|
|
364
|
+
T([]),
|
|
365
|
+
T(['infra']),
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
it('a to-do appears under EVERY label it carries on the axis', () => {
|
|
369
|
+
const groups = groupTodos(plate, 'context', reg())
|
|
370
|
+
const office = groups.find((g) => g.key === 'office')!
|
|
371
|
+
const home = groups.find((g) => g.key === 'home')!
|
|
372
|
+
expect(office.items).toHaveLength(2)
|
|
373
|
+
expect(home.items).toHaveLength(2)
|
|
374
|
+
// the second row is in both, and it is the SAME record
|
|
375
|
+
expect(office.items[1].id).toBe(home.items[0].id)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it('group order is registry array position', () => {
|
|
379
|
+
const groups = groupTodos(plate, 'context', reg())
|
|
380
|
+
const registered = groups.filter((g) => g.tone === '').map((g) => g.key)
|
|
381
|
+
// NINE groups, not five: the four rows the owner minted as states arrive as
|
|
382
|
+
// ordinary contexts after the retirement, so they group here too — in their
|
|
383
|
+
// stored registry positions, which are unchanged. Nothing was deleted from
|
|
384
|
+
// the registry to produce this, and nothing should be.
|
|
385
|
+
expect(registered).toEqual([
|
|
386
|
+
'office',
|
|
387
|
+
'home',
|
|
388
|
+
'phone',
|
|
389
|
+
'computer',
|
|
390
|
+
'errands',
|
|
391
|
+
'next',
|
|
392
|
+
'wait-for',
|
|
393
|
+
'scheduled',
|
|
394
|
+
'someday',
|
|
395
|
+
])
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it('unregistered groups sort LAST and open collapsed', () => {
|
|
399
|
+
const groups = groupTodos(plate, 'context', reg())
|
|
400
|
+
const unreg = groups.filter((g) => g.tone === 'unreg')
|
|
401
|
+
expect(unreg.map((g) => g.key)).toEqual(['infra'])
|
|
402
|
+
expect(unreg[0].collapsedByDefault).toBe(true)
|
|
403
|
+
// …after every registered group and before the residual
|
|
404
|
+
const keys = groups.map((g) => g.key)
|
|
405
|
+
expect(keys.indexOf('infra')).toBeGreaterThan(keys.indexOf('errands'))
|
|
406
|
+
expect(keys.indexOf('infra')).toBeLessThan(keys.indexOf(RESIDUAL_KEY))
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('grouping by context puts the residual LAST and quiet', () => {
|
|
410
|
+
const groups = groupTodos(plate, 'context', reg())
|
|
411
|
+
expect(groups[groups.length - 1].key).toBe(RESIDUAL_KEY)
|
|
412
|
+
expect(groups[groups.length - 1].tone).toBe('resid')
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
it('there is NO "Unprocessed · no state" pile any more, on any axis', () => {
|
|
416
|
+
// THIS ASSERTION TESTED AN AXIS THAT NO LONGER EXISTS. It read "grouping by
|
|
417
|
+
// state puts the residual FIRST and nagging" and asserted a red
|
|
418
|
+
// `tone: 'inbox'` group named "Unprocessed · no state", holding everything
|
|
419
|
+
// that carried no state label.
|
|
420
|
+
//
|
|
421
|
+
// It is a DELETION rather than a re-point, and the reason is the one that
|
|
422
|
+
// made this a replacement instead of an adjustment: that group was computed
|
|
423
|
+
// from label MEMBERSHIP. Once nothing carries a state label its membership
|
|
424
|
+
// is the entire plate — so left in place it would swallow every to-do into
|
|
425
|
+
// one red pile, which is pixel-identical to the failure screen it was
|
|
426
|
+
// written to prevent.
|
|
427
|
+
//
|
|
428
|
+
// WHAT REPLACED IT: `groupByTicketState`, which partitions on the required
|
|
429
|
+
// `state` field, yields exactly three groups and no residual at all,
|
|
430
|
+
// because NEW IS THE INBOX. Asserted in `ticketView.test.ts`.
|
|
431
|
+
const groups = groupTodos(plate, 'context', reg())
|
|
432
|
+
expect(groups.some((g) => g.tone === ('inbox' as unknown))).toBe(false)
|
|
433
|
+
expect(groups.some((g) => g.name.includes('Unprocessed'))).toBe(false)
|
|
434
|
+
// The one residual left is the context one, and it is LAST and quiet.
|
|
435
|
+
const resid = groups.filter((g) => g.key === RESIDUAL_KEY)
|
|
436
|
+
expect(resid).toHaveLength(1)
|
|
437
|
+
expect(resid[0].tone).toBe('resid')
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
it('off-ready there are no groups at all — the axis is unreadable', () => {
|
|
441
|
+
expect(groupTodos(plate, 'context', reg('unavailable'))).toEqual([])
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
it('group → ungroup leaves the plate order byte-identical', () => {
|
|
445
|
+
// The strongest property of the whole shape: grouping is a pure
|
|
446
|
+
// projection, so there is nothing to restore because nothing changed.
|
|
447
|
+
const before = plate.map((t) => t.id)
|
|
448
|
+
groupTodos(plate, 'context', reg())
|
|
449
|
+
expect(plate.map((t) => t.id)).toEqual(before)
|
|
450
|
+
})
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
describe('labelCounts — DISTINCT, never the sum of group counts', () => {
|
|
454
|
+
it('counts each to-do once per slug', () => {
|
|
455
|
+
const plate = [T(['office', 'next']), T(['office', 'home']), T([])]
|
|
456
|
+
const counts = labelCounts(plate)
|
|
457
|
+
expect(counts.get('office')).toBe(2)
|
|
458
|
+
expect(counts.get('home')).toBe(1)
|
|
459
|
+
expect(counts.get('next')).toBe(1)
|
|
460
|
+
expect(counts.get('nope')).toBeUndefined()
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
it('a duplicated slug on one row still counts once for that row', () => {
|
|
464
|
+
expect(labelCounts([T(['office', 'office'])]).get('office')).toBe(1)
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
it('the sum over groups exceeds the plate — which is why the header uses this', () => {
|
|
468
|
+
const plate = [T(['office', 'home']), T(['office', 'home'])]
|
|
469
|
+
const counts = labelCounts(plate)
|
|
470
|
+
expect(counts.get('office')! + counts.get('home')!).toBe(4)
|
|
471
|
+
expect(plate).toHaveLength(2)
|
|
472
|
+
})
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
describe('nextColor — least-used, ties by palette order', () => {
|
|
476
|
+
it('an empty registry takes the first palette token', () => {
|
|
477
|
+
expect(nextColor([])).toBe('blue')
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it('after the six seed rows the sequence is pink, then plum, then blue', () => {
|
|
481
|
+
// blue/green/teal/indigo/amber/slate are taken; pink and plum are free,
|
|
482
|
+
// and pink comes first in PALETTE.
|
|
483
|
+
const seed = [
|
|
484
|
+
L('office', 'blue'),
|
|
485
|
+
L('home', 'green'),
|
|
486
|
+
L('phone', 'teal'),
|
|
487
|
+
L('next', 'indigo'),
|
|
488
|
+
L('wait-for', 'amber'),
|
|
489
|
+
L('someday', 'slate'),
|
|
490
|
+
]
|
|
491
|
+
const first = nextColor(seed)
|
|
492
|
+
expect(first).toBe('pink')
|
|
493
|
+
const withPink = [...seed, L('a', first)]
|
|
494
|
+
const second = nextColor(withPink)
|
|
495
|
+
expect(second).toBe('plum')
|
|
496
|
+
const withPlum = [...withPink, L('b', second)]
|
|
497
|
+
// every token used once now, so the tie-break picks PALETTE[0]
|
|
498
|
+
expect(nextColor(withPlum)).toBe('blue')
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it('delete-then-create lands on a FREE token, not on one in use', () => {
|
|
502
|
+
// The case that kills PALETTE[count % 8]: nine labels, delete one, create
|
|
503
|
+
// one, and the modulo lands back on a token in use while others sit free.
|
|
504
|
+
const nine = LABELS
|
|
505
|
+
const afterDelete = nine.filter((l) => l.slug !== 'errands') // frees `plum`
|
|
506
|
+
expect(nextColor(afterDelete)).toBe('plum')
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
it('a colourless row does not consume a token', () => {
|
|
510
|
+
expect(nextColor([L('x', null)])).toBe('blue')
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
it('cycleColor walks the palette and wraps', () => {
|
|
514
|
+
expect(cycleColor('blue')).toBe('green')
|
|
515
|
+
expect(cycleColor('plum')).toBe('blue')
|
|
516
|
+
expect(cycleColor(null)).toBe('blue')
|
|
517
|
+
})
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
describe('normalizeSlug — the server rule, not the contract prose', () => {
|
|
521
|
+
it.each([
|
|
522
|
+
['@Office', 'office'],
|
|
523
|
+
['Wait For', 'wait-for'],
|
|
524
|
+
['Someday/Maybe', 'someday-maybe'],
|
|
525
|
+
[' Deep Work ', 'deep-work'],
|
|
526
|
+
['!waiting-on-bank', 'waiting-on-bank'],
|
|
527
|
+
['@@office', 'office'],
|
|
528
|
+
])('%s → %s', (raw, slug) => {
|
|
529
|
+
expect(normalizeSlug(raw)).toEqual({ slug, fault: null })
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
it('REPLACES illegal runs with "-" rather than dropping them', () => {
|
|
533
|
+
// Contract §8 step 5 says "drop"; that prose contradicts the contract's own
|
|
534
|
+
// worked example and the shipped server. Dropping would give
|
|
535
|
+
// "somedaymaybe" — this is the check that keeps the right one.
|
|
536
|
+
expect(normalizeSlug('Someday/Maybe').slug).toBe('someday-maybe')
|
|
537
|
+
expect(normalizeSlug('Someday/Maybe').slug).not.toBe('somedaymaybe')
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
it('REFUSES a slug over 32 chars rather than truncating it', () => {
|
|
541
|
+
// The mock's `.slice(0, 32)` would send a value the server 400s.
|
|
542
|
+
const long = 'a'.repeat(40)
|
|
543
|
+
expect(normalizeSlug(long)).toEqual({ slug: long, fault: 'slug_too_long' })
|
|
544
|
+
expect(normalizeSlug('a'.repeat(32)).fault).toBeNull()
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
it.each(['', '@', '@ ', '///', '---'])('%s is invalid_slug', (raw) => {
|
|
548
|
+
expect(normalizeSlug(raw).fault).toBe('invalid_slug')
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
it('a name already in the registry is a DUPLICATE, not a cross-kind clash', () => {
|
|
552
|
+
// THIS ASSERTION TESTED AN AXIS THAT NO LONGER EXISTS. It read "clashWith
|
|
553
|
+
// finds a name already taken by the OTHER kind, both directions", and it
|
|
554
|
+
// guarded the rule that `!office` must not silently attach the context
|
|
555
|
+
// @office — the flat slug namespace was shared by two kinds, so either one
|
|
556
|
+
// could have got there first.
|
|
557
|
+
//
|
|
558
|
+
// `clashWith` is DELETED along with the second kind: with one kind it could
|
|
559
|
+
// only ever return null. WHAT REPLACED IT is the check that was always
|
|
560
|
+
// underneath it — a registry that already has the slug — which the picker
|
|
561
|
+
// and the manage sheet now read directly, and which the server names
|
|
562
|
+
// `labels.duplicate`.
|
|
563
|
+
expect(reg().index.has('next')).toBe(true)
|
|
564
|
+
expect(reg().index.has('office')).toBe(true)
|
|
565
|
+
expect(reg().index.has('brand-new')).toBe(false)
|
|
566
|
+
})
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
describe('unregisteredSlugs', () => {
|
|
570
|
+
it('lists them in first-seen plate order, deduped', () => {
|
|
571
|
+
const plate = [T(['infra']), T(['office', 'offce']), T(['infra'])]
|
|
572
|
+
expect(unregisteredSlugs(plate, reg())).toEqual(['infra', 'offce'])
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
it('off-ready the answer is NONE, not all', () => {
|
|
576
|
+
// Otherwise every "not in your list" surface offers to add back the
|
|
577
|
+
// owner's own labels and each tap 409s.
|
|
578
|
+
const plate = [T(['office', 'infra'])]
|
|
579
|
+
expect(unregisteredSlugs(plate, reg('unavailable'))).toEqual([])
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
describe('diagnoseRegistry — the three content answers must not collapse', () => {
|
|
584
|
+
it('rows present → populated', () => {
|
|
585
|
+
expect(diagnoseRegistry(reg(), [T(['office'])])).toBe('populated')
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
it('zero rows and a bare plate → empty, which is an ANSWER not a failure', () => {
|
|
589
|
+
// `{"labels": []}` is a legitimate 200 from an owner running no vocabulary.
|
|
590
|
+
expect(diagnoseRegistry(reg('ready', []), [T([]), T([])])).toBe('empty')
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
it('zero rows but a LABELLED plate → orphaned, the corrupt-looks-missing trap', () => {
|
|
594
|
+
// An entirely unparseable registry answers with zero rows, never the seed,
|
|
595
|
+
// so it is indistinguishable from a deliberately emptied one EXCEPT by
|
|
596
|
+
// this: emptying detaches on the way out, so labels still on the plate
|
|
597
|
+
// mean the list did not come back.
|
|
598
|
+
expect(diagnoseRegistry(reg('ready', []), [T(['office'])])).toBe('orphaned')
|
|
599
|
+
})
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
describe('reconcileView — runs on a FRESH registry only', () => {
|
|
603
|
+
const base = {
|
|
604
|
+
group: null,
|
|
605
|
+
filter: [],
|
|
606
|
+
matchAll: false,
|
|
607
|
+
states: [],
|
|
608
|
+
collapsed: {},
|
|
609
|
+
} as const
|
|
610
|
+
|
|
611
|
+
it('a merge REPLACES the slug with its target and dedupes', () => {
|
|
612
|
+
const r = reconcileView(
|
|
613
|
+
{ ...base, filter: ['office', 'work'] },
|
|
614
|
+
[L('work', 'blue')],
|
|
615
|
+
new Set(),
|
|
616
|
+
[{ from: 'office', into: 'work' }],
|
|
617
|
+
)
|
|
618
|
+
expect(r.next.filter).toEqual(['work'])
|
|
619
|
+
expect(r.notice).toContain('“office” was merged into @work')
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
it('a merge in ANOTHER TAB is followed too — that is what `into` buys', () => {
|
|
623
|
+
// The receiving tab has only {action:'merge', slug, into} from the wire.
|
|
624
|
+
const r = reconcileView(
|
|
625
|
+
{ ...base, filter: ['office'] },
|
|
626
|
+
[L('work', 'blue')],
|
|
627
|
+
new Set(),
|
|
628
|
+
[{ from: 'office', into: 'work' }],
|
|
629
|
+
)
|
|
630
|
+
expect(r.next.filter).toEqual(['work'])
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
it('a delete DROPS the slug and names it in a notice', () => {
|
|
634
|
+
const r = reconcileView(
|
|
635
|
+
{ ...base, filter: ['office'] },
|
|
636
|
+
[L('home', 'green')],
|
|
637
|
+
new Set(),
|
|
638
|
+
)
|
|
639
|
+
expect(r.next.filter).toEqual([])
|
|
640
|
+
expect(r.notice).toContain('office')
|
|
641
|
+
expect(r.notice).toContain('filter cleared')
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
it('a partial delete says the filter changed rather than that it cleared', () => {
|
|
645
|
+
const r = reconcileView(
|
|
646
|
+
{ ...base, filter: ['office', 'home'] },
|
|
647
|
+
[L('home', 'green')],
|
|
648
|
+
new Set(),
|
|
649
|
+
)
|
|
650
|
+
expect(r.next.filter).toEqual(['home'])
|
|
651
|
+
expect(r.notice).toContain('removed from your filter')
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
it('an UNREGISTERED slug still on the plate is NOT treated as deleted', () => {
|
|
655
|
+
// Without the plate argument every agent-written filter chip is cleared on
|
|
656
|
+
// the next refetch — a filter the owner set, silently destroyed.
|
|
657
|
+
const r = reconcileView(
|
|
658
|
+
{ ...base, filter: ['infra'] },
|
|
659
|
+
[L('home', 'green')],
|
|
660
|
+
new Set(['infra']),
|
|
661
|
+
)
|
|
662
|
+
expect(r.next.filter).toEqual(['infra'])
|
|
663
|
+
expect(r.notice).toBeNull()
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
it('G3 — the "your last state was deleted" branch is GONE, not adapted', () => {
|
|
667
|
+
// THIS ASSERTION TESTED AN AXIS THAT NO LONGER EXISTS. It read "grouping by
|
|
668
|
+
// state with zero states falls back to ungrouped, with a notice", and it
|
|
669
|
+
// guarded a real behaviour: delete your last state row and grouping by
|
|
670
|
+
// state had nothing left to project onto, so it switched off and SAID SO.
|
|
671
|
+
//
|
|
672
|
+
// After the retirement `!labels.some(l => l.kind === 'state')` is ALWAYS
|
|
673
|
+
// true. Left in place, that branch would fire on the first refetch for
|
|
674
|
+
// every owner who had grouping on, switch it off, and announce a deletion
|
|
675
|
+
// that never happened. So the branch is deleted rather than adapted, and
|
|
676
|
+
// this test is inverted to hold it deleted.
|
|
677
|
+
//
|
|
678
|
+
// WHAT REPLACED IT: nothing, and nothing is required to. Ticket grouping
|
|
679
|
+
// reads no registry row, so there is no row whose deletion could take it
|
|
680
|
+
// away — which is the same fact `effectiveGroup`'s exemption encodes.
|
|
681
|
+
const r = reconcileView({ ...base, group: 'ticket' }, [L('home', 'green')], new Set())
|
|
682
|
+
expect(r.next.group).toBe('ticket')
|
|
683
|
+
expect(r.notice).toBeNull()
|
|
684
|
+
// …and with an entirely empty registry, which is where the deleted branch
|
|
685
|
+
// would have been loudest.
|
|
686
|
+
const empty = reconcileView({ ...base, group: 'ticket' }, [], new Set())
|
|
687
|
+
expect(empty.next.group).toBe('ticket')
|
|
688
|
+
expect(empty.notice).toBeNull()
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
it('`states` rides through untouched — it is not the registry\'s to reconcile', () => {
|
|
692
|
+
// Not decoration: the caller writes this whole object back to storage, so a
|
|
693
|
+
// dropped key here silently unpersists the owner's state filter on the next
|
|
694
|
+
// registry refetch.
|
|
695
|
+
const r = reconcileView(
|
|
696
|
+
{ ...base, filter: ['office'], states: ['working', 'completed'] },
|
|
697
|
+
[L('home', 'green')],
|
|
698
|
+
new Set(),
|
|
699
|
+
)
|
|
700
|
+
expect(r.next.states).toEqual(['working', 'completed'])
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
it('a collapsed TICKET group key survives a refetch', () => {
|
|
704
|
+
// A ticket key names no label, so neither the registry nor the plate can
|
|
705
|
+
// vouch for it — and the dead-key sweep would drop it, unfolding the
|
|
706
|
+
// owner's folded Done group on every refetch.
|
|
707
|
+
const r = reconcileView(
|
|
708
|
+
{ ...base, collapsed: { '__tk:completed': true, office: true } },
|
|
709
|
+
[L('home', 'green')],
|
|
710
|
+
new Set(),
|
|
711
|
+
)
|
|
712
|
+
expect(r.next.collapsed).toEqual({ '__tk:completed': true })
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
it('grouping by context with nothing to project onto falls back SILENTLY', () => {
|
|
716
|
+
const r = reconcileView({ ...base, group: 'context' }, [], new Set())
|
|
717
|
+
expect(r.next.group).toBeNull()
|
|
718
|
+
expect(r.notice).toBeNull()
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
it('a context group survives on unregistered slugs alone', () => {
|
|
722
|
+
const r = reconcileView({ ...base, group: 'context' }, [], new Set(['infra']))
|
|
723
|
+
expect(r.next.group).toBe('context')
|
|
724
|
+
})
|
|
725
|
+
|
|
726
|
+
it('a dead collapsed key is dropped INERTLY — no notice', () => {
|
|
727
|
+
const r = reconcileView(
|
|
728
|
+
{ ...base, collapsed: { office: true, home: false, [RESIDUAL_KEY]: true } },
|
|
729
|
+
[L('home', 'green')],
|
|
730
|
+
new Set(),
|
|
731
|
+
)
|
|
732
|
+
expect(r.next.collapsed).toEqual({ home: false, [RESIDUAL_KEY]: true })
|
|
733
|
+
expect(r.notice).toBeNull()
|
|
734
|
+
})
|
|
735
|
+
|
|
736
|
+
it('a steady-state refetch changes nothing and reports changed:false', () => {
|
|
737
|
+
const r = reconcileView({ ...base, filter: ['home'] }, [L('home', 'green')], new Set())
|
|
738
|
+
expect(r.changed).toBe(false)
|
|
739
|
+
expect(r.notice).toBeNull()
|
|
740
|
+
})
|
|
741
|
+
|
|
742
|
+
it('matchAll is carried through untouched', () => {
|
|
743
|
+
const r = reconcileView(
|
|
744
|
+
{ ...base, filter: ['home'], matchAll: true },
|
|
745
|
+
[L('home', 'green')],
|
|
746
|
+
new Set(),
|
|
747
|
+
)
|
|
748
|
+
expect(r.next.matchAll).toBe(true)
|
|
749
|
+
})
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
describe('effectiveGroup — suppressed off-ready, except where it need not be', () => {
|
|
753
|
+
it('renders the stored LABEL axis when the registry is held', () => {
|
|
754
|
+
// Re-pointed from `'state'`, which is no longer a value this can hold.
|
|
755
|
+
expect(effectiveGroup(view({ group: 'context' }), 'ready')).toBe('context')
|
|
756
|
+
expect(effectiveGroup(view({ group: 'context' }), 'provisional')).toBe('context')
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
it('suppresses the LABEL axis while the registry is missing, so it returns by itself', () => {
|
|
760
|
+
expect(effectiveGroup(view({ group: 'context' }), 'unavailable')).toBeNull()
|
|
761
|
+
expect(effectiveGroup(view({ group: 'context' }), 'cold')).toBeNull()
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
it('G2 — `ticket` is EXEMPT from the suppression, in every registry state', () => {
|
|
765
|
+
// The exemption is the whole reason ticket grouping is worth having:
|
|
766
|
+
// suppressed alongside `context`, the one axis that still functions would
|
|
767
|
+
// vanish at exactly the moment it is the only one left. It partitions on a
|
|
768
|
+
// field that arrives with the to-do, so a dead registry costs it nothing.
|
|
769
|
+
for (const st of ['ready', 'provisional', 'cold', 'unavailable'] as RegistryState[]) {
|
|
770
|
+
expect(effectiveGroup(view({ group: 'ticket' }), st)).toBe('ticket')
|
|
771
|
+
}
|
|
772
|
+
})
|
|
773
|
+
})
|
|
774
|
+
|
|
775
|
+
describe('unknownAxisReason — `cold` is "not yet", `unavailable` is "not going to"', () => {
|
|
776
|
+
it('does not report a failure that has not happened', () => {
|
|
777
|
+
expect(unknownAxisReason('cold')).toBe('Still loading your labels…')
|
|
778
|
+
expect(unknownAxisReason('cold')).not.toMatch(/didn't load/)
|
|
779
|
+
})
|
|
780
|
+
|
|
781
|
+
it('does report one that has', () => {
|
|
782
|
+
expect(unknownAxisReason('unavailable')).toMatch(/didn't load/)
|
|
783
|
+
})
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
describe('registryWritable — the guard on every create surface', () => {
|
|
787
|
+
it('allows a create against a registry we can read', () => {
|
|
788
|
+
expect(registryWritable('ready', 'populated')).toBe(true)
|
|
789
|
+
expect(registryWritable('ready', 'empty')).toBe(true)
|
|
790
|
+
// An empty registry is an ANSWER. Refusing to create there would leave a
|
|
791
|
+
// first-time owner with no way to get a first label.
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
it('refuses one against an ORPHANED registry', () => {
|
|
795
|
+
// Zero rows over a labelled plate means the list did not come back — and
|
|
796
|
+
// `create_label` would append to that empty list and save it over the file.
|
|
797
|
+
expect(registryWritable('ready', 'orphaned')).toBe(false)
|
|
798
|
+
expect(registryWritable('provisional', 'orphaned')).toBe(false)
|
|
799
|
+
})
|
|
800
|
+
|
|
801
|
+
it('refuses one when the axis is unknown at all', () => {
|
|
802
|
+
expect(registryWritable('cold', 'empty')).toBe(false)
|
|
803
|
+
expect(registryWritable('unavailable', 'empty')).toBe(false)
|
|
804
|
+
})
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
describe('reconcile notices name a label the way the rail names it', () => {
|
|
808
|
+
const base = { group: null, filter: [], matchAll: false, collapsed: {} } as const
|
|
809
|
+
|
|
810
|
+
it('gives the merge target its sigil and quotes the label that is gone', () => {
|
|
811
|
+
// `@work` is alive and resolvable; `office` has no row left to read a kind
|
|
812
|
+
// from, so a sigil there would assert an axis we just lost.
|
|
813
|
+
const r = reconcileView(
|
|
814
|
+
{ ...base, filter: ['office'] },
|
|
815
|
+
[L('work', 'blue')],
|
|
816
|
+
new Set(),
|
|
817
|
+
[{ from: 'office', into: 'work' }],
|
|
818
|
+
)
|
|
819
|
+
expect(r.notice).toBe('“office” was merged into @work — your filter followed it.')
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
it('quotes a deleted label rather than inventing a sigil for it', () => {
|
|
823
|
+
const r = reconcileView({ ...base, filter: ['office'] }, [], new Set(), [])
|
|
824
|
+
expect(r.notice).toBe('Label “office” was deleted — filter cleared.')
|
|
825
|
+
})
|
|
826
|
+
|
|
827
|
+
it('agrees with itself in the plural', () => {
|
|
828
|
+
const r = reconcileView({ ...base, filter: ['office', 'shed'] }, [], new Set(), [])
|
|
829
|
+
expect(r.notice).toBe('Labels “office”, “shed” were deleted — filter cleared.')
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
it('announces a merge AND a delete that land in the same refetch', () => {
|
|
833
|
+
// Two tabs, two hands, one poll. An early return on the merge branch would
|
|
834
|
+
// swallow the delete — and a filter chip that vanishes with no notice is
|
|
835
|
+
// the one thing §1.1's notice exists to prevent.
|
|
836
|
+
const r = reconcileView(
|
|
837
|
+
{ ...base, filter: ['office', 'shed'] },
|
|
838
|
+
[L('work', 'blue')],
|
|
839
|
+
new Set(),
|
|
840
|
+
[{ from: 'office', into: 'work' }],
|
|
841
|
+
)
|
|
842
|
+
expect(r.next.filter).toEqual(['work'])
|
|
843
|
+
expect(r.notice).toContain('“office” was merged into @work')
|
|
844
|
+
expect(r.notice).toContain('“shed” was deleted')
|
|
845
|
+
})
|
|
846
|
+
})
|