diff --git a/apps/backend/cmd/kandev/initial_agent.go b/apps/backend/cmd/kandev/initial_agent.go index 613d6ddac9..6f0988844c 100644 --- a/apps/backend/cmd/kandev/initial_agent.go +++ b/apps/backend/cmd/kandev/initial_agent.go @@ -6,6 +6,7 @@ import ( agentsettingscontroller "github.com/kandev/kandev/internal/agent/settings/controller" "github.com/kandev/kandev/internal/common/logger" userservice "github.com/kandev/kandev/internal/user/service" + "go.uber.org/zap" ) func runInitialAgentSetup( @@ -20,6 +21,13 @@ func runInitialAgentSetup( return err } + // Ensure all agent profiles that support MCP have MCP enabled by default + // This is idempotent - it only creates MCP configs for profiles that don't have one + if err := agentSettingsController.EnsureDefaultMcpConfig(ctx); err != nil { + log.Warn("Failed to ensure default MCP config", zap.Error(err)) + // Continue anyway - MCP config is not critical for startup + } + // Mark initial setup as complete if not already settings, err := userSvc.GetUserSettings(ctx) if err != nil { diff --git a/apps/backend/internal/agent/lifecycle/session.go b/apps/backend/internal/agent/lifecycle/session.go index 2897920cf1..c4b1ed9759 100644 --- a/apps/backend/internal/agent/lifecycle/session.go +++ b/apps/backend/internal/agent/lifecycle/session.go @@ -182,13 +182,19 @@ func (sm *SessionManager) getResumeContextPrompt(agentConfig *registry.AgentType return resumePrompt } -// injectSessionContext prepends session context information to the prompt. -// This allows the agent to know which session ID and task ID to use when calling MCP tools. -func (sm *SessionManager) injectSessionContext(taskID, sessionID, prompt string) string { - if sessionID == "" && taskID == "" { - return prompt - } - return fmt.Sprintf("\nKandev Task ID: %s\nKandev Session ID: %s\nUse these IDs when calling Kandev MCP tools that require task_id or session_id parameters.\n\n\n%s", taskID, sessionID, prompt) +// injectKandevContext prepends the Kandev system prompt and session context to the user's prompt. +// This provides consistent guidance to the agent about using Kandev tools and the current session IDs. +func (sm *SessionManager) injectKandevContext(taskID, sessionID, prompt string) string { + return fmt.Sprintf(` +IMPORTANT KANDEV INSTRUCTIONS: +- When you have questions for the user, use the ask_user_question_kandev MCP tool to ask them directly. +- When you need to create or update a plan for a task, use the Kandev MCP plan tools (plan_get, plan_update, etc.). +- Kandev Task ID: %s +- Kandev Session ID: %s +- Always use these IDs when calling Kandev MCP tools that require task_id or session_id parameters. + + +%s`, taskID, sessionID, prompt) } // loadSession loads an existing session via ACP session/load @@ -318,11 +324,11 @@ func (sm *SessionManager) InitializeAndPrompt( // For resumed sessions (fork_session pattern): If we have session history but no new task description, // we still need to send a resume context prompt so the agent knows about the prior conversation. if taskDescription != "" { - // Inject task and session ID context so the agent knows which IDs to use for MCP tools - promptWithSessionContext := sm.injectSessionContext(execution.TaskID, execution.SessionID, taskDescription) + // Inject Kandev system prompt and session context + promptWithContext := sm.injectKandevContext(execution.TaskID, execution.SessionID, taskDescription) // Check if we need to inject resume context (fork_session pattern for ACP agents) - effectivePrompt := sm.getResumeContextPrompt(agentConfig, execution.SessionID, promptWithSessionContext) + effectivePrompt := sm.getResumeContextPrompt(agentConfig, execution.SessionID, promptWithContext) if effectivePrompt != taskDescription { sm.logger.Info("injecting resume context into initial prompt", zap.String("execution_id", execution.ID), @@ -405,17 +411,20 @@ func (sm *SessionManager) SendPrompt( // Check if we need to inject resume context (fork_session pattern) // This happens on the first user prompt after resuming a session + // For resumed sessions, we also inject the Kandev context since it's the first prompt in this execution effectivePrompt := prompt if execution.needsResumeContext && !execution.resumeContextInjected { + // Inject Kandev context for resumed sessions (first prompt in this execution) + promptWithContext := sm.injectKandevContext(execution.TaskID, execution.SessionID, prompt) if sm.historyManager != nil { - resumePrompt, err := sm.historyManager.GenerateResumeContext(execution.SessionID, prompt) + resumePrompt, err := sm.historyManager.GenerateResumeContext(execution.SessionID, promptWithContext) if err != nil { sm.logger.Warn("failed to generate resume context for follow-up prompt", zap.String("execution_id", execution.ID), zap.Error(err)) - } else if resumePrompt != prompt { + effectivePrompt = promptWithContext + } else if resumePrompt != promptWithContext { effectivePrompt = resumePrompt - execution.resumeContextInjected = true sm.logger.Info("injecting resume context into follow-up prompt", zap.String("execution_id", execution.ID), zap.String("session_id", execution.SessionID), @@ -425,8 +434,13 @@ func (sm *SessionManager) SendPrompt( sm.logger.Info("resume context prompt content", zap.String("execution_id", execution.ID), zap.String("resume_prompt", effectivePrompt)) + } else { + effectivePrompt = promptWithContext } + } else { + effectivePrompt = promptWithContext } + execution.resumeContextInjected = true } sm.logger.Info("sending prompt to agent", diff --git a/apps/backend/internal/workflow/repository/sqlite.go b/apps/backend/internal/workflow/repository/sqlite.go index 8b8f41e217..1ef42e92c2 100644 --- a/apps/backend/internal/workflow/repository/sqlite.go +++ b/apps/backend/internal/workflow/repository/sqlite.go @@ -142,6 +142,7 @@ func (r *Repository) runMigrations() error { } // seedDefaultWorkflowSteps creates default workflow steps for boards that don't have any. +// Uses the standard template as the default workflow. func (r *Repository) seedDefaultWorkflowSteps() error { // Find boards without workflow steps rows, err := r.db.Query(` @@ -166,27 +167,41 @@ func (r *Repository) seedDefaultWorkflowSteps() error { return err } - // Create default workflow steps for each board + // Use the standard template for default workflow steps now := time.Now() - type stepSeed struct { - name string - position int - stepType string - color string - } - defaultSteps := []stepSeed{ - {name: "Todo", position: 0, stepType: "backlog", color: "bg-neutral-400"}, - {name: "In Progress", position: 1, stepType: "implementation", color: "bg-blue-500"}, - {name: "Review", position: 2, stepType: "review", color: "bg-yellow-500"}, - {name: "Done", position: 3, stepType: "done", color: "bg-green-500"}, - } + standardTemplate := r.getStandardTemplate(now) for _, boardID := range boardIDs { - for _, step := range defaultSteps { + // Map template step IDs to generated UUIDs for linking + idMap := make(map[string]string) + for _, stepDef := range standardTemplate.Steps { + idMap[stepDef.ID] = uuid.New().String() + } + + for _, stepDef := range standardTemplate.Steps { + stepID := idMap[stepDef.ID] + + // Map OnCompleteStepID if set + var onCompleteStepID *string + if stepDef.OnCompleteStepID != "" { + if mappedID, ok := idMap[stepDef.OnCompleteStepID]; ok { + onCompleteStepID = &mappedID + } + } + if _, err := r.db.Exec(` - INSERT INTO workflow_steps (id, board_id, name, step_type, position, color, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - `, uuid.New().String(), boardID, step.name, step.stepType, step.position, step.color, now, now); err != nil { + INSERT INTO workflow_steps ( + id, board_id, name, step_type, position, color, + auto_start_agent, plan_mode, require_approval, + prompt_prefix, prompt_suffix, on_complete_step_id, + allow_manual_move, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, + stepID, boardID, stepDef.Name, string(stepDef.StepType), stepDef.Position, stepDef.Color, + stepDef.AutoStartAgent, stepDef.PlanMode, stepDef.RequireApproval, + stepDef.PromptPrefix, stepDef.PromptSuffix, onCompleteStepID, + stepDef.AllowManualMove, now, now, + ); err != nil { return err } } @@ -231,12 +246,75 @@ func (r *Repository) seedSystemTemplates() error { func (r *Repository) getSystemTemplates() []*models.WorkflowTemplate { now := time.Now().UTC() return []*models.WorkflowTemplate{ + r.getStandardTemplate(now), r.getDevTemplate(now), r.getArchitectureTemplate(now), r.getSimpleTemplate(now), } } +// getStandardTemplate returns the standard 4-step workflow template (default). +// Steps: Todo → Plan → Implementation → Done +func (r *Repository) getStandardTemplate(now time.Time) *models.WorkflowTemplate { + return &models.WorkflowTemplate{ + ID: "standard", + Name: "Standard", + Description: "Standard workflow with planning and implementation phases", + IsSystem: true, + CreatedAt: now, + UpdatedAt: now, + Steps: []models.StepDefinition{ + { + ID: "todo", + Name: "Todo", + StepType: models.StepTypeBacklog, + Position: 0, + Color: "bg-neutral-400", + TaskState: "TODO", + AutoStartAgent: false, + AllowManualMove: true, + }, + { + ID: "plan", + Name: "Plan", + StepType: models.StepTypePlanning, + Position: 1, + Color: "bg-purple-500", + TaskState: "IN_PROGRESS", + AutoStartAgent: true, + PlanMode: true, + RequireApproval: true, + PromptPrefix: "[PLANNING PHASE]\nAnalyze this task and create a detailed implementation plan.\nDo NOT make any code changes yet - only analyze and plan.\n\nBefore creating the plan, ask the user clarifying questions if anything is unclear or ambiguous about the requirements. Use the ask_user_question_kandev tool to get answers before proceeding.\n\nCreate a plan that includes:\n1. Understanding of the requirements\n2. Files that need to be modified or created\n3. Step-by-step implementation approach\n4. Potential risks or considerations\n\nWhen including diagrams in your plan (architecture, sequence, flowcharts, etc.), always use mermaid syntax in code blocks.\n\nIMPORTANT: Save your plan using the create_task_plan_kandev MCP tool with the task_id provided in the session context.\nAfter saving the plan, STOP and wait for user review. The user will review your plan in the UI and may edit it.\nDo not create any other files during this phase - only use the MCP tool to save the plan.", + OnCompleteStepID: "implementation", + AllowManualMove: true, + }, + { + ID: "implementation", + Name: "Implementation", + StepType: models.StepTypeImplementation, + Position: 2, + Color: "bg-blue-500", + TaskState: "IN_PROGRESS", + AutoStartAgent: true, + RequireApproval: true, + PromptPrefix: "[IMPLEMENTATION PHASE]\nBefore starting implementation, retrieve the task plan using get_task_plan_kandev with the task_id from the session context.\nReview the plan carefully, including any edits the user may have made.\nAcknowledge the plan and any user modifications before proceeding.\n\nThen implement the task following the plan step-by-step.\nYou can update the plan during implementation using update_task_plan_kandev if needed.", + OnCompleteStepID: "done", + AllowManualMove: true, + }, + { + ID: "done", + Name: "Done", + StepType: models.StepTypeDone, + Position: 3, + Color: "bg-green-500", + TaskState: "COMPLETED", + AutoStartAgent: false, + AllowManualMove: true, + }, + }, + } +} + // getDevTemplate returns the full development workflow template. func (r *Repository) getDevTemplate(now time.Time) *models.WorkflowTemplate { return &models.WorkflowTemplate{ @@ -266,7 +344,7 @@ func (r *Repository) getDevTemplate(now time.Time) *models.WorkflowTemplate { TaskState: "IN_PROGRESS", AutoStartAgent: true, PlanMode: true, - PromptPrefix: "[PLANNING PHASE]\nAnalyze this task and create a detailed implementation plan.\nDo NOT make any code changes yet - only analyze and plan.\n\nBefore creating the plan, ask the user clarifying questions if anything is unclear or ambiguous about the requirements. Use the ask_user_question_kandev tool to get answers before proceeding.\n\nCreate a plan that includes:\n1. Understanding of the requirements\n2. Files that need to be modified or created\n3. Step-by-step implementation approach\n4. Potential risks or considerations\n\nIMPORTANT: Save your plan using the create_task_plan_kandev MCP tool with the task_id provided in the session context.\nAfter saving the plan, STOP and wait for user review. The user will review your plan in the UI and may edit it.\nDo not create any other files during this phase - only use the MCP tool to save the plan.", + PromptPrefix: "[PLANNING PHASE]\nAnalyze this task and create a detailed implementation plan.\nDo NOT make any code changes yet - only analyze and plan.\n\nBefore creating the plan, ask the user clarifying questions if anything is unclear or ambiguous about the requirements. Use the ask_user_question_kandev tool to get answers before proceeding.\n\nCreate a plan that includes:\n1. Understanding of the requirements\n2. Files that need to be modified or created\n3. Step-by-step implementation approach\n4. Potential risks or considerations\n\nWhen including diagrams in your plan (architecture, sequence, flowcharts, etc.), always use mermaid syntax in code blocks.\n\nIMPORTANT: Save your plan using the create_task_plan_kandev MCP tool with the task_id provided in the session context.\nAfter saving the plan, STOP and wait for user review. The user will review your plan in the UI and may edit it.\nDo not create any other files during this phase - only use the MCP tool to save the plan.", OnCompleteStepID: "review-plan", AllowManualMove: true, }, @@ -349,7 +427,7 @@ func (r *Repository) getArchitectureTemplate(now time.Time) *models.WorkflowTemp TaskState: "IN_PROGRESS", AutoStartAgent: true, PlanMode: true, - PromptPrefix: "[ARCHITECTURE PHASE]\nAnalyze and design the architecture for this task.\n\nBefore creating the design, ask the user clarifying questions if anything is unclear or ambiguous about the requirements. Use the ask_user_question_kandev tool to get answers before proceeding.\n\nIMPORTANT: Save your design using the create_task_plan_kandev MCP tool with the task_id provided in the session context.\nAfter saving the plan, STOP and wait for user review. The user will review your design in the UI and may edit it.\nDo not create any other files during this phase - only use the MCP tool to save the plan.", + PromptPrefix: "[ARCHITECTURE PHASE]\nAnalyze and design the architecture for this task.\n\nBefore creating the design, ask the user clarifying questions if anything is unclear or ambiguous about the requirements. Use the ask_user_question_kandev tool to get answers before proceeding.\n\nWhen including diagrams in your design (architecture, sequence, flowcharts, etc.), always use mermaid syntax in code blocks.\n\nIMPORTANT: Save your design using the create_task_plan_kandev MCP tool with the task_id provided in the session context.\nAfter saving the plan, STOP and wait for user review. The user will review your design in the UI and may edit it.\nDo not create any other files during this phase - only use the MCP tool to save the plan.", OnCompleteStepID: "review", AllowManualMove: true, }, diff --git a/apps/pnpm-lock.yaml b/apps/pnpm-lock.yaml index 21c92bf8f8..ebb8cb4c44 100644 --- a/apps/pnpm-lock.yaml +++ b/apps/pnpm-lock.yaml @@ -378,6 +378,9 @@ importers: input-otp: specifier: ^1.4.2 version: 1.4.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + mermaid: + specifier: ^11.12.2 + version: 11.12.2 next: specifier: 16.1.1 version: 16.1.1(@babel/core@7.28.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -467,6 +470,9 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@antfu/ni@25.0.0': resolution: {integrity: sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==} hasBin: true @@ -625,6 +631,24 @@ packages: '@types/react': optional: true + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + + '@chevrotain/cst-dts-gen@11.0.3': + resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + + '@chevrotain/gast@11.0.3': + resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + + '@chevrotain/types@11.0.3': + resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + + '@chevrotain/utils@11.0.3': + resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@codemirror/autocomplete@6.20.0': resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} @@ -1146,6 +1170,12 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.0': + resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} @@ -1409,6 +1439,9 @@ packages: '@types/react': '>=16' react: '>=16' + '@mermaid-js/parser@0.6.3': + resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@milkdown/components@7.18.0': resolution: {integrity: sha512-Zu/GMqy1byyxul/+/RWcpe02b7luhtW1SfTYNFZnaWPvIap5M9vG7pFeQNRqJe5cbfKI+bvW8Ubyb5BG2kb9Ug==} peerDependencies: @@ -2592,30 +2625,96 @@ packages: '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + '@types/d3-color@3.1.3': resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + '@types/d3-ease@3.0.2': resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} '@types/d3-path@3.1.1': resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + '@types/d3-scale@4.0.9': resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + '@types/d3-shape@3.1.8': resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + '@types/d3-time@3.0.4': resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} '@types/d3-timer@3.0.2': resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -2625,6 +2724,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -3164,6 +3266,14 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + chevrotain-allstar@0.3.1: + resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -3223,6 +3333,10 @@ packages: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -3260,6 +3374,12 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -3284,34 +3404,129 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + d3-format@3.1.0: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + d3-path@3.1.0: resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} engines: {node: '>=12'} + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + d3-scale@4.0.2: resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} engines: {node: '>=12'} + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + d3-shape@3.2.0: resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} engines: {node: '>=12'} @@ -3328,6 +3543,23 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.13: + resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -3353,6 +3585,9 @@ packages: date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -3415,6 +3650,9 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -3962,6 +4200,9 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + happy-dom@20.1.0: resolution: {integrity: sha512-ebvqjBqzenBk2LjzNEAzoj7yhw7rW/R2/wVevMu6Mrq3MXtcI/RUz4+ozpcOcqVLEWPqLfg2v9EAU7fFXZUUJw==} engines: {node: '>=20.0.0'} @@ -4042,6 +4283,10 @@ packages: resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} engines: {node: '>=18.18.0'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -4081,6 +4326,9 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} @@ -4356,6 +4604,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -4368,6 +4619,10 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + langium@3.3.1: + resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} + engines: {node: '>=16.0.0'} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -4375,6 +4630,12 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -4460,6 +4721,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lodash-es@4.17.23: resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} @@ -4499,6 +4763,11 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -4575,6 +4844,9 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + mermaid@11.12.2: + resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -4934,6 +5206,9 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4982,6 +5257,12 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -5303,6 +5584,9 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rollup@4.55.1: resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -5311,6 +5595,9 @@ packages: rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -5322,6 +5609,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -5564,6 +5854,9 @@ packages: babel-plugin-macros: optional: true + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -5649,6 +5942,10 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + ts-morph@26.0.0: resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} @@ -5812,6 +6109,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + validate-npm-package-name@7.0.2: resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} engines: {node: ^20.17.0 || >=22.9.0} @@ -5899,6 +6200,26 @@ packages: jsdom: optional: true + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + vue@3.5.27: resolution: {integrity: sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==} peerDependencies: @@ -6058,6 +6379,11 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + '@antfu/ni@25.0.0': dependencies: ansis: 4.2.0 @@ -6278,6 +6604,25 @@ snapshots: optionalDependencies: '@types/react': 19.2.8 + '@braintree/sanitize-url@7.1.2': {} + + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + '@codemirror/autocomplete@6.20.0': dependencies: '@codemirror/language': 6.12.1 @@ -6850,6 +7195,14 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + mlly: 1.8.0 + '@img/colour@1.0.0': optional: true @@ -7135,6 +7488,10 @@ snapshots: '@types/react': 19.2.8 react: 19.2.3 + '@mermaid-js/parser@0.6.3': + dependencies: + langium: 3.3.1 + '@milkdown/components@7.18.0(@codemirror/language@6.12.1)(@codemirror/state@6.5.3)(@codemirror/view@6.39.10)(typescript@5.9.3)': dependencies: '@codemirror/language': 6.12.1 @@ -8488,28 +8845,121 @@ snapshots: '@types/d3-array@3.2.2': {} + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + '@types/d3-color@3.1.3': {} + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + '@types/d3-ease@3.0.2': {} + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + '@types/d3-interpolate@3.0.4': dependencies: '@types/d3-color': 3.1.3 '@types/d3-path@3.1.1': {} + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + '@types/d3-scale@4.0.9': dependencies: '@types/d3-time': 3.0.4 + '@types/d3-selection@3.0.11': {} + '@types/d3-shape@3.1.8': dependencies: '@types/d3-path': 3.1.1 + '@types/d3-time-format@4.0.3': {} + '@types/d3-time@3.0.4': {} '@types/d3-timer@3.0.2': {} + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -8520,6 +8970,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/geojson@7946.0.16': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -9114,6 +9566,20 @@ snapshots: dependencies: get-func-name: 2.0.2 + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.17.23 + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -9174,6 +9640,8 @@ snapshots: commander@14.0.2: {} + commander@7.2.0: {} + commander@8.3.0: {} concat-map@0.0.1: {} @@ -9197,6 +9665,14 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + cosmiconfig@9.0.0(typescript@5.9.3): dependencies: env-paths: 2.2.1 @@ -9218,22 +9694,107 @@ snapshots: csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape-fcose@2.2.0(cytoscape@3.33.1): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + d3-array@3.2.4: dependencies: internmap: 2.0.3 + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + d3-color@3.1.0: {} + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + d3-ease@3.0.1: {} + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + d3-format@3.1.0: {} + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 + d3-path@1.0.9: {} + d3-path@3.1.0: {} + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + d3-scale@4.0.2: dependencies: d3-array: 3.2.4 @@ -9242,6 +9803,12 @@ snapshots: d3-time: 3.1.0 d3-time-format: 4.1.0 + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + d3-shape@3.2.0: dependencies: d3-path: 3.1.0 @@ -9256,6 +9823,61 @@ snapshots: d3-timer@3.0.1: {} + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.13: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.23 + damerau-levenshtein@1.0.8: {} data-uri-to-buffer@4.0.1: {} @@ -9282,6 +9904,8 @@ snapshots: date-fns@4.1.0: {} + dayjs@1.11.19: {} + debug@3.2.7: dependencies: ms: 2.1.3 @@ -9327,6 +9951,10 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + depd@2.0.0: {} dequal@2.0.3: {} @@ -10160,6 +10788,8 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 + hachure-fill@0.5.2: {} + happy-dom@20.1.0: dependencies: '@types/node': 20.19.28 @@ -10273,6 +10903,10 @@ snapshots: human-signals@8.0.1: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -10305,6 +10939,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@1.0.1: {} + internmap@2.0.3: {} ipaddr.js@1.9.1: {} @@ -10545,18 +11181,32 @@ snapshots: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + kind-of@6.0.3: {} kleur@3.0.3: {} kleur@4.1.5: {} + langium@3.3.1: + dependencies: + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + language-subtag-registry@0.3.23: {} language-tags@1.0.9: dependencies: language-subtag-registry: 0.3.23 + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -10622,6 +11272,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.17.21: {} + lodash-es@4.17.23: {} lodash.merge@4.6.2: {} @@ -10661,6 +11313,8 @@ snapshots: markdown-table@3.0.4: {} + marked@16.4.2: {} + math-intrinsics@1.1.0: {} mdast-util-definitions@6.0.0: @@ -10857,6 +11511,29 @@ snapshots: merge2@1.4.1: {} + mermaid@11.12.2: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.0 + '@mermaid-js/parser': 0.6.3 + '@types/d3': 7.4.3 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.19 + dompurify: 3.3.1 + katex: 0.16.28 + khroma: 2.1.0 + lodash-es: 4.17.23 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.2.0 @@ -11418,6 +12095,8 @@ snapshots: path-browserify@1.0.1: {} + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -11450,6 +12129,13 @@ snapshots: mlly: 1.8.0 pathe: 2.0.3 + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + possible-typed-array-names@1.1.0: {} postcss-selector-parser@7.1.1: @@ -11949,6 +12635,8 @@ snapshots: reusify@1.1.0: {} + robust-predicates@3.0.2: {} + rollup@4.55.1: dependencies: '@types/estree': 1.0.8 @@ -11982,6 +12670,13 @@ snapshots: rope-sequence@1.3.4: {} + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + router@2.2.0: dependencies: debug: 4.4.3 @@ -11998,6 +12693,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -12343,6 +13040,8 @@ snapshots: optionalDependencies: '@babel/core': 7.28.6 + stylis@4.3.6: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -12402,6 +13101,8 @@ snapshots: dependencies: typescript: 5.9.3 + ts-dedent@2.2.0: {} + ts-morph@26.0.0: dependencies: '@ts-morph/common': 0.27.0 @@ -12624,6 +13325,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@11.1.0: {} + validate-npm-package-name@7.0.2: {} vary@1.1.2: {} @@ -12732,6 +13435,23 @@ snapshots: - supports-color - terser + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + vue@3.5.27(typescript@5.9.3): dependencies: '@vue/compiler-dom': 3.5.27 diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 83d0455100..c727917a0e 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -485,7 +485,7 @@ /* Base text styling - match chat text-xs */ .milkdown-wrapper .editor, -.milkdown-wrapper .editor * { +.milkdown-wrapper .editor *:not(.mermaid-container):not(.mermaid-container *) { font-size: 0.75rem !important; line-height: 1.625; color: var(--foreground); @@ -612,3 +612,37 @@ border-top: 1px solid var(--border) !important; margin: 1rem 0 !important; } + +/* Comment highlights in plan editor - inline */ +.milkdown-wrapper .comment-highlight { + background-color: rgba(59, 130, 246, 0.45); + border-radius: 3px; + cursor: pointer; + transition: background-color 0.15s ease; + padding: 2px 1px; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; +} + +.milkdown-wrapper .comment-highlight:hover { + background-color: rgba(59, 130, 246, 0.6); +} + +/* Mermaid diagram styles */ +.milkdown-wrapper .mermaid-container { + margin: 0.75rem 0 !important; + padding: 0.5rem !important; + overflow-x: auto !important; +} + +.milkdown-wrapper .mermaid-container svg { + max-width: 100% !important; + height: auto !important; +} + +.milkdown-wrapper .mermaid-error { + color: #ef4444 !important; + font-size: 0.75rem !important; + padding: 0.5rem !important; + white-space: pre-wrap !important; +} diff --git a/apps/web/components/task/chat/messages/chat-message.tsx b/apps/web/components/task/chat/messages/chat-message.tsx index 8734543491..4d8ea7df3e 100644 --- a/apps/web/components/task/chat/messages/chat-message.tsx +++ b/apps/web/components/task/chat/messages/chat-message.tsx @@ -111,7 +111,7 @@ export const ChatMessage = memo(function ChatMessage({ comment, label, className ); } - // User message: right-aligned bubble + // User message: right-aligned bubble with markdown support if (isUser) { return (
diff --git a/apps/web/components/task/chat/messages/clarification-request-message.tsx b/apps/web/components/task/chat/messages/clarification-request-message.tsx index 36a5929f2f..1746d2e825 100644 --- a/apps/web/components/task/chat/messages/clarification-request-message.tsx +++ b/apps/web/components/task/chat/messages/clarification-request-message.tsx @@ -68,19 +68,24 @@ export function ClarificationRequestMessage({ comment }: ClarificationRequestMes {/* Content */}
- {/* Header with question and answer */} -
- - {question.prompt} - - {getStatusIndicator()} - {isAnswered && ( - {getAnswerSummary()} - )} - {isSkipped && ( - Skipped - )} + {/* Question */} +
+ {question.prompt}
+ + {/* Answer - indented below question */} + {isAnswered && ( +
+ {getStatusIndicator()} + {getAnswerSummary()} +
+ )} + {isSkipped && ( +
+ {getStatusIndicator()} + Skipped +
+ )}
diff --git a/apps/web/components/task/markdown-editor.tsx b/apps/web/components/task/markdown-editor.tsx index cd4f45a433..b574a466e6 100644 --- a/apps/web/components/task/markdown-editor.tsx +++ b/apps/web/components/task/markdown-editor.tsx @@ -1,32 +1,427 @@ 'use client'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, useCallback } from 'react'; import { useTheme } from 'next-themes'; -import { Editor, rootCtx, defaultValueCtx } from '@milkdown/core'; +import { Editor, rootCtx, defaultValueCtx, editorViewCtx } from '@milkdown/core'; import { nord } from '@milkdown/theme-nord'; -import { commonmark } from '@milkdown/preset-commonmark'; +import { commonmark, codeBlockSchema } from '@milkdown/preset-commonmark'; import { gfm } from '@milkdown/preset-gfm'; import { listener, listenerCtx } from '@milkdown/plugin-listener'; import { Milkdown, MilkdownProvider, useEditor } from '@milkdown/react'; +import { Plugin, PluginKey } from '@milkdown/prose/state'; +import { Decoration, DecorationSet } from '@milkdown/prose/view'; +import { $prose, $view } from '@milkdown/utils'; +import type { Node as ProseMirrorNode } from '@milkdown/prose/model'; +import mermaid from 'mermaid'; + +export type TextSelection = { + text: string; + position: { x: number; y: number }; +}; + +export type CommentHighlight = { + id: string; + selectedText: string; + comment: string; +}; type MilkdownEditorProps = { value: string; onChange: (value: string) => void; placeholder?: string; + onSelectionChange?: (selection: TextSelection | null) => void; + comments?: CommentHighlight[]; + onCommentClick?: (id: string, position: { x: number; y: number }) => void; }; -function MilkdownEditorInner({ value, onChange }: MilkdownEditorProps) { +// Minimum text length required for comment highlighting +const MIN_COMMENT_TEXT_LENGTH = 3; + +// Initialize mermaid with default config +let mermaidInitialized = false; +function initMermaid(theme: 'dark' | 'light' = 'dark') { + mermaid.initialize({ + startOnLoad: false, + theme: theme === 'dark' ? 'dark' : 'default', + securityLevel: 'loose', + }); + mermaidInitialized = true; +} + +// Unique ID counter for mermaid diagrams +let mermaidIdCounter = 0; + +// Create mermaid node view for code blocks using $view +const mermaidCodeBlockView = $view(codeBlockSchema.node, () => { + return (node: ProseMirrorNode) => { + const language = node.attrs.language as string; + + // If not mermaid, return null to use default rendering + if (language !== 'mermaid') { + // Create default code block rendering + const pre = document.createElement('pre'); + const code = document.createElement('code'); + code.textContent = node.textContent; + if (language) { + code.className = `language-${language}`; + } + pre.appendChild(code); + return { + dom: pre, + contentDOM: code, + }; + } + + // Track current content for comparison in update + let currentContent = node.textContent; + + // Create container for mermaid diagram + const dom = document.createElement('div'); + dom.className = 'mermaid-container'; + + // Initialize mermaid if not already done + if (!mermaidInitialized) { + initMermaid(); + } + + // Render the mermaid diagram + const id = `mermaid-${++mermaidIdCounter}`; + mermaid.render(id, currentContent).then(({ svg }) => { + dom.innerHTML = svg; + }).catch((err) => { + dom.innerHTML = `
Error rendering diagram: ${err.message}
`; + }); + + return { + dom, + contentDOM: null, + update: (updatedNode: ProseMirrorNode) => { + if (updatedNode.type.name !== 'code_block') return false; + const updatedLang = updatedNode.attrs.language as string; + if (updatedLang !== 'mermaid') return false; + const newContent = updatedNode.textContent; + if (newContent !== currentContent) { + currentContent = newContent; // Update tracked content + const newId = `mermaid-${++mermaidIdCounter}`; + mermaid.render(newId, newContent).then(({ svg }) => { + dom.innerHTML = svg; + }).catch((err) => { + dom.innerHTML = `
Error rendering diagram: ${err.message}
`; + }); + } + return true; + }, + }; + }; +}); + +// Plugin key for managing comment highlights +const commentHighlightPluginKey = new PluginKey<{ + deco: DecorationSet; + comments: CommentHighlight[]; +}>('comment-highlight'); + +// Helper to get plain text content and position mapping from a document range +function getTextWithPositions(doc: Parameters[0]): { text: string; positions: number[] } { + let text = ''; + const positions: number[] = []; // Maps text index to document position + + doc.descendants((node, pos) => { + if (node.isText && node.text) { + for (let i = 0; i < node.text.length; i++) { + positions.push(pos + i); + text += node.text[i]; + } + } else if (node.type.name === 'hardbreak') { + // Handle hardbreak as a space/newline + positions.push(-1); + text += ' '; + } else if (node.isBlock && text.length > 0 && !text.endsWith('\n')) { + // Add newline between blocks + positions.push(-1); // -1 indicates a synthetic newline + text += '\n'; + } + }); + + return { text, positions }; +} + +// Normalize text for comparison (collapse whitespace, normalize newlines) +function normalizeForSearch(text: string): string { + return text + .replace(/\r\n/g, '\n') + .replace(/[\t ]+/g, ' ') + .replace(/\n+/g, '\n') + .trim() + .toLowerCase(); +} + +// Create decorations for comment highlights +function createCommentDecorations( + doc: Parameters[0], + comments: CommentHighlight[] +): DecorationSet { + const decorations: Decoration[] = []; + + if (comments.length === 0) { + return DecorationSet.empty; + } + + // Get the full text content with position mapping + const { text: fullText, positions } = getTextWithPositions(doc); + const normalizedFullText = normalizeForSearch(fullText); + + comments.forEach((comment) => { + const searchText = normalizeForSearch(comment.selectedText); + + if (!searchText || searchText.length < MIN_COMMENT_TEXT_LENGTH) { + return; + } + + // Find the text in the normalized full document text + const normalizedIndex = normalizedFullText.indexOf(searchText); + + if (normalizedIndex === -1) { + // Try with just the first line as fallback + const firstLine = normalizeForSearch(comment.selectedText.split('\n')[0]); + if (firstLine.length >= MIN_COMMENT_TEXT_LENGTH) { + const fallbackIndex = normalizedFullText.indexOf(firstLine); + if (fallbackIndex !== -1) { + createDecorationForRange(fallbackIndex, firstLine.length, comment, fullText, positions, decorations); + } + } + return; + } + + createDecorationForRange(normalizedIndex, searchText.length, comment, fullText, positions, decorations); + }); + + return DecorationSet.create(doc, decorations); +} + +// Helper to create decoration(s) for a text range, handling multi-line selections +function createDecorationForRange( + normalizedIndex: number, + length: number, + comment: CommentHighlight, + fullText: string, + positions: number[], + decorations: Decoration[] +) { + // Map normalized index back to original text index + const normalizedFullText = normalizeForSearch(fullText); + + const normalizedChars = normalizedFullText.split(''); + const origChars = fullText.split(''); + + // Map from normalized position to original position + const normToOrig: number[] = []; + let oi = 0; + for (let ni = 0; ni < normalizedChars.length; ni++) { + // Skip whitespace differences in original + while (oi < origChars.length && + origChars[oi].toLowerCase() !== normalizedChars[ni] && + /\s/.test(origChars[oi])) { + oi++; + } + normToOrig[ni] = oi; + oi++; + } + + const startOrig = normToOrig[normalizedIndex] ?? 0; + const endOrig = normToOrig[normalizedIndex + length - 1] ?? startOrig; + + // Now create decorations for each contiguous range (split by newlines/block boundaries) + let rangeStart = -1; + + for (let i = startOrig; i <= endOrig && i < positions.length; i++) { + const pos = positions[i]; + + if (pos === -1) { + // Hit a block boundary - close current range if open + if (rangeStart !== -1) { + const fromPos = positions[rangeStart]; + const toPos = positions[i - 1] + 1; + if (fromPos >= 0 && toPos > fromPos) { + decorations.push(Decoration.inline(fromPos, toPos, { + class: 'comment-highlight', + 'data-comment-id': comment.id, + title: comment.comment, + })); + } + rangeStart = -1; + } + } else { + // Valid position - start range if not started + if (rangeStart === -1) { + rangeStart = i; + } + } + } + + // Close final range + if (rangeStart !== -1 && rangeStart < positions.length) { + const fromPos = positions[rangeStart]; + const lastValidIndex = Math.min(endOrig, positions.length - 1); + const toPos = positions[lastValidIndex] + 1; + if (fromPos >= 0 && toPos > fromPos) { + decorations.push(Decoration.inline(fromPos, toPos, { + class: 'comment-highlight', + 'data-comment-id': comment.id, + title: comment.comment, + })); + } + } +} + +// Create the comment highlight plugin +function createCommentHighlightPlugin(initialComments: CommentHighlight[]) { + return $prose(() => { + return new Plugin({ + key: commentHighlightPluginKey, + state: { + init(_, state) { + return { + deco: createCommentDecorations(state.doc, initialComments), + comments: initialComments, + }; + }, + apply(tr, value, _, newState) { + // Check if comments were updated via meta + const newComments = tr.getMeta(commentHighlightPluginKey); + if (newComments !== undefined) { + return { + deco: createCommentDecorations(newState.doc, newComments), + comments: newComments, + }; + } + // If document changed, remap decorations + if (tr.docChanged) { + return { + deco: createCommentDecorations(newState.doc, value.comments), + comments: value.comments, + }; + } + return value; + }, + }, + props: { + decorations(state) { + const pluginState = commentHighlightPluginKey.getState(state); + return pluginState?.deco ?? DecorationSet.empty; + }, + }, + }); + }); +} + +function MilkdownEditorInner({ + value, + onChange, + onSelectionChange, + comments = [], + onCommentClick, +}: MilkdownEditorProps) { const { resolvedTheme } = useTheme(); + const wrapperRef = useRef(null); // Capture initial value at mount - editor manages its own state after that const initialValueRef = useRef(value); const onChangeRef = useRef(onChange); + const onSelectionChangeRef = useRef(onSelectionChange); + const commentsRef = useRef(comments); + const onCommentClickRef = useRef(onCommentClick); - // Keep onChange ref updated + // Keep refs updated useEffect(() => { onChangeRef.current = onChange; }, [onChange]); - const { loading } = useEditor((root) => + useEffect(() => { + onSelectionChangeRef.current = onSelectionChange; + }, [onSelectionChange]); + + useEffect(() => { + commentsRef.current = comments; + }, [comments]); + + useEffect(() => { + onCommentClickRef.current = onCommentClick; + }, [onCommentClick]); + + // Set up click handler for comment highlights via event delegation + useEffect(() => { + const wrapper = wrapperRef.current; + if (!wrapper) return; + + const handleClick = (e: MouseEvent) => { + const target = e.target as HTMLElement; + const commentHighlight = target.closest('.comment-highlight'); + if (commentHighlight && onCommentClickRef.current) { + const commentId = commentHighlight.getAttribute('data-comment-id'); + if (commentId) { + onCommentClickRef.current(commentId, { x: e.clientX, y: e.clientY }); + } + } + }; + + wrapper.addEventListener('click', handleClick); + return () => wrapper.removeEventListener('click', handleClick); + }, []); + + // Handle Cmd+I to trigger selection popover (overrides italic formatting) + useEffect(() => { + const wrapper = wrapperRef.current; + if (!wrapper) return; + + const handleKeyDown = (e: KeyboardEvent) => { + // Check for Cmd+I (Mac) or Ctrl+I (Windows/Linux) + if ((e.metaKey || e.ctrlKey) && e.key === 'i') { + // Only handle if we have a selection callback + if (!onSelectionChangeRef.current) return; + + const selection = window.getSelection(); + if (!selection) return; + + // Prevent Milkdown's italic formatting immediately + e.preventDefault(); + e.stopImmediatePropagation(); + + // Now check if we have valid selected text + if (selection.isCollapsed || !selection.toString().trim()) { + return; + } + + const selectedText = selection.toString().trim(); + if (selectedText.length < MIN_COMMENT_TEXT_LENGTH) { + return; + } + + // Get position from selection range + const range = selection.getRangeAt(0); + const rect = range.getBoundingClientRect(); + + onSelectionChangeRef.current({ + text: selectedText, + position: { + x: rect.left + rect.width / 2, + y: rect.bottom, + }, + }); + } + }; + + // Attach to wrapper element in capture phase to intercept before Milkdown + wrapper.addEventListener('keydown', handleKeyDown, true); + return () => wrapper.removeEventListener('keydown', handleKeyDown, true); + }, []); + + // Create the highlight plugin once with initial comments + const highlightPlugin = useCallback( + () => createCommentHighlightPlugin(commentsRef.current), + [] + ); + + const { loading, get } = useEditor((root) => Editor.make() .config((ctx) => { ctx.set(rootCtx, root); @@ -38,11 +433,39 @@ function MilkdownEditorInner({ value, onChange }: MilkdownEditorProps) { .config(nord) .use(commonmark) .use(gfm) + .use(mermaidCodeBlockView) .use(listener) + .use(highlightPlugin()) ); + // Update decorations when comments change + useEffect(() => { + const editor = get(); + if (!editor || loading) return; + + try { + // Get the editor view and dispatch a transaction to update comments + editor.action((ctx) => { + try { + const view = ctx.get(editorViewCtx); + if (view) { + const tr = view.state.tr.setMeta(commentHighlightPluginKey, comments); + view.dispatch(tr); + } + } catch { + // editorViewCtx not available yet + } + }); + } catch { + // Editor might not be ready + } + }, [comments, get, loading]); + return ( -
+
{loading && (
diff --git a/apps/web/components/task/plan-comments.tsx b/apps/web/components/task/plan-comments.tsx new file mode 100644 index 0000000000..60c02885a5 --- /dev/null +++ b/apps/web/components/task/plan-comments.tsx @@ -0,0 +1,5 @@ +export type PlanComment = { + id: string; + selectedText: string; + comment: string; +}; diff --git a/apps/web/components/task/plan-selection-popover.tsx b/apps/web/components/task/plan-selection-popover.tsx new file mode 100644 index 0000000000..737bb1d841 --- /dev/null +++ b/apps/web/components/task/plan-selection-popover.tsx @@ -0,0 +1,142 @@ +'use client'; + +import { useState, useCallback, useRef, useEffect } from 'react'; +import { IconPlus } from '@tabler/icons-react'; +import { Button } from '@kandev/ui/button'; +import { Textarea } from '@kandev/ui/textarea'; +import { cn } from '@/lib/utils'; + +type SelectionPosition = { + x: number; + y: number; +}; + +type PlanSelectionPopoverProps = { + selectedText: string; + position: SelectionPosition; + onAdd: (comment: string, selectedText: string) => void; + onClose: () => void; + editingComment?: string; // Pre-fill if editing existing comment +}; + +export function PlanSelectionPopover({ + selectedText, + position, + onAdd, + onClose, + editingComment, +}: PlanSelectionPopoverProps) { + const [comment, setComment] = useState(editingComment || ''); + const textareaRef = useRef(null); + const popoverRef = useRef(null); + + // Focus textarea on mount + useEffect(() => { + textareaRef.current?.focus(); + }, []); + + // Close on click outside + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) { + onClose(); + } + }; + // Delay adding listener to avoid immediate close from selection click + const timer = setTimeout(() => { + document.addEventListener('mousedown', handleClickOutside); + }, 100); + return () => { + clearTimeout(timer); + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [onClose]); + + // Close on Escape + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + onClose(); + } + }; + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [onClose]); + + const handleSubmit = useCallback(() => { + if (!comment.trim()) return; + onAdd(comment.trim(), selectedText); + onClose(); + }, [comment, onAdd, selectedText, onClose]); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + handleSubmit(); + } + }, + [handleSubmit] + ); + + const isDisabled = !comment.trim(); + + // Truncate text for preview + const previewText = selectedText.length > 80 + ? selectedText.slice(0, 80).trim() + '…' + : selectedText; + + // Calculate position - flip above if would overflow bottom + const popoverHeight = 200; // approximate height of popover + const gap = 10; + const wouldOverflowBottom = position.y + gap + popoverHeight > window.innerHeight; + const top = wouldOverflowBottom + ? position.y - popoverHeight - gap // Position above selection + : position.y + gap; // Position below selection + + return ( +
+ {/* Text preview */} +

+ “{previewText}” +

+ + {/* Comment input */} +