Compare commits

...

14 Commits

Author SHA1 Message Date
ricardo a45893a9bc Revert: stats cards a 5 originales + sidecar Python pospuesto 2026-05-30 01:04:24 -05:00
ricardo 1f39c4df7a Exportar informe DOCX + DashboardView simplificado a 4 tarjetas
- services/report-export.ts: generación de DOCX con docx (npm)
  incluye: estado general, épicas, tabla HUs, bloqueos, curva S, sesiones
- DashboardView: botón Exportar al lado del badge del proyecto
  stats simplificados a 4 tarjetas (combinada épicas/HUs/progreso, QA, sesiones, curva S)
- SCurveChart: modo compacto con área gradient fill estilo shadcn
- Notificación inline: soporte para tipo 'info' (azul)
2026-05-30 01:00:02 -05:00
ricardo 97950adf8b Settings: prompts como tarjetas + modal editor TipTap (negrita, listas, código, etc.)
- PromptEditorModal.vue: nuevo componente con editor TipTap rich text
  toolbar: bold, italic, headings, bullet/ordered lists, code, quote, undo/redo
- SettingsView: prompts se muestran como tarjetas con preview y botón editar
  clic abre modal con editor completo + guardar/restaurar
- Instalado @tiptap/vue-3 con starter-kit y extensiones (placeholder, code, link, table)
2026-05-30 00:48:21 -05:00
ricardo b21214d1f1 Teams integration: webhook messaging + configuración en Settings + plugin-http
- @tauri-apps/plugin-http instalado (npm + Cargo.toml + capabilities)
- src-tauri/src/main.rs: registrado tauri_plugin_http::init()
- services/teams.ts: servicio para enviar mensajes Teams via webhook
  soporta Tauri plugin y browser fetch con fallback automático
  incluye notifyHUCreated, notifyBlockerLogged, sendTestMessage
- SettingsView: nueva sección Teams con input de webhook URL + botón test
- ChartAreaInteractive.vue: removido import no usado (@unovis/vue)
2026-05-29 23:55:37 -05:00
ricardo bf81b8e04b Fix: categoría bloqueo pm renombrada a Lambda 2026-05-29 23:28:39 -05:00
ricardo ad715a9409 DashboardView: integrar SCurveChart con métricas y curvas planificada/real 2026-05-29 23:28:18 -05:00
ricardo f09a249caa S-Curve + Blocker tracking: curva planificada/real, bloqueos categorizados, proyección
- services/blocker-log.ts: registro de bloqueos con categorías (cliente/pm/tech/external)
  Cada bloqueo: descripción, SP afectados, horas perdidas, categoría
- services/s-curve.ts: cálculo de curva S planificada (distribución lineal SP entre fechas)
  + curva real (desde status + fechas) + proyección (velocidad semanal)
  + métricas: SPI, velocity, blocked hours, estimated end date
- db.ts: schema v10 con tabla blockers (++id, projectId, category)
- components/SCurveChart.vue: gráfico SVG con curvas planificada (azul) y real (verde)
  + tarjetas de métricas (SP total, velocidad, SPI, proyección)
  + resumen de horas bloqueadas imputables al cliente
2026-05-29 23:27:50 -05:00
ricardo d8a5917bad Prompts: regla 1 SP = 1 hora en analysis_transcription y project_gap FASE 2 2026-05-29 23:19:59 -05:00
ricardo 0958d52fa9 Fix: status 6 es QA-Client no bloqueado + isDone() ampliado
- isBlocked(): removido '6' (QA-Client) de la lista de bloqueados
- isDone(): nuevo helper que incluye 6, 7, qa-client, ready to deploy como completados
- doneHUs y epicProgress ahora usan isDone() consistente
2026-05-29 22:55:57 -05:00
ricardo bbd367a266 Fix: mapeo de prioridad en Alpha (3=Alta, 2=Media, 1=Baja) 2026-05-29 22:54:02 -05:00
ricardo cb0e8067b6 K-12 Priorizador diario: HUs vencidas, hoy, esta semana y bloqueadas
- PrioritizerCard.vue: nuevo componente con secciones de priorización
- Tarjetas resumen: total HUs, en progreso, vencidas, bloqueadas
- Listas agrupadas: vencidas (rojo), hoy (ámbar), esta semana, bloqueadas
- Cada item: código, título, prioridad, fecha
- KappaUserStory: agregados end_date, sprint, initial_date
- enrichHU: pasa end_date y sprint al objeto enriquecido
2026-05-29 22:50:38 -05:00
ricardo 2073d936e2 Epics table: barra de progreso (% HUs completadas) + removida columna Asignado
- epicProgress computed: agrupa HUs por _epicCode, calcula % completadas
- getEpicProgress(epic): resuelve progreso para cada épica
- Barra visual con colores: verde=100%, azul=>0%, gris=0%
2026-05-29 22:38:52 -05:00
ricardo 3035351e6f Sistema de códigos jerárquicos 2-niveles + asignación determinista post-IA
- hierarchy.ts: Spike (S) agregado, buildHierarchyPath genera [E01-F04] (2 niveles)
  Legacy [E05-F04-U01] preservado (regex opcional 3er segmento)
- hierarchy-generator.ts (nuevo): analyzeExisting() computa contadores por épica+tipo
  assignEpicCodes() asigna E{max+1} secuencial
  assignItemCodes() asigna {epic}-{tipo}{n+1} a cada HU dentro de su épica
- project-analyzer.ts: post-procesa épicas y HUs con generador de códigos
  saveEpicDrafts usa epicCode en metadata y título con [E01]
- prompts-db.ts: prompt FASE 2 instruye a la IA a no generar códigos
- workitems.ts: EnrichedEpic._epicCode, EnrichedUserStory._epicCode/_itemCode
- DashboardView: muestra códigos en drafts y tabla de épicas
2026-05-29 18:13:17 -05:00
ricardo 9cf12b482f Análisis IA en dos fases: épicas primero, luego HUs vinculadas con epic_development
- project-analyzer.ts: dividido en analyzeProjectEpics() y analyzeProjectHUs()
  Fase 1: genera solo épicas con linkedHuTitles
  Fase 2: genera HUs dentro de épicas con epicName
- saveEpicDrafts / saveHUDrafts separados para cada tipo
- DashboardView: dos botones '1. Generar Épicas' y '2. Generar HUs'
- pushDraft épica: al crear en KAPPA, actualiza metadata de HUs vinculadas con epicDevelopment
- pushDraft HU: envía epic_development + payload completo (feature, sprint, asignado_a, etc.)
- project_gap prompt: instrucciones separadas para FASE 1 (épicas) y FASE 2 (HUs)
2026-05-29 17:33:47 -05:00
29 changed files with 11524 additions and 200 deletions
+311 -15
View File
@@ -12,6 +12,17 @@
"@tanstack/vue-table": "^8.21.3", "@tanstack/vue-table": "^8.21.3",
"@tauri-apps/api": "^2.11.0", "@tauri-apps/api": "^2.11.0",
"@tauri-apps/cli": "^2.11.2", "@tauri-apps/cli": "^2.11.2",
"@tauri-apps/plugin-http": "^2.5.9",
"@tiptap/extension-code-block-lowlight": "^3.23.6",
"@tiptap/extension-image": "^3.23.6",
"@tiptap/extension-link": "^3.23.6",
"@tiptap/extension-placeholder": "^3.23.6",
"@tiptap/extension-table": "^3.23.6",
"@tiptap/extension-table-cell": "^3.23.6",
"@tiptap/extension-table-header": "^3.23.6",
"@tiptap/extension-table-row": "^3.23.6",
"@tiptap/starter-kit": "^3.23.6",
"@tiptap/vue-3": "^3.23.6",
"@vueuse/core": "^14.3.0", "@vueuse/core": "^14.3.0",
"ag-grid-community": "^35.3.0", "ag-grid-community": "^35.3.0",
"ag-grid-vue3": "^35.3.0", "ag-grid-vue3": "^35.3.0",
@@ -19,10 +30,13 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dexie": "^4.0.4", "dexie": "^4.0.4",
"dnd-kit-vue": "^0.0.2", "dnd-kit-vue": "^0.0.2",
"docx": "^9.7.1",
"lowlight": "^3.3.0",
"lucide-vue-next": "^1.0.0", "lucide-vue-next": "^1.0.0",
"mammoth": "^1.12.0", "mammoth": "^1.12.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"reka-ui": "^2.9.8", "reka-ui": "^2.9.8",
"shadcn-minimal-tiptap": "github:Aslam97/minimal-tiptap",
"shadcn-vue": "^2.7.3", "shadcn-vue": "^2.7.3",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
@@ -178,12 +192,16 @@
"@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="],
"@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="],
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
"@floating-ui/vue": ["@floating-ui/vue@1.1.11", "", { "dependencies": { "@floating-ui/dom": "^1.7.6", "@floating-ui/utils": "^0.2.11", "vue-demi": ">=0.13.0" } }, "sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw=="], "@floating-ui/vue": ["@floating-ui/vue@1.1.11", "", { "dependencies": { "@floating-ui/dom": "^1.7.6", "@floating-ui/utils": "^0.2.11", "vue-demi": ">=0.13.0" } }, "sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw=="],
"@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="],
"@hookform/resolvers": ["@hookform/resolvers@3.10.0", "", { "peerDependencies": { "react-hook-form": "^7.0.0" } }, "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag=="],
"@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="],
"@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="],
@@ -286,6 +304,82 @@
"@preact/signals-core": ["@preact/signals-core@1.14.2", "", {}, "sha512-RZHdBj9ZF4n40Rp4jS052EHHjBWf96P9oNdXPfhQTovCuWY9iQn3Gq+gOTJSgBO9A/JBuPfMOWsSX/lIU9Pc/A=="], "@preact/signals-core": ["@preact/signals-core@1.14.2", "", {}, "sha512-RZHdBj9ZF4n40Rp4jS052EHHjBWf96P9oNdXPfhQTovCuWY9iQn3Gq+gOTJSgBO9A/JBuPfMOWsSX/lIU9Pc/A=="],
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="],
"@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
"@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="],
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="],
"@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="],
"@radix-ui/react-icons": ["@radix-ui/react-icons@1.3.2", "", { "peerDependencies": { "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" } }, "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g=="],
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
"@radix-ui/react-label": ["@radix-ui/react-label@2.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A=="],
"@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="],
"@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="],
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="],
"@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
"@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="],
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
"@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="],
"@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="],
"@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="],
"@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="],
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
"@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
"@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="],
"@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="],
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="],
"@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="],
"@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="],
"@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="],
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.4", "", { "os": "android", "cpu": "arm" }, "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.4", "", { "os": "android", "cpu": "arm" }, "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.4", "", { "os": "android", "cpu": "arm64" }, "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.4", "", { "os": "android", "cpu": "arm64" }, "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw=="],
@@ -406,18 +500,114 @@
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.11.2", "", { "os": "win32", "cpu": "x64" }, "sha512-d2JchlFIpZevZVReyqhQOekJmb1UH3rhZ5VX6sH3ty9ETE0TKQavpihvoScUXfKKpW6HZC0MrFGRU0ZtD+w3gA=="], "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.11.2", "", { "os": "win32", "cpu": "x64" }, "sha512-d2JchlFIpZevZVReyqhQOekJmb1UH3rhZ5VX6sH3ty9ETE0TKQavpihvoScUXfKKpW6HZC0MrFGRU0ZtD+w3gA=="],
"@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.9", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-lCiY0+vs4HvIUSvZrBs8TC3TiCB0MOPRmiUjTq4prW7SlcJE2jdLeT6KBsJrT9Tlplufl7W1pY6SFAO3gCWxDA=="],
"@tiptap/core": ["@tiptap/core@3.23.6", "", { "peerDependencies": { "@tiptap/pm": "3.23.6" } }, "sha512-MRB3pHz4Oxqmcawh0cQ5iOGdY5xtNYp/1CoK7hdTLzw5K0C6/gTC2VvanB1R4INaB6EpBkxG/GiWkVirDRnuXw=="],
"@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-2RmnqNqTltZ2k1F7IfjoDNs935Uq4rRDR7d98mqkg3OlDktcQIyBpv0t9dTay6H5bkQeZUuS8ogK2S1E8Edjug=="],
"@tiptap/extension-bold": ["@tiptap/extension-bold@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-1LMhjnytdbbhWHSoOwnLxZAOQZWPkKyXVCNmaIk0Mhi4tLPUXptG4qKS5sVYTCveE5H6IBPFrbgBFi5dMI6krA=="],
"@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.23.6", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6" } }, "sha512-Mwkyp9LkDHFbqmWRIkp63FinRxFu3ajC4qSb9t4mnHsb4kAdbNLLsGtbFg+le0SWk4CxGwAOwM7SzeJ+6UGqCA=="],
"@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.23.6", "", { "peerDependencies": { "@tiptap/extension-list": "3.23.6" } }, "sha512-RMRgfXZykr/13X8UBOwvpgysVOo9KchwqMoEbvqQSj4YFfU56iIn59C8sbxiQ1sKfeltUf0wH4fPc0I4iwKqAA=="],
"@tiptap/extension-code": ["@tiptap/extension-code@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-KG8KXFYyLrtYvT7AZ1WGV61ofx8pDe5g9pH658MERxqQGii+Pyfc6xkz04l7XeBts/7+571UQp/0O7i/z560TA=="],
"@tiptap/extension-code-block": ["@tiptap/extension-code-block@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6" } }, "sha512-4kccgcn5yHThxrzsIhJny3EwfEZYIk+BjUCL4uIuzOyWvExtGhZ6JMHVCZeMhI8D1/bX1LNkkAKN5DXPzH4lXQ=="],
"@tiptap/extension-code-block-lowlight": ["@tiptap/extension-code-block-lowlight@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6", "@tiptap/extension-code-block": "3.23.6", "@tiptap/pm": "3.23.6", "highlight.js": "^11", "lowlight": "^2 || ^3" } }, "sha512-GuB7qDfWKp02FoUFB5SwjkczayE5JMlbgNRHr5H1fMN7j9ofsV4SijvbZpocj5X6kSjNyOnx5nhdOyPwlsvOgw=="],
"@tiptap/extension-color": ["@tiptap/extension-color@3.23.6", "", { "peerDependencies": { "@tiptap/extension-text-style": "3.23.6" } }, "sha512-2WxYc+X+OGFXbq/Ak0VXODEzfK1l5wz33oTWSuhdSw44HAQM7+SSyiDnMCXDQdqi063ANqMmQmlw5WngjQ6wCA=="],
"@tiptap/extension-document": ["@tiptap/extension-document@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-XDAIgG9KcKumFM9KJWUEUhXPbFIhhl47bfy5GknareWTRKke85rcoj/oxKKO9ihLZr8JfpbXjqnS4SCm5yhYPw=="],
"@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.23.6", "", { "peerDependencies": { "@tiptap/extensions": "3.23.6" } }, "sha512-+XWEoRKf3lXxi7Le1aOM2xU1XHwxICGpXjT3m4QaYqUgIpsq8gQEuso6kVg8DnTD7biKQs6+oIQ0o2b/gTW9WA=="],
"@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.23.6", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6" } }, "sha512-2kjuDcEq69lEcECl75xqY5MyzUSh2zcC5aLrpwP1WwhJz5bxsIFHiaps5AP6h9R4A+ZBj5b2haay2Y1wDUU3VA=="],
"@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.23.6", "", { "peerDependencies": { "@tiptap/extensions": "3.23.6" } }, "sha512-wbKmxXsszxWacEkrHucRpSQbiKjz4fmOebD6OVyL9AcrmlbxNk8vcM3iyh/8cVeRy09XY+morM165t/u7/z4IQ=="],
"@tiptap/extension-hard-break": ["@tiptap/extension-hard-break@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-KeUm+tkUfIVSX9QM9XOIhaay0Fn36sLKUo5NVYjN3uJaxFvaZXZmTlxdO85OTdgF2P5sqh9LomrIgliaFRGk4w=="],
"@tiptap/extension-heading": ["@tiptap/extension-heading@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-A/0jPhxnUh9THSZymlu0OGPZe1wdFdwHAXnRCmqvYUCwJjrG7LCC/ahzmcj1tcNzI9hgHyuYPSfev8RXYrNu/w=="],
"@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6" } }, "sha512-hEUlz4H+I64r+TH6LCuNCRgO7JTHncXGmx9+WbU69EOfY8O0ZurcgeJc8HeiAKL+r9YuC1e5YHfFxgCaaC0jlg=="],
"@tiptap/extension-image": ["@tiptap/extension-image@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-vvNGxArvD2dW+XvV0KdYovRVUzCy8QVNulc2r5pV7umnG1E6cCmMkiHiif8J2ePJu2KtysAvJQe0iF+UqueGMw=="],
"@tiptap/extension-italic": ["@tiptap/extension-italic@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-wol5KdwCPAvpiYhH9PLlvO8ZnJHwZtIboVevrfOGgBcKlXRA3dedR4OAMXHnUtkkzu9KtliLg1+TYzEx4JZG9Q=="],
"@tiptap/extension-link": ["@tiptap/extension-link@3.23.6", "", { "dependencies": { "linkifyjs": "^4.3.3" }, "peerDependencies": { "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6" } }, "sha512-KNZz7z7P2/qbQsx5bPAbSPjrKDg1VHsedGlLHJCr8U2VRD5VgmDLkMpkouP1CsDg15qgyUKv/nDib5KgPpLNWA=="],
"@tiptap/extension-list": ["@tiptap/extension-list@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6" } }, "sha512-z6vj9+Qht2sjdQkyyHcUpsC/yCIZqTrQiyHDhs/HGKrfvoANyAZGpqdNeKf1wSyjIso+27tQuIH5NDfk8ygyNw=="],
"@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.23.6", "", { "peerDependencies": { "@tiptap/extension-list": "3.23.6" } }, "sha512-3zzyhdkUWcHVpXuvy6KiIwjh29rbH6gEDEqPQqHLrl1XGnO9pnShC7pSHctlCDjmcx3O4n9cd4QMtVBlUerbiA=="],
"@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.23.6", "", { "peerDependencies": { "@tiptap/extension-list": "3.23.6" } }, "sha512-x8bPcLViGzg/RAmQM/XtmfqIwQ/Pv9Q8mkd+OgfUiTqjeJqKwVQmiqbLFNa7zw81+H61M+HDU+qGAaQ3vRIMjw=="],
"@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.23.6", "", { "peerDependencies": { "@tiptap/extension-list": "3.23.6" } }, "sha512-1m/wWB/ZtXcmG2vNdiUkCqsOgqv5vBjCv/mVaHhF9OvV+zQS8YDjoWE7zEuT/GgELdT77Xq8lHrn4nCDudB3/A=="],
"@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-+7m58LUSncodjrIyXks4RZ3tLNYrvgT77wRR4l3HnM5OABY3GDsDTqi7c1t1yI29NVOSk/DUacqy6UwYAj1DGg=="],
"@tiptap/extension-placeholder": ["@tiptap/extension-placeholder@3.23.6", "", { "peerDependencies": { "@tiptap/extensions": "3.23.6" } }, "sha512-8I6b2aevF74aLgymKMxbDxSLxWA2y+2dh0zZDeI8sRZ2m6WHHes+Kyuuwkq1HIPcR+ZLpbec74cmf6lcL/yvqQ=="],
"@tiptap/extension-strike": ["@tiptap/extension-strike@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-oF7FEZ37f15aCe5kPgzGDYf/m+hr7VdQ/Ko/Hds/UM9pX7AG1fdtmRrl6wqkRqDM/incZaC/AQR2/Dpo2VCNGQ=="],
"@tiptap/extension-table": ["@tiptap/extension-table@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6" } }, "sha512-XbhZXjhsS6AP7ThoZxjAnNs+NiR81YRori25l6E+ORqB7quiPkIXOAi5h4AIpkn/CYIqze6ere11lWsYpDjtaQ=="],
"@tiptap/extension-table-cell": ["@tiptap/extension-table-cell@3.23.6", "", { "peerDependencies": { "@tiptap/extension-table": "3.23.6" } }, "sha512-hS9TmmvRlT9/ikT+0ukACS+hmJuii4zQaH47cg3oJkz/Fv7O7tL7GZniKtK6l2OUZGPhY+4SV2RkDB6bD7DXfw=="],
"@tiptap/extension-table-header": ["@tiptap/extension-table-header@3.23.6", "", { "peerDependencies": { "@tiptap/extension-table": "3.23.6" } }, "sha512-D6o0a1cJXUU0xWakainBFGPnGHinQkPcdu1YqGd/PoFANY38lnuZt/NW2O/OLfLXu5LXDRfpqF1+dsKww27dUA=="],
"@tiptap/extension-table-row": ["@tiptap/extension-table-row@3.23.6", "", { "peerDependencies": { "@tiptap/extension-table": "3.23.6" } }, "sha512-OauWVzkyRQg0rKOqM/a3PuKPc1S7YXMb1LRN7Nh8Ytvglvd7GFRTbl1lVqdZRaz4Jzopag4PQnriIZfMPUpxWw=="],
"@tiptap/extension-text": ["@tiptap/extension-text@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-ipoC2TkIAIOTiF5ByiGgvQB1DqDyfP90wrUB3mohBcgvp7lQnwHszCDGv8dNnmcUek8uXV/uoLu2VXeVQlxjPA=="],
"@tiptap/extension-text-style": ["@tiptap/extension-text-style@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-Auasgj469wkQ5ip+Zi2gaRzvqxx9qKG58+1mkT8yPE3QAGnOIg/AaKyQ7pTV77UyL3FHvLnU+KsWCad+qcobww=="],
"@tiptap/extension-typography": ["@tiptap/extension-typography@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-eo/+e5TJ4cPfkk6ugMaoW+UVfTC3CEU97EH2g72H+L90qywnOJalrRNXTkdSEbYxGtLs9xthTAHpy2YImf4r1A=="],
"@tiptap/extension-underline": ["@tiptap/extension-underline@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6" } }, "sha512-P55wGIZGYTVH92Fq0cgI4/O9AhLCaJC3hhxg15RSERP5/YegM9eJHDK/GQ1EE/DvYA+xpYGOV6agKwAUqfA/Iw=="],
"@tiptap/extensions": ["@tiptap/extensions@3.23.6", "", { "peerDependencies": { "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6" } }, "sha512-X09/Db1teB+ifXzDGVVFmOeQRx7wTAayE9/280spxpsHkHZvJ5bHRvWIzUzviMIjbBz+NPDIKYPK7gMfh9iaig=="],
"@tiptap/markdown": ["@tiptap/markdown@3.23.6", "", { "dependencies": { "marked": "^17.0.1" }, "peerDependencies": { "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6" } }, "sha512-11RS+WswVAtAJMn1CaXN1YblMdLn69I6TeFVoHwJXbprV8M45tjyID9uV0WoGYPnpjhfepPhaG7kivgAu5XukQ=="],
"@tiptap/pm": ["@tiptap/pm@3.23.6", "", { "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-keymap": "^1.2.2", "prosemirror-model": "^1.24.1", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" } }, "sha512-in5CaMaWlJcH2A1q6GJKFtrodE8WLS3M9tIi/f89jPmIVHJShpodC0KZDNyJkrVBQomYk0DEh86Utm6ASXzQww=="],
"@tiptap/react": ["@tiptap/react@3.23.6", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "fast-equals": "^5.3.3", "use-sync-external-store": "^1.4.0" }, "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.23.6", "@tiptap/extension-floating-menu": "^3.23.6" }, "peerDependencies": { "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Tw9KZkYqFMk3vaJAEQKqEYIO/iq3cSJe7OUEGBul4k4GaMQeLItLf5EYhUd0GIPXci1WVVPNntKJsHfX25M37w=="],
"@tiptap/starter-kit": ["@tiptap/starter-kit@3.23.6", "", { "dependencies": { "@tiptap/core": "^3.23.6", "@tiptap/extension-blockquote": "^3.23.6", "@tiptap/extension-bold": "^3.23.6", "@tiptap/extension-bullet-list": "^3.23.6", "@tiptap/extension-code": "^3.23.6", "@tiptap/extension-code-block": "^3.23.6", "@tiptap/extension-document": "^3.23.6", "@tiptap/extension-dropcursor": "^3.23.6", "@tiptap/extension-gapcursor": "^3.23.6", "@tiptap/extension-hard-break": "^3.23.6", "@tiptap/extension-heading": "^3.23.6", "@tiptap/extension-horizontal-rule": "^3.23.6", "@tiptap/extension-italic": "^3.23.6", "@tiptap/extension-link": "^3.23.6", "@tiptap/extension-list": "^3.23.6", "@tiptap/extension-list-item": "^3.23.6", "@tiptap/extension-list-keymap": "^3.23.6", "@tiptap/extension-ordered-list": "^3.23.6", "@tiptap/extension-paragraph": "^3.23.6", "@tiptap/extension-strike": "^3.23.6", "@tiptap/extension-text": "^3.23.6", "@tiptap/extension-underline": "^3.23.6", "@tiptap/extensions": "^3.23.6", "@tiptap/pm": "^3.23.6" } }, "sha512-gykwtGWrnWCmtql1hid3opac/KV8zQvOAnu3bTqIqcHrn1FusbUwKmNzavSbfGvcktHM3hFjb35W48JyVLyu/A=="],
"@tiptap/vue-3": ["@tiptap/vue-3@3.23.6", "", { "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.23.6", "@tiptap/extension-floating-menu": "^3.23.6" }, "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "3.23.6", "@tiptap/pm": "3.23.6", "vue": "^3.0.0" } }, "sha512-MQ9uX1PGKqOrDpdq5O72zJIME7BA8AatcEDUmiJvEC8MH7cF2hpKBVt9TeBE+zTr1jrEuGG8wz76gaJsiykwhQ=="],
"@ts-morph/common": ["@ts-morph/common@0.28.1", "", { "dependencies": { "minimatch": "^10.0.1", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.14" } }, "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g=="], "@ts-morph/common": ["@ts-morph/common@0.28.1", "", { "dependencies": { "minimatch": "^10.0.1", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.14" } }, "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g=="],
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="],
"@types/react": ["@types/react@19.2.15", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
"@unovue/detypes": ["@unovue/detypes@0.8.5", "", { "dependencies": { "@babel/core": "^7.24.5", "@babel/preset-typescript": "^7.24.1", "@vue/compiler-dom": "^3.4.27", "@vue/compiler-sfc": "^3.4.27", "@vuedx/template-ast-types": "0.7.1", "fast-glob": "^3.3.2", "prettier": "^3.2.5", "typescript": "^5.4.5" }, "bin": { "detypes": "detype.js" } }, "sha512-Yz4JeWOHGa+w/3YudVdng8hgN/VGW9cvp8xmFkmPPFzalGblLPPSpIRiwVo853yLstMZO2LLwe0vOoLAQsUQXw=="], "@unovue/detypes": ["@unovue/detypes@0.8.5", "", { "dependencies": { "@babel/core": "^7.24.5", "@babel/preset-typescript": "^7.24.1", "@vue/compiler-dom": "^3.4.27", "@vue/compiler-sfc": "^3.4.27", "@vuedx/template-ast-types": "0.7.1", "fast-glob": "^3.3.2", "prettier": "^3.2.5", "typescript": "^5.4.5" }, "bin": { "detypes": "detype.js" } }, "sha512-Yz4JeWOHGa+w/3YudVdng8hgN/VGW9cvp8xmFkmPPFzalGblLPPSpIRiwVo853yLstMZO2LLwe0vOoLAQsUQXw=="],
"@vercel/analytics": ["@vercel/analytics@1.6.1", "", { "peerDependencies": { "@remix-run/react": "^2", "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@remix-run/react", "@sveltejs/kit", "next", "react", "svelte", "vue", "vue-router"] }, "sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg=="],
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.4", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA=="], "@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.4", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA=="],
"@volar/language-core": ["@volar/language-core@2.4.15", "", { "dependencies": { "@volar/source-map": "2.4.15" } }, "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA=="], "@volar/language-core": ["@volar/language-core@2.4.15", "", { "dependencies": { "@volar/source-map": "2.4.15" } }, "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA=="],
@@ -616,10 +806,16 @@
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
"dexie": ["dexie@4.4.2", "", {}, "sha512-zMtV8q79EFE5U8FKZvt0Y/77PCU/Hr/RDxv1EDeo228L+m/HTbeN2AjoQm674rhQCX8n3ljK87lajt7UQuZfvw=="], "dexie": ["dexie@4.4.2", "", {}, "sha512-zMtV8q79EFE5U8FKZvt0Y/77PCU/Hr/RDxv1EDeo228L+m/HTbeN2AjoQm674rhQCX8n3ljK87lajt7UQuZfvw=="],
"diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
@@ -628,6 +824,8 @@
"dnd-kit-vue": ["dnd-kit-vue@0.0.2", "", { "dependencies": { "@dnd-kit/abstract": "^0.1.19", "@dnd-kit/dom": "^0.1.19", "@dnd-kit/state": "^0.1.19", "@vueuse/core": "^13.4.0", "reka-ui": "^2.3.1" }, "peerDependencies": { "vue": "^3.3.0" } }, "sha512-2ZQfqTulZI7vqFiYscV7VMQRXSEryjanlaCY5BvkDf5i+whEAvOKSckyBa6SK8LCPaF5f/IIcUhfh6TnbaWq3A=="], "dnd-kit-vue": ["dnd-kit-vue@0.0.2", "", { "dependencies": { "@dnd-kit/abstract": "^0.1.19", "@dnd-kit/dom": "^0.1.19", "@dnd-kit/state": "^0.1.19", "@vueuse/core": "^13.4.0", "reka-ui": "^2.3.1" }, "peerDependencies": { "vue": "^3.3.0" } }, "sha512-2ZQfqTulZI7vqFiYscV7VMQRXSEryjanlaCY5BvkDf5i+whEAvOKSckyBa6SK8LCPaF5f/IIcUhfh6TnbaWq3A=="],
"docx": ["docx@9.7.1", "", { "dependencies": { "@types/node": "^25.2.3", "hash.js": "^1.1.7", "jszip": "^3.10.1", "nanoid": "^5.1.3", "xml": "^1.0.1", "xml-js": "^1.6.8" } }, "sha512-ilXFf9Moz47ABjFpDiA5s1w9lpb4EFSp7+5iiJSbfyYDM+bpZdAgLlSr7fW4aXhVe/E+F6QCv0EvRVFEd5CsWg=="],
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
@@ -708,6 +906,8 @@
"fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
"fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
@@ -758,6 +958,8 @@
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
"get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="], "get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
@@ -768,7 +970,7 @@
"giget": ["giget@3.2.0", "", { "bin": { "giget": "dist/cli.mjs" } }, "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A=="], "giget": ["giget@3.2.0", "", { "bin": { "giget": "dist/cli.mjs" } }, "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A=="],
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@@ -780,10 +982,14 @@
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"hash.js": ["hash.js@1.1.7", "", { "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA=="],
"hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="],
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
"highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
"hono": ["hono@4.12.22", "", {}, "sha512-7fvVPbB92zNRsQke+uiRGwtTuef0tB2Dg4hWxYfFNvkQhIltWoyi0ONReM5LWA+jJWS3nfT5lTq+qbsIpX0IQw=="], "hono": ["hono@4.12.22", "", {}, "sha512-7fvVPbB92zNRsQke+uiRGwtTuef0tB2Dg4hWxYfFNvkQhIltWoyi0ONReM5LWA+jJWS3nfT5lTq+qbsIpX0IQw=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
@@ -896,6 +1102,8 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"linkifyjs": ["linkifyjs@4.3.3", "", {}, "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
@@ -908,7 +1116,11 @@
"lop": ["lop@0.4.2", "", { "dependencies": { "duck": "^0.1.12", "option": "~0.2.1", "underscore": "^1.13.1" } }, "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw=="], "lop": ["lop@0.4.2", "", { "dependencies": { "duck": "^0.1.12", "option": "~0.2.1", "underscore": "^1.13.1" } }, "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw=="],
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "lowlight": ["lowlight@3.3.0", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.0.0", "highlight.js": "~11.11.0" } }, "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ=="],
"lru-cache": ["lru-cache@11.5.0", "", {}, "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA=="],
"lucide-react": ["lucide-react@0.486.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-xWop/wMsC1ikiEVLZrxXjPKw4vU/eAip33G2mZHgbWnr4Nr5Rt4Vx4s/q1D3B/rQVbxjOuqASkEZcUxDEKzecw=="],
"lucide-vue-next": ["lucide-vue-next@1.0.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-V6SPvx1IHTj/UY+FrIYWV5faISsPSb8BnWSFDxAtezWKvWc9ZZ40PDrdu1/Qb5vg4lHWr1hs1BAMGVGm6V1Xdg=="], "lucide-vue-next": ["lucide-vue-next@1.0.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-V6SPvx1IHTj/UY+FrIYWV5faISsPSb8BnWSFDxAtezWKvWc9ZZ40PDrdu1/Qb5vg4lHWr1hs1BAMGVGm6V1Xdg=="],
@@ -918,6 +1130,8 @@
"mammoth": ["mammoth@1.12.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.6", "argparse": "~1.0.3", "base64-js": "^1.5.1", "bluebird": "~3.4.0", "dingbat-to-unicode": "^1.0.1", "jszip": "^3.7.1", "lop": "^0.4.2", "path-is-absolute": "^1.0.0", "underscore": "^1.13.1", "xmlbuilder": "^10.0.0" }, "bin": { "mammoth": "bin/mammoth" } }, "sha512-cwnK1RIcRdDMi2HRx2EXGYlxqIEh0Oo3bLhorgnsVJi2UkbX1+jKxuBNR9PC5+JaX7EkmJxFPmo6mjLpqShI2w=="], "mammoth": ["mammoth@1.12.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.6", "argparse": "~1.0.3", "base64-js": "^1.5.1", "bluebird": "~3.4.0", "dingbat-to-unicode": "^1.0.1", "jszip": "^3.7.1", "lop": "^0.4.2", "path-is-absolute": "^1.0.0", "underscore": "^1.13.1", "xmlbuilder": "^10.0.0" }, "bin": { "mammoth": "bin/mammoth" } }, "sha512-cwnK1RIcRdDMi2HRx2EXGYlxqIEh0Oo3bLhorgnsVJi2UkbX1+jKxuBNR9PC5+JaX7EkmJxFPmo6mjLpqShI2w=="],
"marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
@@ -938,6 +1152,8 @@
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="],
"minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], "minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -948,12 +1164,14 @@
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
"nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "nanoid": ["nanoid@5.1.11", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
"node-html-parser": ["node-html-parser@7.1.0", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ=="], "node-html-parser": ["node-html-parser@7.1.0", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ=="],
@@ -990,6 +1208,8 @@
"ora": ["ora@9.4.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" } }, "sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ=="], "ora": ["ora@9.4.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" } }, "sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ=="],
"orderedmap": ["orderedmap@2.1.1", "", {}, "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="],
"p-event": ["p-event@6.0.1", "", { "dependencies": { "p-timeout": "^6.1.2" } }, "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w=="], "p-event": ["p-event@6.0.1", "", { "dependencies": { "p-timeout": "^6.1.2" } }, "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
@@ -1050,6 +1270,30 @@
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
"prosemirror-changeset": ["prosemirror-changeset@2.4.1", "", { "dependencies": { "prosemirror-transform": "^1.0.0" } }, "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw=="],
"prosemirror-commands": ["prosemirror-commands@1.7.1", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.10.2" } }, "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w=="],
"prosemirror-dropcursor": ["prosemirror-dropcursor@1.8.2", "", { "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0", "prosemirror-view": "^1.1.0" } }, "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw=="],
"prosemirror-gapcursor": ["prosemirror-gapcursor@1.4.1", "", { "dependencies": { "prosemirror-keymap": "^1.0.0", "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-view": "^1.0.0" } }, "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw=="],
"prosemirror-history": ["prosemirror-history@1.5.0", "", { "dependencies": { "prosemirror-state": "^1.2.2", "prosemirror-transform": "^1.0.0", "prosemirror-view": "^1.31.0", "rope-sequence": "^1.3.0" } }, "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg=="],
"prosemirror-keymap": ["prosemirror-keymap@1.2.3", "", { "dependencies": { "prosemirror-state": "^1.0.0", "w3c-keyname": "^2.2.0" } }, "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw=="],
"prosemirror-model": ["prosemirror-model@1.25.7", "", { "dependencies": { "orderedmap": "^2.0.0" } }, "sha512-A79aN8QEFUwI6cax8Yq4Rpcx1TJZ3Kagn+ii7qLo4/V8H3mMiHrhFyhTyHHvpSnOgMPpWiDGSwM3etwrxE50ug=="],
"prosemirror-schema-list": ["prosemirror-schema-list@1.5.1", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.7.3" } }, "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q=="],
"prosemirror-state": ["prosemirror-state@1.4.4", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", "prosemirror-view": "^1.27.0" } }, "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw=="],
"prosemirror-tables": ["prosemirror-tables@1.8.5", "", { "dependencies": { "prosemirror-keymap": "^1.2.3", "prosemirror-model": "^1.25.4", "prosemirror-state": "^1.4.4", "prosemirror-transform": "^1.10.5", "prosemirror-view": "^1.41.4" } }, "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw=="],
"prosemirror-transform": ["prosemirror-transform@1.12.0", "", { "dependencies": { "prosemirror-model": "^1.21.0" } }, "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w=="],
"prosemirror-view": ["prosemirror-view@1.41.8", "", { "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } }, "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
@@ -1064,6 +1308,20 @@
"rc9": ["rc9@3.0.1", "", { "dependencies": { "defu": "^6.1.6", "destr": "^2.0.5" } }, "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ=="], "rc9": ["rc9@3.0.1", "", { "dependencies": { "defu": "^6.1.6", "destr": "^2.0.5" } }, "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ=="],
"react": ["react@19.2.6", "", {}, "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q=="],
"react-dom": ["react-dom@19.2.6", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.6" } }, "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g=="],
"react-hook-form": ["react-hook-form@7.76.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-rYM7tPiWlu3nZchkR/ex7piyzui2vFPyaLnXnI/RnblB/L4qfMmyses8llJVtF1NpE9WBBsJlGtcSZzPCXW1qQ=="],
"react-medium-image-zoom": ["react-medium-image-zoom@5.4.5", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-58QSIRK6X3uw2fSTejJRnH0JuKTZl7ZJYX+sAMaYx4YTEm33gsNdnP5RuQSCnBiAvisQeErqZWAT31bR89WB6g=="],
"react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
@@ -1084,6 +1342,8 @@
"rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="], "rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="],
"rope-sequence": ["rope-sequence@1.3.4", "", {}, "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="],
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
"run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
@@ -1096,6 +1356,8 @@
"sax": ["sax@1.2.4", "", {}, "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="], "sax": ["sax@1.2.4", "", {}, "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="], "semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="],
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
@@ -1106,6 +1368,8 @@
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"shadcn-minimal-tiptap": ["shadcn-minimal-tiptap@github:Aslam97/minimal-tiptap#93b5559", { "dependencies": { "@hookform/resolvers": "^3.10.0", "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", "@tiptap/extension-bubble-menu": "^3.14.0", "@tiptap/extension-code-block-lowlight": "^3.14.0", "@tiptap/extension-color": "^3.14.0", "@tiptap/extension-horizontal-rule": "^3.14.0", "@tiptap/extension-image": "^3.14.0", "@tiptap/extension-list": "^3.14.0", "@tiptap/extension-table": "^3.14.0", "@tiptap/extension-text-style": "^3.14.0", "@tiptap/extension-typography": "^3.14.0", "@tiptap/extensions": "^3.14.0", "@tiptap/markdown": "^3.14.0", "@tiptap/pm": "^3.14.0", "@tiptap/react": "^3.14.0", "@tiptap/starter-kit": "^3.14.0", "@vercel/analytics": "^1.6.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "glob": "^11.1.0", "lowlight": "^3.3.0", "lucide-react": "^0.486.0", "next-themes": "^0.4.6", "react": "^19.2.1", "react-dom": "^19.2.1", "react-hook-form": "^7.68.0", "react-medium-image-zoom": "^5.4.0", "sonner": "^1.7.4", "tailwind-merge": "^3.4.0", "zod": "^3.25.76" } }, "Aslam97-minimal-tiptap-93b5559", "sha512-n3D4e9DiPrWWGd/8QcSwN1E1FfwPKcyJ5SZTHhJLsZynLGWLER2EvRT0vBdYf+JjHypz2hvV2HRU04JsyxEXDw=="],
"shadcn-vue": ["shadcn-vue@2.7.3", "", { "dependencies": { "@dotenvx/dotenvx": "^1.51.1", "@modelcontextprotocol/sdk": "^1.24.3", "@unovue/detypes": "^0.8.5", "@vue/compiler-sfc": "^3.5", "c12": "^3.3.2", "commander": "^14.0.2", "consola": "^3.4.2", "dedent": "^1.7.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "fs-extra": "^11.3.2", "fuzzysort": "^3.1.0", "get-tsconfig": "^4.13.0", "giget": "^3.2.0", "magic-string": "^0.30.21", "nypm": "^0.6.2", "ofetch": "^1.5.1", "open": "^10.2.0", "ora": "^9.0.0", "pathe": "^2.0.3", "postcss": "^8.5.10", "postcss-selector-parser": "^7.1.1", "prompts": "^2.4.2", "reka-ui": "^2.9.2", "semver": "^7.7.3", "stringify-object": "^6.0.0", "tailwindcss": "^4.1.17", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "ts-morph": "^27.0.2", "undici": "^7.16.0", "validate-npm-package-name": "^5.0.1", "vue-metamorph": "3.3.4", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.0" }, "bin": { "shadcn-vue": "dist/index.js" } }, "sha512-bYfn9RbjG98++IjvCEhVhvva64mjDAGrtE4QNSuy6jjtE/XpI3syRJaBYmrvPBI93W27dLGbCTr8p7gnvjkQvQ=="], "shadcn-vue": ["shadcn-vue@2.7.3", "", { "dependencies": { "@dotenvx/dotenvx": "^1.51.1", "@modelcontextprotocol/sdk": "^1.24.3", "@unovue/detypes": "^0.8.5", "@vue/compiler-sfc": "^3.5", "c12": "^3.3.2", "commander": "^14.0.2", "consola": "^3.4.2", "dedent": "^1.7.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "fs-extra": "^11.3.2", "fuzzysort": "^3.1.0", "get-tsconfig": "^4.13.0", "giget": "^3.2.0", "magic-string": "^0.30.21", "nypm": "^0.6.2", "ofetch": "^1.5.1", "open": "^10.2.0", "ora": "^9.0.0", "pathe": "^2.0.3", "postcss": "^8.5.10", "postcss-selector-parser": "^7.1.1", "prompts": "^2.4.2", "reka-ui": "^2.9.2", "semver": "^7.7.3", "stringify-object": "^6.0.0", "tailwindcss": "^4.1.17", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "ts-morph": "^27.0.2", "undici": "^7.16.0", "validate-npm-package-name": "^5.0.1", "vue-metamorph": "3.3.4", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.0" }, "bin": { "shadcn-vue": "dist/index.js" } }, "sha512-bYfn9RbjG98++IjvCEhVhvva64mjDAGrtE4QNSuy6jjtE/XpI3syRJaBYmrvPBI93W27dLGbCTr8p7gnvjkQvQ=="],
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
@@ -1122,12 +1386,14 @@
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="], "slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="],
"sonner": ["sonner@1.7.4", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="],
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
@@ -1196,6 +1462,8 @@
"undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="], "undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="],
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
@@ -1204,6 +1472,12 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="], "validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="],
@@ -1228,6 +1502,8 @@
"vue-tsc": ["vue-tsc@2.2.12", "", { "dependencies": { "@volar/typescript": "2.4.15", "@vue/language-core": "2.2.12" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw=="], "vue-tsc": ["vue-tsc@2.2.12", "", { "dependencies": { "@volar/typescript": "2.4.15", "@vue/language-core": "2.2.12" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw=="],
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
"web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="], "web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="],
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
@@ -1244,6 +1520,10 @@
"xlsx": ["xlsx@0.18.5", "", { "dependencies": { "adler-32": "~1.3.0", "cfb": "~1.2.1", "codepage": "~1.15.0", "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" }, "bin": { "xlsx": "bin/xlsx.njs" } }, "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ=="], "xlsx": ["xlsx@0.18.5", "", { "dependencies": { "adler-32": "~1.3.0", "cfb": "~1.2.1", "codepage": "~1.15.0", "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" }, "bin": { "xlsx": "bin/xlsx.njs" } }, "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ=="],
"xml": ["xml@1.0.1", "", {}, "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw=="],
"xml-js": ["xml-js@1.6.11", "", { "dependencies": { "sax": "^1.2.4" }, "bin": { "xml-js": "./bin/cli.js" } }, "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g=="],
"xmlbuilder": ["xmlbuilder@10.1.1", "", {}, "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg=="], "xmlbuilder": ["xmlbuilder@10.1.1", "", {}, "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
@@ -1260,6 +1540,8 @@
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -1270,6 +1552,22 @@
"@eslint/config-array/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "@eslint/config-array/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
"@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
"@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
@@ -1300,13 +1598,13 @@
"eslint/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "eslint/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"path-scurry/lru-cache": ["lru-cache@11.5.0", "", {}, "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA=="], "postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="],
"recast-x/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "recast-x/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@@ -1314,18 +1612,16 @@
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
"restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"stylus/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"table/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "table/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"vue-metamorph/@babel/parser": ["@babel/parser@8.0.0-alpha.12", "", { "bin": "./bin/babel-parser.js" }, "sha512-AzWmrp4uJ+DcXVH0uoUpJVhRqxNirC0BbXsZ82AQuVod41CoaV5G+cwcvtYusrIIxv7BIJb6ce0dQ9L0wAl1iA=="], "vue-metamorph/@babel/parser": ["@babel/parser@8.0.0-alpha.12", "", { "bin": "./bin/babel-parser.js" }, "sha512-AzWmrp4uJ+DcXVH0uoUpJVhRqxNirC0BbXsZ82AQuVod41CoaV5G+cwcvtYusrIIxv7BIJb6ce0dQ9L0wAl1iA=="],
"vue-metamorph/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
"@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
"@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], "@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
@@ -1340,11 +1636,11 @@
"eslint/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], "eslint/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
"glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], "glob/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
"string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"vue-metamorph/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "stylus/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"@eslint/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "@eslint/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
@@ -1352,8 +1648,8 @@
"eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"vue-metamorph/glob/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"vue-metamorph/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "stylus/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],
} }
} }
+5884
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -18,6 +18,17 @@
"@tanstack/vue-table": "^8.21.3", "@tanstack/vue-table": "^8.21.3",
"@tauri-apps/api": "^2.11.0", "@tauri-apps/api": "^2.11.0",
"@tauri-apps/cli": "^2.11.2", "@tauri-apps/cli": "^2.11.2",
"@tauri-apps/plugin-http": "^2.5.9",
"@tiptap/extension-code-block-lowlight": "^3.23.6",
"@tiptap/extension-image": "^3.23.6",
"@tiptap/extension-link": "^3.23.6",
"@tiptap/extension-placeholder": "^3.23.6",
"@tiptap/extension-table": "^3.23.6",
"@tiptap/extension-table-cell": "^3.23.6",
"@tiptap/extension-table-header": "^3.23.6",
"@tiptap/extension-table-row": "^3.23.6",
"@tiptap/starter-kit": "^3.23.6",
"@tiptap/vue-3": "^3.23.6",
"@vueuse/core": "^14.3.0", "@vueuse/core": "^14.3.0",
"ag-grid-community": "^35.3.0", "ag-grid-community": "^35.3.0",
"ag-grid-vue3": "^35.3.0", "ag-grid-vue3": "^35.3.0",
@@ -25,10 +36,13 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dexie": "^4.0.4", "dexie": "^4.0.4",
"dnd-kit-vue": "^0.0.2", "dnd-kit-vue": "^0.0.2",
"docx": "^9.7.1",
"lowlight": "^3.3.0",
"lucide-vue-next": "^1.0.0", "lucide-vue-next": "^1.0.0",
"mammoth": "^1.12.0", "mammoth": "^1.12.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"reka-ui": "^2.9.8", "reka-ui": "^2.9.8",
"shadcn-minimal-tiptap": "github:Aslam97/minimal-tiptap",
"shadcn-vue": "^2.7.3", "shadcn-vue": "^2.7.3",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
+405 -13
View File
@@ -71,6 +71,7 @@ dependencies = [
"serde_json", "serde_json",
"tauri", "tauri",
"tauri-build", "tauri-build",
"tauri-plugin-http",
"tauri-plugin-opener", "tauri-plugin-opener",
] ]
@@ -621,6 +622,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.44" version = "0.4.44"
@@ -690,10 +697,29 @@ version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [ dependencies = [
"percent-encoding",
"time", "time",
"version_check", "version_check",
] ]
[[package]]
name = "cookie_store"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206"
dependencies = [
"cookie",
"document-features",
"idna",
"log",
"publicsuffix",
"serde",
"serde_derive",
"serde_json",
"time",
"url",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@@ -860,6 +886,12 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "data-url"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
[[package]] [[package]]
name = "dbus" name = "dbus"
version = "0.9.11" version = "0.9.11"
@@ -1000,6 +1032,15 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "document-features"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
dependencies = [
"litrs",
]
[[package]] [[package]]
name = "dom_query" name = "dom_query"
version = "0.27.0" version = "0.27.0"
@@ -1092,6 +1133,15 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "endi" name = "endi"
version = "1.1.1" version = "1.1.1"
@@ -1497,8 +1547,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi", "wasi",
"wasm-bindgen",
] ]
[[package]] [[package]]
@@ -1508,9 +1560,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"r-efi 5.3.0", "r-efi 5.3.0",
"wasip2", "wasip2",
"wasm-bindgen",
] ]
[[package]] [[package]]
@@ -1693,6 +1747,25 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "h2"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http 1.4.1",
"indexmap 2.14.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@@ -1859,7 +1932,7 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2", "h2 0.3.27",
"http 0.2.12", "http 0.2.12",
"http-body 0.4.6", "http-body 0.4.6",
"httparse", "httparse",
@@ -1883,6 +1956,7 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"h2 0.4.14",
"http 1.4.1", "http 1.4.1",
"http-body 1.0.1", "http-body 1.0.1",
"httparse", "httparse",
@@ -1903,14 +1977,30 @@ dependencies = [
"http 0.2.12", "http 0.2.12",
"hyper 0.14.32", "hyper 0.14.32",
"log", "log",
"rustls", "rustls 0.22.4",
"rustls-native-certs", "rustls-native-certs",
"rustls-pki-types", "rustls-pki-types",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls 0.25.0",
"webpki-roots 0.26.11", "webpki-roots 0.26.11",
] ]
[[package]]
name = "hyper-rustls"
version = "0.27.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
dependencies = [
"http 1.4.1",
"hyper 1.9.0",
"hyper-util",
"rustls 0.23.40",
"tokio",
"tokio-rustls 0.26.4",
"tower-service",
"webpki-roots 1.0.7",
]
[[package]] [[package]]
name = "hyper-timeout" name = "hyper-timeout"
version = "0.4.1" version = "0.4.1"
@@ -1941,9 +2031,11 @@ dependencies = [
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2 0.6.3", "socket2 0.6.3",
"system-configuration",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
"windows-registry",
] ]
[[package]] [[package]]
@@ -2394,7 +2486,7 @@ dependencies = [
"futures", "futures",
"http 0.2.12", "http 0.2.12",
"hyper 0.14.32", "hyper 0.14.32",
"hyper-rustls", "hyper-rustls 0.25.0",
"libsql-hrana", "libsql-hrana",
"libsql-sqlite3-parser", "libsql-sqlite3-parser",
"libsql-sys", "libsql-sys",
@@ -2529,6 +2621,12 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
[[package]]
name = "litrs"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.14" version = "0.4.14"
@@ -2544,6 +2642,12 @@ version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]] [[package]]
name = "markup5ever" name = "markup5ever"
version = "0.38.0" version = "0.38.0"
@@ -2830,6 +2934,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [ dependencies = [
"bitflags 2.11.1", "bitflags 2.11.1",
"block2", "block2",
"libc",
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
] ]
@@ -3061,7 +3166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [ dependencies = [
"phf_shared 0.11.3", "phf_shared 0.11.3",
"rand", "rand 0.8.6",
] ]
[[package]] [[package]]
@@ -3327,6 +3432,22 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "psl-types"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
[[package]]
name = "publicsuffix"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
dependencies = [
"idna",
"psl-types",
]
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.39.4" version = "0.39.4"
@@ -3336,6 +3457,61 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "quinn"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash 2.1.2",
"rustls 0.23.40",
"socket2 0.6.3",
"thiserror 2.0.18",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
"bytes",
"getrandom 0.3.4",
"lru-slab",
"rand 0.9.4",
"ring",
"rustc-hash 2.1.2",
"rustls 0.23.40",
"rustls-pki-types",
"slab",
"thiserror 2.0.18",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2 0.6.3",
"tracing",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.45" version = "1.0.45"
@@ -3364,8 +3540,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.1",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.5",
] ]
[[package]] [[package]]
@@ -3375,7 +3561,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
] ]
[[package]] [[package]]
@@ -3387,6 +3583,15 @@ dependencies = [
"getrandom 0.2.17", "getrandom 0.2.17",
] ]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]] [[package]]
name = "raw-window-handle" name = "raw-window-handle"
version = "0.6.2" version = "0.6.2"
@@ -3473,6 +3678,49 @@ version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "reqwest"
version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
"base64 0.22.1",
"bytes",
"cookie",
"cookie_store",
"encoding_rs",
"futures-core",
"h2 0.4.14",
"http 1.4.1",
"http-body 1.0.1",
"http-body-util",
"hyper 1.9.0",
"hyper-rustls 0.27.9",
"hyper-util",
"js-sys",
"log",
"mime",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.40",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 1.0.2",
"tokio",
"tokio-rustls 0.26.4",
"tower 0.5.3",
"tower-http 0.6.11",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots 1.0.7",
]
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.13.4" version = "0.13.4"
@@ -3577,7 +3825,21 @@ dependencies = [
"log", "log",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki", "rustls-webpki 0.102.8",
"subtle",
"zeroize",
]
[[package]]
name = "rustls"
version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki 0.103.13",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@@ -3610,6 +3872,7 @@ version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
dependencies = [ dependencies = [
"web-time",
"zeroize", "zeroize",
] ]
@@ -3624,12 +3887,29 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]] [[package]]
name = "same-file" name = "same-file"
version = "1.0.6" version = "1.0.6"
@@ -3852,6 +4132,18 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "serde_with" name = "serde_with"
version = "3.20.0" version = "3.20.0"
@@ -4134,6 +4426,27 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "system-configuration"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
"bitflags 2.11.1",
"core-foundation 0.9.4",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "system-deps" name = "system-deps"
version = "6.2.2" version = "6.2.2"
@@ -4234,7 +4547,7 @@ dependencies = [
"percent-encoding", "percent-encoding",
"plist", "plist",
"raw-window-handle", "raw-window-handle",
"reqwest", "reqwest 0.13.4",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
@@ -4333,6 +4646,54 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "tauri-plugin-fs"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371"
dependencies = [
"anyhow",
"dunce",
"glob",
"log",
"objc2-foundation",
"percent-encoding",
"schemars 0.8.22",
"serde",
"serde_json",
"serde_repr",
"tauri",
"tauri-plugin",
"tauri-utils",
"thiserror 2.0.18",
"toml 1.1.2+spec-1.1.0",
"url",
]
[[package]]
name = "tauri-plugin-http"
version = "2.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5bd512048e1985b7ec78f96d99083e2ddaf7e0d906b2b63c44ce5bb8b894067"
dependencies = [
"bytes",
"cookie_store",
"data-url",
"http 1.4.1",
"regex",
"reqwest 0.12.28",
"schemars 0.8.22",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"tauri-plugin-fs",
"thiserror 2.0.18",
"tokio",
"url",
"urlpattern",
]
[[package]] [[package]]
name = "tauri-plugin-opener" name = "tauri-plugin-opener"
version = "2.5.4" version = "2.5.4"
@@ -4618,11 +4979,21 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
dependencies = [ dependencies = [
"rustls", "rustls 0.22.4",
"rustls-pki-types", "rustls-pki-types",
"tokio", "tokio",
] ]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls 0.23.40",
"tokio",
]
[[package]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.18" version = "0.1.18"
@@ -4778,7 +5149,7 @@ dependencies = [
"axum", "axum",
"base64 0.21.7", "base64 0.21.7",
"bytes", "bytes",
"h2", "h2 0.3.27",
"http 0.2.12", "http 0.2.12",
"http-body 0.4.6", "http-body 0.4.6",
"hyper 0.14.32", "hyper 0.14.32",
@@ -4825,7 +5196,7 @@ dependencies = [
"indexmap 1.9.3", "indexmap 1.9.3",
"pin-project", "pin-project",
"pin-project-lite", "pin-project-lite",
"rand", "rand 0.8.6",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
@@ -5292,6 +5663,16 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "web_atoms" name = "web_atoms"
version = "0.2.4" version = "0.2.4"
@@ -5563,6 +5944,17 @@ dependencies = [
"windows-link 0.1.3", "windows-link 0.1.3",
] ]
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]] [[package]]
name = "windows-result" name = "windows-result"
version = "0.3.4" version = "0.3.4"
+1
View File
@@ -6,6 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
tauri = { version = "2", features = [] } tauri = { version = "2", features = [] }
tauri-plugin-opener = "2" tauri-plugin-opener = "2"
tauri-plugin-http = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
libsql = "0.9" libsql = "0.9"
+10
View File
@@ -0,0 +1,10 @@
{
"identifier": "default",
"description": "Default capabilities",
"windows": ["main"],
"permissions": [
"core:default",
"opener:default",
"http:default"
]
}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{} {"default":{"identifier":"default","description":"Default capabilities","local":true,"windows":["main"],"permissions":["core:default","opener:default","http:default"]}}
File diff suppressed because it is too large Load Diff
+204
View File
@@ -134,6 +134,144 @@
"description": "Reference a permission or permission set by identifier and extends its scope.", "description": "Reference a permission or permission set by identifier and extends its scope.",
"type": "object", "type": "object",
"allOf": [ "allOf": [
{
"if": {
"properties": {
"identifier": {
"anyOf": [
{
"description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-send`\n- `allow-fetch-read-body`\n- `allow-fetch-cancel-body`",
"type": "string",
"const": "http:default",
"markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-send`\n- `allow-fetch-read-body`\n- `allow-fetch-cancel-body`"
},
{
"description": "Enables the fetch command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch",
"markdownDescription": "Enables the fetch command without any pre-configured scope."
},
{
"description": "Enables the fetch_cancel command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-cancel",
"markdownDescription": "Enables the fetch_cancel command without any pre-configured scope."
},
{
"description": "Enables the fetch_cancel_body command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-cancel-body",
"markdownDescription": "Enables the fetch_cancel_body command without any pre-configured scope."
},
{
"description": "Enables the fetch_read_body command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-read-body",
"markdownDescription": "Enables the fetch_read_body command without any pre-configured scope."
},
{
"description": "Enables the fetch_send command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-send",
"markdownDescription": "Enables the fetch_send command without any pre-configured scope."
},
{
"description": "Denies the fetch command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch",
"markdownDescription": "Denies the fetch command without any pre-configured scope."
},
{
"description": "Denies the fetch_cancel command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-cancel",
"markdownDescription": "Denies the fetch_cancel command without any pre-configured scope."
},
{
"description": "Denies the fetch_cancel_body command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-cancel-body",
"markdownDescription": "Denies the fetch_cancel_body command without any pre-configured scope."
},
{
"description": "Denies the fetch_read_body command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-read-body",
"markdownDescription": "Denies the fetch_read_body command without any pre-configured scope."
},
{
"description": "Denies the fetch_send command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-send",
"markdownDescription": "Denies the fetch_send command without any pre-configured scope."
}
]
}
}
},
"then": {
"properties": {
"allow": {
"items": {
"title": "HttpScopeEntry",
"description": "HTTP scope entry.",
"anyOf": [
{
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
},
{
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
}
}
}
]
}
},
"deny": {
"items": {
"title": "HttpScopeEntry",
"description": "HTTP scope entry.",
"anyOf": [
{
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
},
{
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
}
}
}
]
}
}
}
},
"properties": {
"identifier": {
"description": "Identifier of the permission or permission set.",
"allOf": [
{
"$ref": "#/definitions/Identifier"
}
]
}
}
},
{ {
"if": { "if": {
"properties": { "properties": {
@@ -2360,6 +2498,72 @@
"const": "core:window:deny-unminimize", "const": "core:window:deny-unminimize",
"markdownDescription": "Denies the unminimize command without any pre-configured scope." "markdownDescription": "Denies the unminimize command without any pre-configured scope."
}, },
{
"description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-send`\n- `allow-fetch-read-body`\n- `allow-fetch-cancel-body`",
"type": "string",
"const": "http:default",
"markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-send`\n- `allow-fetch-read-body`\n- `allow-fetch-cancel-body`"
},
{
"description": "Enables the fetch command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch",
"markdownDescription": "Enables the fetch command without any pre-configured scope."
},
{
"description": "Enables the fetch_cancel command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-cancel",
"markdownDescription": "Enables the fetch_cancel command without any pre-configured scope."
},
{
"description": "Enables the fetch_cancel_body command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-cancel-body",
"markdownDescription": "Enables the fetch_cancel_body command without any pre-configured scope."
},
{
"description": "Enables the fetch_read_body command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-read-body",
"markdownDescription": "Enables the fetch_read_body command without any pre-configured scope."
},
{
"description": "Enables the fetch_send command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-send",
"markdownDescription": "Enables the fetch_send command without any pre-configured scope."
},
{
"description": "Denies the fetch command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch",
"markdownDescription": "Denies the fetch command without any pre-configured scope."
},
{
"description": "Denies the fetch_cancel command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-cancel",
"markdownDescription": "Denies the fetch_cancel command without any pre-configured scope."
},
{
"description": "Denies the fetch_cancel_body command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-cancel-body",
"markdownDescription": "Denies the fetch_cancel_body command without any pre-configured scope."
},
{
"description": "Denies the fetch_read_body command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-read-body",
"markdownDescription": "Denies the fetch_read_body command without any pre-configured scope."
},
{
"description": "Denies the fetch_send command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-send",
"markdownDescription": "Denies the fetch_send command without any pre-configured scope."
},
{ {
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
"type": "string", "type": "string",
+204
View File
@@ -134,6 +134,144 @@
"description": "Reference a permission or permission set by identifier and extends its scope.", "description": "Reference a permission or permission set by identifier and extends its scope.",
"type": "object", "type": "object",
"allOf": [ "allOf": [
{
"if": {
"properties": {
"identifier": {
"anyOf": [
{
"description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-send`\n- `allow-fetch-read-body`\n- `allow-fetch-cancel-body`",
"type": "string",
"const": "http:default",
"markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-send`\n- `allow-fetch-read-body`\n- `allow-fetch-cancel-body`"
},
{
"description": "Enables the fetch command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch",
"markdownDescription": "Enables the fetch command without any pre-configured scope."
},
{
"description": "Enables the fetch_cancel command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-cancel",
"markdownDescription": "Enables the fetch_cancel command without any pre-configured scope."
},
{
"description": "Enables the fetch_cancel_body command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-cancel-body",
"markdownDescription": "Enables the fetch_cancel_body command without any pre-configured scope."
},
{
"description": "Enables the fetch_read_body command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-read-body",
"markdownDescription": "Enables the fetch_read_body command without any pre-configured scope."
},
{
"description": "Enables the fetch_send command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-send",
"markdownDescription": "Enables the fetch_send command without any pre-configured scope."
},
{
"description": "Denies the fetch command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch",
"markdownDescription": "Denies the fetch command without any pre-configured scope."
},
{
"description": "Denies the fetch_cancel command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-cancel",
"markdownDescription": "Denies the fetch_cancel command without any pre-configured scope."
},
{
"description": "Denies the fetch_cancel_body command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-cancel-body",
"markdownDescription": "Denies the fetch_cancel_body command without any pre-configured scope."
},
{
"description": "Denies the fetch_read_body command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-read-body",
"markdownDescription": "Denies the fetch_read_body command without any pre-configured scope."
},
{
"description": "Denies the fetch_send command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-send",
"markdownDescription": "Denies the fetch_send command without any pre-configured scope."
}
]
}
}
},
"then": {
"properties": {
"allow": {
"items": {
"title": "HttpScopeEntry",
"description": "HTTP scope entry.",
"anyOf": [
{
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
},
{
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
}
}
}
]
}
},
"deny": {
"items": {
"title": "HttpScopeEntry",
"description": "HTTP scope entry.",
"anyOf": [
{
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
},
{
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
}
}
}
]
}
}
}
},
"properties": {
"identifier": {
"description": "Identifier of the permission or permission set.",
"allOf": [
{
"$ref": "#/definitions/Identifier"
}
]
}
}
},
{ {
"if": { "if": {
"properties": { "properties": {
@@ -2360,6 +2498,72 @@
"const": "core:window:deny-unminimize", "const": "core:window:deny-unminimize",
"markdownDescription": "Denies the unminimize command without any pre-configured scope." "markdownDescription": "Denies the unminimize command without any pre-configured scope."
}, },
{
"description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-send`\n- `allow-fetch-read-body`\n- `allow-fetch-cancel-body`",
"type": "string",
"const": "http:default",
"markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-send`\n- `allow-fetch-read-body`\n- `allow-fetch-cancel-body`"
},
{
"description": "Enables the fetch command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch",
"markdownDescription": "Enables the fetch command without any pre-configured scope."
},
{
"description": "Enables the fetch_cancel command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-cancel",
"markdownDescription": "Enables the fetch_cancel command without any pre-configured scope."
},
{
"description": "Enables the fetch_cancel_body command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-cancel-body",
"markdownDescription": "Enables the fetch_cancel_body command without any pre-configured scope."
},
{
"description": "Enables the fetch_read_body command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-read-body",
"markdownDescription": "Enables the fetch_read_body command without any pre-configured scope."
},
{
"description": "Enables the fetch_send command without any pre-configured scope.",
"type": "string",
"const": "http:allow-fetch-send",
"markdownDescription": "Enables the fetch_send command without any pre-configured scope."
},
{
"description": "Denies the fetch command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch",
"markdownDescription": "Denies the fetch command without any pre-configured scope."
},
{
"description": "Denies the fetch_cancel command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-cancel",
"markdownDescription": "Denies the fetch_cancel command without any pre-configured scope."
},
{
"description": "Denies the fetch_cancel_body command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-cancel-body",
"markdownDescription": "Denies the fetch_cancel_body command without any pre-configured scope."
},
{
"description": "Denies the fetch_read_body command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-read-body",
"markdownDescription": "Denies the fetch_read_body command without any pre-configured scope."
},
{
"description": "Denies the fetch_send command without any pre-configured scope.",
"type": "string",
"const": "http:deny-fetch-send",
"markdownDescription": "Denies the fetch_send command without any pre-configured scope."
},
{ {
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
"type": "string", "type": "string",
+1
View File
@@ -18,6 +18,7 @@ fn main() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_http::init())
.manage(Mutex::new(db_path)) .manage(Mutex::new(db_path))
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
commands::get_projects, commands::get_projects,
+220
View File
@@ -0,0 +1,220 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useWorkItemsStore, type EnrichedUserStory } from '@/stores/workitems'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { AlertTriangle, Calendar, Clock, ListChecks } from 'lucide-vue-next'
const { t } = useI18n()
const workItems = useWorkItemsStore()
const today = new Date()
today.setHours(0, 0, 0, 0)
function daysUntil(dateStr: string | undefined): number | null {
if (!dateStr) return null
const d = new Date(dateStr)
d.setHours(0, 0, 0, 0)
return Math.ceil((d.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
}
function isOverdue(dateStr: string | undefined): boolean {
const d = daysUntil(dateStr)
return d !== null && d < 0
}
function isToday(dateStr: string | undefined): boolean {
const d = daysUntil(dateStr)
return d === 0
}
function isThisWeek(dateStr: string | undefined): boolean {
const d = daysUntil(dateStr)
return d !== null && d > 0 && d <= 7
}
function isNextWeek(dateStr: string | undefined): boolean {
const d = daysUntil(dateStr)
return d !== null && d > 7 && d <= 14
}
function isBlocked(hu: EnrichedUserStory): boolean {
const s = String(hu.status ?? '').toLowerCase()
return ['blocked', 'bloqueado'].includes(s)
}
function isInProgress(hu: EnrichedUserStory): boolean {
const s = String(hu.status ?? '').toLowerCase()
return ['in_progress', 'doing', 'wip', 'active', 'in progress', 'en progreso', 'true', '2'].includes(s)
}
function isDone(hu: EnrichedUserStory): boolean {
const s = String(hu.status ?? '').toLowerCase()
return ['done', 'completed', 'closed', 'finalizado', '5', '6', '7', 'qa-client', 'ready to deploy'].includes(s)
}
// Agrupar HUs
const overdueHUs = computed(() =>
workItems.userStories.filter(hu => hu.end_date && isOverdue(hu.end_date))
)
const todayHUs = computed(() =>
workItems.userStories.filter(hu => hu.end_date && isToday(hu.end_date))
)
const thisWeekHUs = computed(() =>
workItems.userStories.filter(hu => hu.end_date && isThisWeek(hu.end_date))
)
const nextWeekHUs = computed(() =>
workItems.userStories.filter(hu => hu.end_date && isNextWeek(hu.end_date))
)
const blockedHUs = computed(() =>
workItems.userStories.filter(hu => isBlocked(hu))
)
const inProgressHUs = computed(() =>
workItems.userStories.filter(hu => isInProgress(hu))
)
function formatDate(dateStr: string | undefined): string {
if (!dateStr) return ''
const d = new Date(dateStr)
return d.toLocaleDateString('es-CO', { day: 'numeric', month: 'short' })
}
function priorityVariant(p: unknown) {
const s = String(p ?? '').toLowerCase().trim()
if (['alta', 'high', 'critical', 'urgente', '1'].includes(s)) return 'destructive'
if (['media', 'medium', 'normal', '2'].includes(s)) return 'default'
if (['baja', 'low', '3'].includes(s)) return 'secondary'
return 'outline'
}
const totalHUs = computed(() => workItems.userStories.length)
const doneHUs = computed(() =>
workItems.userStories.filter(hu => isDone(hu)).length
)
</script>
<template>
<div class="space-y-4">
<!-- Summary cards -->
<div class="grid gap-3 @xl:grid-cols-4">
<Card class="bg-gradient-to-t from-primary/5 to-card shadow-xs">
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-xs font-medium">{{ t('dashboard.hus') }}</CardTitle>
<ListChecks class="size-3.5 text-muted-foreground" />
</CardHeader>
<CardContent>
<div class="text-xl font-bold">{{ totalHUs }}</div>
<p class="text-[10px] text-muted-foreground">{{ doneHUs }} completadas ({{ totalHUs > 0 ? Math.round(doneHUs / totalHUs * 100) : 0 }}%)</p>
</CardContent>
</Card>
<Card class="bg-gradient-to-t from-amber-500/5 to-card shadow-xs">
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-xs font-medium">En progreso</CardTitle>
<Clock class="size-3.5 text-muted-foreground" />
</CardHeader>
<CardContent>
<div class="text-xl font-bold">{{ inProgressHUs.length }}</div>
<p class="text-[10px] text-muted-foreground">activas</p>
</CardContent>
</Card>
<Card class="bg-gradient-to-t from-red-500/5 to-card shadow-xs">
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-xs font-medium">Vencidas</CardTitle>
<AlertTriangle class="size-3.5 text-red-500" />
</CardHeader>
<CardContent>
<div class="text-xl font-bold text-red-500">{{ overdueHUs.length }}</div>
<p class="text-[10px] text-muted-foreground">requieren atención</p>
</CardContent>
</Card>
<Card class="bg-gradient-to-t from-purple-500/5 to-card shadow-xs">
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-xs font-medium">Bloqueadas</CardTitle>
<AlertTriangle class="size-3.5 text-purple-500" />
</CardHeader>
<CardContent>
<div class="text-xl font-bold text-purple-500">{{ blockedHUs.length }}</div>
<p class="text-[10px] text-muted-foreground">con impedimentos</p>
</CardContent>
</Card>
</div>
<!-- Overdue -->
<Card v-if="overdueHUs.length > 0" class="border-l-3 border-l-red-500">
<CardHeader class="pb-2">
<CardTitle class="text-xs font-medium flex items-center gap-2 text-red-600">
<AlertTriangle class="size-3.5" />
Vencidas ({{ overdueHUs.length }})
</CardTitle>
</CardHeader>
<CardContent class="space-y-1">
<div v-for="hu in overdueHUs" :key="hu.id" class="flex items-center gap-2 text-xs py-1 border-b last:border-0">
<span class="font-mono text-[10px] text-muted-foreground w-16">{{ hu.code || `#${hu.id}` }}</span>
<span class="flex-1 truncate">{{ hu._cleanTitle || hu.title }}</span>
<Badge :variant="priorityVariant(hu.priority)" class="text-[10px]">{{ hu.priority }}</Badge>
<span class="text-red-500 font-mono text-[10px] w-16 text-right">{{ formatDate(hu.end_date) }}</span>
</div>
</CardContent>
</Card>
<!-- Today -->
<Card v-if="todayHUs.length > 0" class="border-l-3 border-l-amber-500">
<CardHeader class="pb-2">
<CardTitle class="text-xs font-medium flex items-center gap-2">
<Calendar class="size-3.5" />
Hoy ({{ todayHUs.length }})
</CardTitle>
</CardHeader>
<CardContent class="space-y-1">
<div v-for="hu in todayHUs" :key="hu.id" class="flex items-center gap-2 text-xs py-1 border-b last:border-0">
<span class="font-mono text-[10px] text-muted-foreground w-16">{{ hu.code || `#${hu.id}` }}</span>
<span class="flex-1 truncate">{{ hu._cleanTitle || hu.title }}</span>
<Badge :variant="priorityVariant(hu.priority)" class="text-[10px]">{{ hu.priority }}</Badge>
<Badge variant="outline" class="text-[10px]">Hoy</Badge>
</div>
</CardContent>
</Card>
<!-- This week -->
<Card v-if="thisWeekHUs.length > 0">
<CardHeader class="pb-2">
<CardTitle class="text-xs font-medium flex items-center gap-2">
<Calendar class="size-3.5" />
Esta semana ({{ thisWeekHUs.length }})
</CardTitle>
</CardHeader>
<CardContent class="space-y-1">
<div v-for="hu in thisWeekHUs" :key="hu.id" class="flex items-center gap-2 text-xs py-1 border-b last:border-0">
<span class="font-mono text-[10px] text-muted-foreground w-16">{{ hu.code || `#${hu.id}` }}</span>
<span class="flex-1 truncate">{{ hu._cleanTitle || hu.title }}</span>
<Badge :variant="priorityVariant(hu.priority)" class="text-[10px]">{{ hu.priority }}</Badge>
<span class="font-mono text-[10px] text-muted-foreground w-16 text-right">{{ formatDate(hu.end_date) }}</span>
</div>
</CardContent>
</Card>
<!-- Blocked -->
<Card v-if="blockedHUs.length > 0" class="border-l-3 border-l-purple-500">
<CardHeader class="pb-2">
<CardTitle class="text-xs font-medium flex items-center gap-2 text-purple-600">
<AlertTriangle class="size-3.5" />
Bloqueadas ({{ blockedHUs.length }})
</CardTitle>
</CardHeader>
<CardContent class="space-y-1">
<div v-for="hu in blockedHUs" :key="hu.id" class="flex items-center gap-2 text-xs py-1 border-b last:border-0">
<span class="font-mono text-[10px] text-muted-foreground w-16">{{ hu.code || `#${hu.id}` }}</span>
<span class="flex-1 truncate">{{ hu._cleanTitle || hu.title }}</span>
<Badge variant="destructive" class="text-[10px]">Bloqueada</Badge>
<Badge v-if="hu.has_impairment" variant="outline" class="text-[10px]">Impedimento</Badge>
</div>
</CardContent>
</Card>
</div>
</template>
+154
View File
@@ -0,0 +1,154 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Placeholder from '@tiptap/extension-placeholder'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Bold, Italic, List, ListOrdered, Code, Heading, Quote, Undo, Redo } from 'lucide-vue-next'
const props = defineProps<{
open: boolean
title: string
content: string
}>()
const emit = defineEmits<{
'update:open': [value: boolean]
save: [content: string]
reset: []
}>()
const editor = useEditor({
content: props.content,
extensions: [
StarterKit.configure({
heading: { levels: [1, 2, 3] },
}),
Placeholder.configure({ placeholder: 'Editá el prompt...' }),
],
onUpdate: () => {
currentContent.value = editor.value?.getHTML() || ''
},
})
const currentContent = ref(props.content)
watch(() => props.content, (val) => {
currentContent.value = val
editor.value?.commands.setContent(val)
})
watch(() => props.open, (open) => {
if (open) {
setTimeout(() => editor.value?.commands.setContent(props.content), 50)
}
})
function handleSave() {
const html = editor.value?.getHTML() || ''
// Convert HTML to plain text for storage (los prompts son texto plano)
const text = html
.replace(/<p>/g, '')
.replace(/<\/p>/g, '\n')
.replace(/<br\s*\/?>/g, '\n')
.replace(/<[^>]*>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/\n{3,}/g, '\n\n')
.trim()
emit('save', text)
emit('update:open', false)
}
function toggleBold() { editor.value?.chain().focus().toggleBold().run() }
function toggleItalic() { editor.value?.chain().focus().toggleItalic().run() }
function toggleBulletList() { editor.value?.chain().focus().toggleBulletList().run() }
function toggleOrderedList() { editor.value?.chain().focus().toggleOrderedList().run() }
function toggleCode() { editor.value?.chain().focus().toggleCodeBlock().run() }
function toggleHeading(level: 1 | 2 | 3) { editor.value?.chain().focus().toggleHeading({ level }).run() }
function toggleBlockquote() { editor.value?.chain().focus().toggleBlockquote().run() }
function undo() { editor.value?.chain().focus().undo().run() }
function redo() { editor.value?.chain().focus().redo().run() }
</script>
<template>
<Dialog :open="open" @update:open="emit('update:open', $event)">
<DialogContent class="sm:max-w-[800px] max-h-[85vh] flex flex-col">
<DialogHeader>
<DialogTitle class="text-sm">{{ title }}</DialogTitle>
</DialogHeader>
<!-- Toolbar -->
<div class="flex items-center gap-0.5 p-1 rounded-lg bg-muted/50 border mb-2 flex-wrap">
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="toggleBold" title="Negrita"><Bold class="size-3.5" /></button>
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="toggleItalic" title="Cursiva"><Italic class="size-3.5" /></button>
<span class="w-px h-5 bg-border mx-0.5" />
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="toggleHeading(1)" title="H1"><Heading class="size-3.5" /><sup class="text-[8px]">1</sup></button>
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="toggleHeading(2)" title="H2"><Heading class="size-3.5" /><sup class="text-[8px]">2</sup></button>
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="toggleHeading(3)" title="H3"><Heading class="size-3.5" /><sup class="text-[8px]">3</sup></button>
<span class="w-px h-5 bg-border mx-0.5" />
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="toggleBulletList" title="Lista"><List class="size-3.5" /></button>
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="toggleOrderedList" title="Lista numerada"><ListOrdered class="size-3.5" /></button>
<span class="w-px h-5 bg-border mx-0.5" />
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="toggleCode" title="Código"><Code class="size-3.5" /></button>
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="toggleBlockquote" title="Cita"><Quote class="size-3.5" /></button>
<span class="w-px h-5 bg-border mx-0.5" />
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="undo" title="Deshacer"><Undo class="size-3.5" /></button>
<button class="size-7 flex items-center justify-center rounded hover:bg-muted transition-colors" @click="redo" title="Rehacer"><Redo class="size-3.5" /></button>
</div>
<!-- Editor -->
<div class="flex-1 overflow-y-auto min-h-[300px] rounded-lg border p-4 text-sm">
<EditorContent :editor="editor" class="prose prose-sm dark:prose-invert max-w-none focus:outline-none" />
</div>
<!-- Footer -->
<div class="flex items-center justify-between pt-3 border-t mt-2">
<Button size="sm" variant="ghost" class="text-xs text-muted-foreground" @click="emit('reset')">
Restaurar default
</Button>
<div class="flex gap-2">
<Button size="sm" variant="outline" class="text-xs" @click="emit('update:open', false)">Cancelar</Button>
<Button size="sm" class="text-xs" @click="handleSave">Guardar</Button>
</div>
</div>
</DialogContent>
</Dialog>
</template>
<style>
.tiptap {
outline: none;
min-height: 250px;
}
.tiptap p.is-editor-empty:first-child::before {
color: hsl(var(--muted-foreground));
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
opacity: 0.5;
}
.tiptap pre {
background: hsl(var(--muted));
border-radius: 0.5rem;
padding: 0.75rem 1rem;
font-size: 0.875rem;
overflow-x: auto;
}
.tiptap blockquote {
border-left: 3px solid hsl(var(--border));
padding-left: 1rem;
margin: 0.5rem 0;
color: hsl(var(--muted-foreground));
}
.tiptap ul, .tiptap ol {
padding-left: 1.5rem;
}
.tiptap h1 { font-size: 1.25rem; font-weight: 700; margin: 0.75rem 0 0.5rem; }
.tiptap h2 { font-size: 1.1rem; font-weight: 600; margin: 0.5rem 0 0.25rem; }
.tiptap h3 { font-size: 1rem; font-weight: 600; margin: 0.5rem 0 0.25rem; }
</style>
+130
View File
@@ -0,0 +1,130 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { useWorkItemsStore } from '@/stores/workitems'
import { calculateSCurve, type CurvePoint, type SCurveData } from '@/services/s-curve'
import { getBlockers } from '@/services/blocker-log'
const props = defineProps<{ projectId: number; compact?: boolean }>()
const workItems = useWorkItemsStore()
const loading = ref(true)
const sCurve = ref<SCurveData | null>(null)
async function load() {
loading.value = true
try {
const blockers = await getBlockers(props.projectId)
sCurve.value = calculateSCurve(workItems.userStories, blockers)
} catch (e) {
console.error('[Alpha] S-Curve error:', e)
} finally {
loading.value = false
}
}
onMounted(load)
watch(() => workItems.userStories.length, load)
const CHART_W = 400
const CHART_H = 150
const PAD = { top: 10, right: 10, bottom: 20, left: 35 }
function buildAreaPath(points: CurvePoint[], maxSp: number, totalDays: number): string {
if (points.length === 0) return ''
const w = CHART_W - PAD.left - PAD.right
const h = CHART_H - PAD.top - PAD.bottom
const firstDate = points[0].date
const line = points.map((p, i) => {
const dx = dayOffset(p.date, firstDate)
const x = PAD.left + (dx / totalDays) * w
const y = PAD.top + h - (p.cumulative / maxSp) * h
return `${i === 0 ? 'M' : 'L'}${x},${y}`
}).join(' ')
// Close the area for fill
const last = points[points.length - 1]
const lastDx = dayOffset(last.date, firstDate)
const lastX = PAD.left + (lastDx / totalDays) * w
const bottomY = PAD.top + h
return `${line} L${lastX},${bottomY} L${PAD.left},${bottomY} Z`
}
function dayOffset(date: string, from: string): number {
return Math.max(0, Math.ceil((new Date(date).getTime() - new Date(from).getTime()) / (1000 * 60 * 60 * 24)))
}
const planned = computed(() => sCurve.value?.planned || [])
const actual = computed(() => sCurve.value?.actual || [])
const metrics = computed(() => sCurve.value?.metrics)
const maxSp = computed(() => {
const all = [...planned.value, ...actual.value]
return Math.max(...all.map(p => p.cumulative), 1)
})
const totalDays = computed(() => {
const all = [...planned.value, ...actual.value]
if (all.length < 2) return 30
const first = new Date(all[0].date)
const last = new Date(all[all.length - 1].date)
return Math.max(1, Math.ceil((last.getTime() - first.getTime()) / (1000 * 60 * 60 * 24)))
})
</script>
<template>
<div v-if="loading" class="text-[10px] text-muted-foreground text-center py-4">Cargando...</div>
<div v-else-if="planned.length === 0" class="text-[10px] text-muted-foreground text-center py-4">Sin datos</div>
<div v-else class="space-y-2">
<!-- Mini metrics row for compact -->
<div v-if="metrics && compact" class="flex items-center gap-2 text-[10px] text-muted-foreground px-1">
<span class="font-mono font-bold text-foreground">{{ metrics.totalSpPlanned }} SP</span>
<span class="text-muted-foreground/50">|</span>
<span :class="metrics.spi >= 1 ? 'text-green-500' : 'text-red-500'">SPI {{ metrics.spi }}</span>
</div>
<!-- SVG Chart -->
<svg :viewBox="`0 0 ${CHART_W} ${CHART_H}`" class="w-full h-auto">
<defs>
<linearGradient id="planGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.3" />
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0.02" />
</linearGradient>
<linearGradient id="actualGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#22c55e" stop-opacity="0.25" />
<stop offset="100%" stop-color="#22c55e" stop-opacity="0.02" />
</linearGradient>
</defs>
<!-- Grid -->
<line v-for="i in 3" :key="i"
:x1="PAD.left" :y1="PAD.top + ((i-1)/2) * (CHART_H - PAD.top - PAD.bottom)"
:x2="CHART_W - PAD.right" :y2="PAD.top + ((i-1)/2) * (CHART_H - PAD.top - PAD.bottom)"
stroke="currentColor" stroke-opacity="0.08" stroke-dasharray="3,3"
/>
<!-- Area fills -->
<path :d="buildAreaPath(planned, maxSp, totalDays)" fill="url(#planGrad)" />
<path :d="buildAreaPath(actual, maxSp, totalDays)" fill="url(#actualGrad)" />
<!-- Lines -->
<path :d="buildAreaPath(planned, maxSp, totalDays).replace(/Z$/, '')" fill="none" stroke="#3b82f6" stroke-width="1.5" />
<path :d="buildAreaPath(actual, maxSp, totalDays).replace(/Z$/, '')" fill="none" stroke="#22c55e" stroke-width="1.5" />
<!-- Y labels -->
<text v-for="i in 3" :key="'y'+i"
:x="PAD.left - 4"
:y="PAD.top + ((i-1)/2) * (CHART_H - PAD.top - PAD.bottom) + 3"
text-anchor="end" class="fill-muted-foreground text-[8px]"
>{{ Math.round(maxSp * (i-1) / 2) }}</text>
<!-- X labels -->
<text :x="PAD.left" :y="CHART_H - 4" text-anchor="middle" class="fill-muted-foreground text-[8px]">{{ planned[0]?.date?.slice(5) || '' }}</text>
<text :x="CHART_W - PAD.right" :y="CHART_H - 4" text-anchor="middle" class="fill-muted-foreground text-[8px]">{{ planned[planned.length - 1]?.date?.slice(5) || '' }}</text>
<!-- Mini legend -->
<circle cx="20" cy="8" r="3" fill="#3b82f6" />
<text x="27" y="12" class="fill-muted-foreground text-[8px]">Plan</text>
<circle cx="65" cy="8" r="3" fill="#22c55e" />
<text x="72" y="12" class="fill-muted-foreground text-[8px]">Real</text>
</svg>
</div>
</template>
@@ -3,7 +3,6 @@ import { ref, computed } from "vue"
import type { ChartConfig } from "@/components/ui/chart" import type { ChartConfig } from "@/components/ui/chart"
// import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts" // import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"
import { VisArea, VisAxis, VisLine, VisXYContainer } from "@unovis/vue"
import { import {
Card, Card,
CardContent, CardContent,
+107
View File
@@ -0,0 +1,107 @@
import db from '@/services/db'
export interface BlockerRecord {
id?: number
projectId: number
huId?: number
huTitle?: string
date: string
category: string
description: string
spAffected: number
resolvedAt?: string
resolution?: string
timeLostHours: number
createdBy?: string
createdAt: string
}
export type BlockerCategory = 'client' | 'pm' | 'tech' | 'external' | 'other'
const CATEGORY_LABELS: Record<BlockerCategory, string> = {
client: 'Cliente',
pm: 'Lambda',
tech: 'Técnico',
external: 'Terceros',
other: 'Otro',
}
const CATEGORY_COLORS: Record<BlockerCategory, string> = {
client: 'text-orange-600 bg-orange-100 dark:bg-orange-900/30 dark:text-orange-400',
pm: 'text-blue-600 bg-blue-100 dark:bg-blue-900/30 dark:text-blue-400',
tech: 'text-red-600 bg-red-100 dark:bg-red-900/30 dark:text-red-400',
external: 'text-purple-600 bg-purple-100 dark:bg-purple-900/30 dark:text-purple-400',
other: 'text-gray-600 bg-gray-100 dark:bg-gray-900/30 dark:text-gray-400',
}
export function getBlockerCategoryLabel(cat: BlockerCategory): string {
return CATEGORY_LABELS[cat] || cat
}
export function getBlockerCategoryColor(cat: BlockerCategory): string {
return CATEGORY_COLORS[cat] || ''
}
export const BLOCKER_CATEGORIES: BlockerCategory[] = ['client', 'pm', 'tech', 'external', 'other']
export async function saveBlocker(blocker: BlockerRecord): Promise<number> {
if (blocker.id) {
await (db as any).table('blockers').put(blocker)
return blocker.id
}
return (db as any).table('blockers').add(blocker)
}
export async function getBlockers(projectId: number): Promise<BlockerRecord[]> {
return (db as any).table('blockers').where('projectId').equals(projectId).toArray()
}
export async function getActiveBlockers(projectId: number): Promise<BlockerRecord[]> {
const all = await getBlockers(projectId)
return all.filter(b => !b.resolvedAt)
}
export async function getBlockersByCategory(projectId: number): Promise<Record<BlockerCategory, BlockerRecord[]>> {
const all = await getBlockers(projectId)
const grouped: Record<string, BlockerRecord[]> = {}
for (const b of all) {
const cat = b.category || 'other'
if (!grouped[cat]) grouped[cat] = []
grouped[cat].push(b)
}
return grouped as Record<BlockerCategory, BlockerRecord[]>
}
export async function resolveBlocker(id: number, resolution: string, resolvedAt: string): Promise<void> {
const blocker = await (db as any).table('blockers').get(id)
if (blocker) {
blocker.resolvedAt = resolvedAt
blocker.resolution = resolution
await (db as any).table('blockers').put(blocker)
}
}
export async function getBlockerStats(projectId: number): Promise<{
totalBlocks: number
activeBlocks: number
totalHoursLost: number
byCategory: Record<string, { count: number; hours: number }>
}> {
const all = await getBlockers(projectId)
const active = all.filter(b => !b.resolvedAt)
const byCategory: Record<string, { count: number; hours: number }> = {}
for (const b of all) {
const cat = b.category || 'other'
if (!byCategory[cat]) byCategory[cat] = { count: 0, hours: 0 }
byCategory[cat].count++
byCategory[cat].hours += b.timeLostHours || 0
}
return {
totalBlocks: all.length,
activeBlocks: active.length,
totalHoursLost: all.reduce((s, b) => s + (b.timeLostHours || 0), 0),
byCategory,
}
}
+19 -1
View File
@@ -135,6 +135,22 @@ export interface PromptRecord {
updatedAt: string updatedAt: string
} }
export interface DexieBlocker {
id?: number
projectId: number
huId?: number
huTitle?: string
date: string
category: string
description: string
spAffected: number
resolvedAt?: string
resolution?: string
timeLostHours: number
createdBy?: string
createdAt: string
}
const db = new Dexie('alpha-core') as Dexie & { const db = new Dexie('alpha-core') as Dexie & {
settings: Dexie.Table<SettingEntry, string> settings: Dexie.Table<SettingEntry, string>
project_docs: Dexie.Table<ProjectDocRecord, number> project_docs: Dexie.Table<ProjectDocRecord, number>
@@ -149,9 +165,10 @@ const db = new Dexie('alpha-core') as Dexie & {
cell_members: Dexie.Table<CellMemberRecord, string> cell_members: Dexie.Table<CellMemberRecord, string>
user_stories: Dexie.Table<DexieUserStory, number> user_stories: Dexie.Table<DexieUserStory, number>
prompts: Dexie.Table<PromptRecord, string> prompts: Dexie.Table<PromptRecord, string>
blockers: Dexie.Table<DexieBlocker, number>
} }
db.version(9).stores({ db.version(10).stores({
settings: '&key', settings: '&key',
project_docs: '&projectId, projectName, updatedAt', project_docs: '&projectId, projectName, updatedAt',
sessions: '++id, projectId, date', sessions: '++id, projectId, date',
@@ -165,6 +182,7 @@ db.version(9).stores({
cell_members: '[cellId+userId], cellId, userId', cell_members: '[cellId+userId], cellId, userId',
user_stories: '&id, initiative_id', user_stories: '&id, initiative_id',
prompts: '&key', prompts: '&key',
blockers: '++id, projectId, category',
}) })
export default db export default db
+144
View File
@@ -0,0 +1,144 @@
import { parseHierarchy, buildFullTitle, buildHierarchyPath, type ItemType } from '@/services/hierarchy'
import type { AnalysisEpic, AnalysisHU } from '@/services/project-analyzer'
import type { EnrichedEpic, EnrichedUserStory } from '@/stores/workitems'
export interface MaxCounters {
epicMax: number
byEpic: Map<string, Record<string, number>>
}
const ITEM_TYPE_MAP: Record<string, ItemType> = {
feature: 'F',
task: 'T',
us: 'U',
bug: 'B',
spike: 'S',
}
/**
* Analiza épicas e ítems existentes para determinar el próximo número disponible
* para cada nivel jerárquico.
*/
export function analyzeExisting(
epics: EnrichedEpic[],
items: EnrichedUserStory[],
): MaxCounters {
let epicMax = 0
const byEpic = new Map<string, Record<string, number>>()
function updateCounter(epicCode: string, type: string, num: number) {
if (!byEpic.has(epicCode)) byEpic.set(epicCode, {})
const map = byEpic.get(epicCode)!
const current = map[type] || 0
if (num > current) map[type] = num
}
for (const epic of epics) {
const h = parseHierarchy(epic._cleanName || epic.name || epic.title || '')
if (!h) {
// Si no tiene código jerárquico, asumimos que usa el id numérico de KAPPA
const fakeH = parseHierarchy(buildFullTitle('', Math.max(epicMax, 1)))
if (fakeH) {
const code = fakeH.epicCode
if (code) {
const num = parseInt(code.slice(1), 10)
if (num > epicMax) epicMax = num
}
}
continue
}
const epicItem = h.items.find(i => i.type === 'E')
if (epicItem) {
const code = `E${String(epicItem.number).padStart(2, '0')}`
if (epicItem.number > epicMax) epicMax = epicItem.number
}
}
for (const item of items) {
const h = parseHierarchy(item._cleanTitle || item.title || '')
if (!h) continue
const epicItem = h.items.find(i => i.type === 'E')
if (!epicItem) continue
const epicCode = `E${String(epicItem.number).padStart(2, '0')}`
for (const seg of h.items) {
if (seg.type === 'E') continue
updateCounter(epicCode, seg.type, seg.number)
}
}
return { epicMax, byEpic }
}
/**
* Asigna códigos jerárquicos a épicas propuestas y devuelve el array
* enriquecido con epicCode en metadata.
*/
export function assignEpicCodes(
proposedEpics: AnalysisEpic[],
counters: MaxCounters,
): (AnalysisEpic & { epicCode: string })[] {
let nextEpicNum = counters.epicMax + 1
return proposedEpics.map(epic => {
const epicCode = `E${String(nextEpicNum).padStart(2, '0')}`
nextEpicNum++
return { ...epic, epicCode }
})
}
/**
* Asigna códigos jerárquicos a ítems propuestos dentro de épicas confirmadas.
* Requiere que cada HU tenga epicName (nombre de la épica) y type.
* Devuelve HUs con title y description enriquecidos con el código.
*/
export function assignItemCodes(
proposedHUs: AnalysisHU[],
confirmedEpics: { name: string; epicCode: string }[],
counters: MaxCounters,
): AnalysisHU[] {
const epicNameToCode = new Map<string, string>()
for (const e of confirmedEpics) {
epicNameToCode.set(e.name.toLowerCase().trim(), e.epicCode)
}
// Clonar counters para no mutar el original
const localCounters = new Map<string, Record<string, number>>()
for (const [key, val] of counters.byEpic) {
localCounters.set(key, { ...val })
}
return proposedHUs.map(hu => {
const epicName = (hu.epicName || '').toLowerCase().trim()
const epicCode = epicNameToCode.get(epicName) || ''
const epicNum = epicCode ? parseInt(epicCode.slice(1), 10) : 0
const itemType = ITEM_TYPE_MAP[(hu.type || 'feature').toLowerCase()] || 'F'
// Obtener próximo número para este tipo dentro de la épica
const epicMap = localCounters.get(epicCode) || {}
const nextNum = (epicMap[itemType] || 0) + 1
if (!localCounters.has(epicCode)) localCounters.set(epicCode, {})
localCounters.get(epicCode)![itemType] = nextNum
// Construir título y descripción con código
const fullTitle = buildFullTitle(hu.title, epicNum || undefined, itemType, nextNum)
const codeTag = buildHierarchyPath(epicNum || undefined, itemType, nextNum)
const typeLabel: Record<string, string> = {
feature: 'Feature', task: 'Tarea', us: 'HU', bug: 'Bug', spike: 'Spike',
}
const huType = (hu.type || 'feature').toLowerCase()
const descPrefix = `[Tipo: ${typeLabel[huType] || 'HU'} | Épica: ${epicCode} | Código: ${codeTag}]<br>`
const enrichedDesc = hu.description
? `${descPrefix}${hu.description}`
: descPrefix
return {
...hu,
title: fullTitle,
description: enrichedDesc,
}
})
}
+13 -15
View File
@@ -1,10 +1,9 @@
export type ItemType = 'E' | 'F' | 'U' | 'T' | 'B' export type ItemType = 'E' | 'F' | 'U' | 'T' | 'B' | 'S'
export interface HierarchyInfo { export interface HierarchyInfo {
fullPath: string fullPath: string
items: { type: ItemType; number: number }[] items: { type: ItemType; number: number }[]
epicCode: string | null epicCode: string | null
featureCode: string | null
itemCode: string | null itemCode: string | null
} }
@@ -14,6 +13,7 @@ const TYPE_LABELS: Record<ItemType, string> = {
U: 'HU', U: 'HU',
T: 'Tarea', T: 'Tarea',
B: 'Bug', B: 'Bug',
S: 'Spike',
} }
const TYPE_COLORS: Record<ItemType, string> = { const TYPE_COLORS: Record<ItemType, string> = {
@@ -22,6 +22,7 @@ const TYPE_COLORS: Record<ItemType, string> = {
U: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400', U: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400',
T: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400', T: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400',
B: 'bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-400', B: 'bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-400',
S: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400',
} }
const TYPE_ICONS: Record<ItemType, string> = { const TYPE_ICONS: Record<ItemType, string> = {
@@ -30,6 +31,7 @@ const TYPE_ICONS: Record<ItemType, string> = {
U: '📋', U: '📋',
T: '⚙️', T: '⚙️',
B: '🐛', B: '🐛',
S: '🔬',
} }
export function getTypeLabel(type: ItemType): string { export function getTypeLabel(type: ItemType): string {
@@ -44,7 +46,7 @@ export function getTypeIcon(type: ItemType): string {
return TYPE_ICONS[type] || '📄' return TYPE_ICONS[type] || '📄'
} }
const HIERARCHY_REGEX = /\[(([EFUTB]\d+)(?:-([EFUTB]\d+))?(?:-([EFUTB]\d+))?)\]/i const HIERARCHY_REGEX = /\[(([EFUTBS]\d+)(?:-([EFUTBS]\d+))?(?:-([EFUTBS]\d+))?)\]/i
export function parseHierarchy(title: string): HierarchyInfo | null { export function parseHierarchy(title: string): HierarchyInfo | null {
const match = title.match(HIERARCHY_REGEX) const match = title.match(HIERARCHY_REGEX)
@@ -61,15 +63,12 @@ export function parseHierarchy(title: string): HierarchyInfo | null {
})) }))
const epic = items.find(i => i.type === 'E') const epic = items.find(i => i.type === 'E')
const feature = items.find(i => i.type === 'F')
const item = items[items.length - 1]
return { return {
fullPath, fullPath,
items, items,
epicCode: epic ? `E${String(epic.number).padStart(2, '0')}` : null, epicCode: epic ? `E${String(epic.number).padStart(2, '0')}` : null,
featureCode: feature ? `F${String(feature.number).padStart(2, '0')}` : null, itemCode: items.length > 1 ? `${items[1].type}${String(items[1].number).padStart(2, '0')}` : null,
itemCode: item ? `${item.type}${String(item.number).padStart(2, '0')}` : null,
} }
} }
@@ -85,25 +84,24 @@ export function getItemType(title: string): ItemType {
export function buildHierarchyPath( export function buildHierarchyPath(
epicNum?: number, epicNum?: number,
featureNum?: number,
itemType?: ItemType, itemType?: ItemType,
itemNum?: number, itemNum?: number,
): string { ): string {
const parts: string[] = [] if (epicNum === undefined) return ''
if (epicNum !== undefined) parts.push(`E${String(epicNum).padStart(2, '0')}`) const parts: string[] = [`E${String(epicNum).padStart(2, '0')}`]
if (featureNum !== undefined) parts.push(`F${String(featureNum).padStart(2, '0')}`) if (itemType && itemNum !== undefined) {
if (itemType && itemNum !== undefined) parts.push(`${itemType}${String(itemNum).padStart(2, '0')}`) parts.push(`${itemType}${String(itemNum).padStart(2, '0')}`)
return parts.length ? `[${parts.join('-')}]` : '' }
return `[${parts.join('-')}]`
} }
export function buildFullTitle( export function buildFullTitle(
name: string, name: string,
epicNum?: number, epicNum?: number,
featureNum?: number,
itemType?: ItemType, itemType?: ItemType,
itemNum?: number, itemNum?: number,
): string { ): string {
const path = buildHierarchyPath(epicNum, featureNum, itemType, itemNum) const path = buildHierarchyPath(epicNum, itemType, itemNum)
return path ? `${path} ${name}` : name return path ? `${path} ${name}` : name
} }
+170 -45
View File
@@ -2,14 +2,19 @@ import { callAI } from '@/services/ai'
import { getPrompt } from '@/services/prompts-db' import { getPrompt } from '@/services/prompts-db'
import { getSessionsByProject, getSessionSummary, getProjectState } from '@/services/transcriptions-db' import { getSessionsByProject, getSessionSummary, getProjectState } from '@/services/transcriptions-db'
import { saveDraft, createDraftId } from '@/services/hu-drafts-db' import { saveDraft, createDraftId } from '@/services/hu-drafts-db'
import { generateAndSavePlan } from '@/services/qa-analyzer' import { analyzeExisting, assignEpicCodes, assignItemCodes } from '@/services/hierarchy-generator'
import type { EnrichedUserStory } from '@/stores/workitems' import type { EnrichedEpic, EnrichedUserStory } from '@/stores/workitems'
export interface AnalysisHU { export interface AnalysisHU {
title: string title: string
description: string description: string
acceptance_criteria: string[] acceptance_criteria: string[]
priority: string priority: string
story_points?: number
type?: string
feature?: string
sprint?: number
epicName?: string // nombre de la épica a la que pertenece
} }
export interface AnalysisEpic { export interface AnalysisEpic {
@@ -20,18 +25,18 @@ export interface AnalysisEpic {
estimatedEnd?: string estimatedEnd?: string
} }
interface AnalysisResult { interface AnalysisEpicsResult {
hus: AnalysisHU[]
epics: AnalysisEpic[] epics: AnalysisEpic[]
summary: string summary: string
rationale: string // explicación de por qué estas épicas
}
interface AnalysisHUsResult {
hus: AnalysisHU[]
summary: string
} }
export async function analyzeProject( async function buildProjectContext(projectId: number, projectName: string, existingHUs: EnrichedUserStory[]) {
projectId: number,
projectName: string,
existingHUs: EnrichedUserStory[],
signal?: AbortSignal,
): Promise<AnalysisResult> {
const sessions = await getSessionsByProject(projectId) const sessions = await getSessionsByProject(projectId)
const state = await getProjectState(projectId) const state = await getProjectState(projectId)
@@ -48,9 +53,9 @@ export async function analyzeProject(
}) })
} }
const context = { return {
projectName, projectName,
existingHUs: existingHUs.map(h => ({ t: h._cleanTitle || h.title, s: h.status, p: h.priority })), existingHUs: existingHUs.map(h => ({ t: h._cleanTitle || h.title, s: h.status, p: h.priority, e: h._assignedName || '' })),
sessions: sessionsWithSummaries.slice(-10).reverse(), sessions: sessionsWithSummaries.slice(-10).reverse(),
projectState: state ? { projectState: state ? {
summary: state.summary?.slice(0, 500), summary: state.summary?.slice(0, 500),
@@ -58,39 +63,172 @@ export async function analyzeProject(
tasks: (safeParse<{ status: string }[]>(state.tasks, [])).filter(t => t.status !== 'completada').slice(0, 30), tasks: (safeParse<{ status: string }[]>(state.tasks, [])).filter(t => t.status !== 'completada').slice(0, 30),
} : null, } : null,
} }
}
const userContent = `Contexto completo del proyecto en JSON:\n${JSON.stringify(context, null, 0)}` // ─── FASE 1: Generar solo Épicas ──────────────────────
console.log(`[Alpha] Project analysis — ${projectId}, ${existingHUs.length} HUs existentes, ${sessions.length} sesiones`) export async function analyzeProjectEpics(
projectId: number,
projectName: string,
existingHUs: EnrichedUserStory[],
existingEpics: string[], // nombres de épicas que ya están en KAPPA
signal?: AbortSignal,
existingEpicItems?: EnrichedEpic[], // épicas enriquecidas para contar códigos
): Promise<AnalysisEpicsResult> {
const context = await buildProjectContext(projectId, projectName, existingHUs)
const userContent = `Contexto del proyecto:\n${JSON.stringify(context, null, 0)}\n\nÉpicas ya existentes en KAPPA: ${existingEpics.join(', ') || 'Ninguna'}`
console.log(`[Alpha] Phase 1 — Analyzing epics for ${projectName}, ${existingEpics.length} existentes`)
const systemPrompt = await getPrompt('project_gap') const systemPrompt = await getPrompt('project_gap')
// Pasamos una instrucción adicional para que solo genere épicas en esta fase
const phasePrompt = `[FASE 1: SOLO ÉPICAS]
Analizá TODO el contexto del proyecto.
Identificá las épicas necesarias. Una épica agrupa funcionalidades de un mismo tema (ej: "Módulo de Pagos", "Scrapers", "Dashboard").
Revisá si las épicas ya existentes en KAPPA cubren las necesidades.
Si una épica ya existe en KAPPA, NO la generes de nuevo.
Si el acta de inicio, sesiones o documentación mencionan funcionalidades que no están cubiertas por ninguna épica existente, proponé nuevas épicas.
Para cada épica, listá los títulos tentativos de las HUs que pertenecerían a ella (linkedHuTitles).
${systemPrompt}
IMPORTANTE: Respondé SOLO con épicas en esta fase. NO generes HUs aún.`
const content = await callAI( const content = await callAI(
[{ role: 'system', content: systemPrompt }, { role: 'user', content: userContent }], [{ role: 'system', content: phasePrompt }, { role: 'user', content: userContent }],
0.3, 8192, signal, 0.3, 8192, signal,
) )
try { try {
const jsonStr = extractJSON(content) const jsonStr = extractJSON(content)
const result: AnalysisResult = JSON.parse(jsonStr) const parsed = JSON.parse(jsonStr)
console.log(`[Alpha] Analysis result: ${result.hus.length} HUs, ${result.epics?.length || 0} épicas`) const rawEpics: AnalysisEpic[] = parsed.epics || parsed.epic || []
// Asignar códigos jerárquicos (E06, E07...) a las épicas propuestas
const counters = analyzeExisting(existingEpicItems || [], existingHUs)
const epicsWithCodes = assignEpicCodes(rawEpics, counters)
const result: AnalysisEpicsResult = {
epics: epicsWithCodes,
summary: parsed.summary || '',
rationale: parsed.rationale || parsed.summary || '',
}
console.log(`[Alpha] Phase 1 complete: ${result.epics.length} épicas propuestas`)
return result return result
} catch (e) { } catch (e) {
console.error('[Alpha] Failed to parse analysis. Raw:', content) console.error('[Alpha] Failed to parse epics analysis. Raw:', content)
throw new Error('No se pudo procesar el análisis del proyecto') throw new Error('No se pudieron generar las épicas')
} }
} }
export async function saveAsDrafts( // ─── FASE 2: Generar HUs dentro de las épicas confirmadas ──
export async function analyzeProjectHUs(
projectId: number, projectId: number,
analysis: AnalysisResult, projectName: string,
existingHUs: EnrichedUserStory[],
confirmedEpics: AnalysisEpic[], // épicas que el usuario aceptó y ya están o serán enviadas a KAPPA
signal?: AbortSignal,
): Promise<AnalysisHUsResult> {
const context = await buildProjectContext(projectId, projectName, existingHUs)
const epicsDetail = confirmedEpics.map(e =>
`- ${e.name}: ${e.description?.slice(0, 200)} (HUs sugeridas: ${e.linkedHuTitles.join(', ')})`
).join('\n')
const userContent = `Contexto del proyecto:\n${JSON.stringify(context, null, 0)}\n\nÉpicas confirmadas:\n${epicsDetail}`
console.log(`[Alpha] Phase 2 — Generating HUs for ${confirmedEpics.length} epics in ${projectName}`)
const systemPrompt = await getPrompt('project_gap')
const phasePrompt = `[FASE 2: GENERAR HUs]
Las siguientes épicas ya están definidas. Tu tarea es generar las HUs (feature, task, US, bug, spike, etc.) que pertenecen a cada épica.
Cada HU debe estar vinculada a UNA épica existente.
Usá el campo "epicName" para indicar a qué épica pertenece cada HU.
No generes HUs duplicadas con las que ya existen en KAPPA.
Incluí para cada HU: título, descripción, criterios de aceptación, prioridad, story points, tipo, feature, sprint estimado.
${systemPrompt}
IMPORTANTE: Respondé SOLO con HUs en esta fase. NO generes épicas nuevas.`
const content = await callAI(
[{ role: 'system', content: phasePrompt }, { role: 'user', content: userContent }],
0.3, 8192, signal,
)
try {
const jsonStr = extractJSON(content)
const parsed = JSON.parse(jsonStr)
const rawHUs: AnalysisHU[] = parsed.hus || []
// Asignar códigos jerárquicos (E01-F04, E01-T01...) a las HUs propuestas
const counters = analyzeExisting([], existingHUs)
const epicsWithCodes = confirmedEpics.map(e => ({
name: e.name,
epicCode: (e as any).epicCode || '',
}))
const husWithCodes = assignItemCodes(rawHUs, epicsWithCodes, counters)
const result: AnalysisHUsResult = {
hus: husWithCodes,
summary: parsed.summary || '',
}
console.log(`[Alpha] Phase 2 complete: ${result.hus.length} HUs generadas con códigos`)
return result
} catch (e) {
console.error('[Alpha] Failed to parse HUs analysis. Raw:', content)
throw new Error('No se pudieron generar las HUs')
}
}
// ─── Guardar drafts ───────────────────────────────────
export async function saveEpicDrafts(
projectId: number,
epics: AnalysisEpic[],
existingHUs: EnrichedUserStory[], existingHUs: EnrichedUserStory[],
sourceSessionId?: number,
): Promise<{ saved: number; skipped: number }> { ): Promise<{ saved: number; skipped: number }> {
let saved = 0 let saved = 0
let skipped = 0 let skipped = 0
// Guardar HUs for (const epic of epics) {
for (const hu of analysis.hus || []) { const epicWithCode = epic as any
const epicCode = epicWithCode.epicCode || ''
const fullTitle = epicCode ? `[${epicCode}] ${epic.name}` : epic.name
const normalizedName = epic.name.toLowerCase().trim()
const isDuplicate = existingHUs.some(ex => (ex._cleanTitle || ex.title).toLowerCase().trim() === normalizedName)
if (isDuplicate) { skipped++; continue }
await saveDraft({
id: createDraftId(), projectId, title: fullTitle,
description: epic.description, acceptanceCriteria: '',
priority: 'Media', type: 'E',
metadata: JSON.stringify({
linkedHuTitles: epic.linkedHuTitles,
estimatedStart: epic.estimatedStart,
estimatedEnd: epic.estimatedEnd,
epicCode,
}),
syncStatus: 'draft', createdAt: new Date().toISOString(),
})
saved++
}
return { saved, skipped }
}
export async function saveHUDrafts(
projectId: number,
hus: AnalysisHU[],
existingHUs: EnrichedUserStory[],
): Promise<{ saved: number; skipped: number }> {
let saved = 0
let skipped = 0
for (const hu of hus) {
const normalizedTitle = hu.title.toLowerCase().trim() const normalizedTitle = hu.title.toLowerCase().trim()
const isDuplicate = existingHUs.some(ex => { const isDuplicate = existingHUs.some(ex => {
const et = (ex._cleanTitle || ex.title).toLowerCase().trim() const et = (ex._cleanTitle || ex.title).toLowerCase().trim()
@@ -102,27 +240,14 @@ export async function saveAsDrafts(
await saveDraft({ await saveDraft({
id: draftId, projectId, title: hu.title, id: draftId, projectId, title: hu.title,
description: hu.description, acceptanceCriteria: hu.acceptance_criteria.join('\n'), description: hu.description, acceptanceCriteria: hu.acceptance_criteria.join('\n'),
priority: hu.priority, type: 'U', metadata: '{}', priority: hu.priority, type: hu.type === 'task' ? 'T' : hu.type === 'bug' ? 'B' : hu.type === 'feature' ? 'F' : 'U',
sourceSessionId, syncStatus: 'draft', createdAt: new Date().toISOString(), metadata: JSON.stringify({
}) epicName: hu.epicName,
// QA plan: fire-and-forget para no bloquear el guardado feature: hu.feature,
generateAndSavePlan(projectId, draftId, hu.title, hu.description, hu.acceptance_criteria.join('\n')) sprint: hu.sprint,
.catch(e => console.error(`[Alpha] QA auto-gen failed for ${hu.title}:`, e)) storyPoints: hu.story_points,
saved++ }),
} syncStatus: 'draft', createdAt: new Date().toISOString(),
// Guardar épicas
for (const epic of analysis.epics || []) {
const normalizedName = epic.name.toLowerCase().trim()
const isDuplicate = existingHUs.some(ex => (ex._cleanTitle || ex.title).toLowerCase().trim() === normalizedName)
if (isDuplicate) { skipped++; continue }
await saveDraft({
id: createDraftId(), projectId, title: epic.name,
description: epic.description, acceptanceCriteria: '',
priority: 'Media', type: 'E',
metadata: JSON.stringify({ linkedHuTitles: epic.linkedHuTitles, estimatedStart: epic.estimatedStart, estimatedEnd: epic.estimatedEnd }),
sourceSessionId, syncStatus: 'draft', createdAt: new Date().toISOString(),
}) })
saved++ saved++
} }
+46 -15
View File
@@ -20,8 +20,9 @@ Reglas:
3. Los criterios de aceptación deben ser verificables (condiciones específicas) 3. Los criterios de aceptación deben ser verificables (condiciones específicas)
4. Usa el formato "Como [rol] quiero [funcionalidad] para [beneficio]" cuando sea posible 4. Usa el formato "Como [rol] quiero [funcionalidad] para [beneficio]" cuando sea posible
5. Asigna prioridad (Alta/Media/Baja) basada en urgencia implícita 5. Asigna prioridad (Alta/Media/Baja) basada en urgencia implícita
6. No inventes información que no esté en la transcripción 6. Asigna story points donde 1 SP = 1 hora de trabajo estimado
7. Si el texto no contiene información relevante para HUs, devuelve un arreglo vacío 7. No inventes información que no esté en la transcripción
8. Si el texto no contiene información relevante para HUs, devuelve un arreglo vacío
Responde SOLO con JSON válido en este formato: Responde SOLO con JSON válido en este formato:
{ {
@@ -40,38 +41,68 @@ Responde SOLO con JSON válido en este formato:
}, },
project_gap: { project_gap: {
label: 'Análisis de brechas del proyecto', label: 'Análisis de brechas del proyecto',
content: `Eres un analista funcional experto. Tu tarea es analizar TODO el contexto de un proyecto y generar las Épicas e Historias de Usuario (HUs) que faltan. content: `Eres un analista funcional experto en metodologías ágiles.
Trabajás en DOS FASES separadas. Cada fase tiene sus propias instrucciones.
Reglas: Reglas generales:
1. Analizá TODA la información disponible: sesiones, resúmenes, estado del proyecto, HUs existentes 1. Analizá TODA la información disponible: sesiones, resúmenes, estado del proyecto, HUs existentes
2. Identificá requisitos, funcionalidades, mejoras o bugs que NO estén cubiertos 2. Identificá requisitos, funcionalidades, mejoras o bugs que NO estén cubiertos
3. Agrupá HUs relacionadas en Épicas. Cada épica agrupa funcionalidades de un mismo tema 3. No generes duplicados. Compará con TODO lo que ya existe
4. Cada HU debe tener: título claro, descripción, criterios de aceptación verificables 4. Respondé SOLO con JSON válido
5. No generes duplicados. Compará con la lista existente 5. Si todo ya está cubierto, devolvé arreglos vacíos
6. Priorizá según urgencia implícita (Alta/Media/Baja)
7. Si todo ya está cubierto, devolvé arreglos vacíos
8. Respondé SOLO con JSON válido
Formato de respuesta: --- FASE 1: Generar Épicas ---
Instrucciones específicas:
- Identificá las épicas necesarias agrupando funcionalidades por tema
- Revisá si las épicas ya existentes cubren las necesidades
- Si una épica ya existe en KAPPA, NO la generes de nuevo
- Para cada épica, listá los títulos tentativos de las HUs que pertenecerían a ella (linkedHuTitles)
- Incluí un campo "rationale" explicando por qué proponés cada épica
- NO generes HUs en esta fase
Formato de respuesta FASE 1:
{ {
"epics": [ "epics": [
{ {
"name": "Nombre de la Épica", "name": "Nombre de la Épica (ej: Módulo de Pagos)",
"description": "Descripción de la épica", "description": "Descripción de la épica",
"linkedHuTitles": ["Título HU 1", "Título HU 2"], "linkedHuTitles": ["Título HU 1", "Título HU 2"],
"estimatedStart": "YYYY-MM-DD", "estimatedStart": "YYYY-MM-DD",
"estimatedEnd": "YYYY-MM-DD" "estimatedEnd": "YYYY-MM-DD"
} }
], ],
"summary": "Resumen del análisis de épicas",
"rationale": "Explicación de por qué estas épicas y no otras"
}
--- FASE 2: Generar HUs dentro de Épicas ---
Instrucciones específicas:
- Las épicas ya están definidas. Generá las HUs que pertenecen a cada una.
- NO generes códigos jerárquicos en los títulos. El sistema los asigna después.
- Proponé solo el nombre del ítem sin prefijos como [E01-F04].
- Cada HU debe tener un campo "epicName" con el NOMBRE de la épica a la que pertenece.
- Tipos de HU: feature, task, US (historia de usuario), bug, spike
- Incluí: título, descripción, criterios de aceptación, prioridad, story points, tipo, feature, sprint estimado
- Story points: 1 SP = 1 hora de trabajo estimado. Una jornada laboral = 8 SP
- No generes HUs duplicadas con las existentes
- NO generes épicas en esta fase
Formato de respuesta FASE 2:
{
"hus": [ "hus": [
{ {
"title": "Título de la HU", "title": "Nombre del ítem (sin código jerárquico)",
"description": "Descripción detallada", "description": "Descripción detallada",
"acceptance_criteria": ["Criterio 1", "Criterio 2"], "acceptance_criteria": ["Criterio 1", "Criterio 2"],
"priority": "Alta|Media|Baja" "priority": "Alta|Media|Baja",
"story_points": 3,
"type": "feature|task|bug|spike",
"feature": "Nombre de la feature",
"sprint": 12,
"epicName": "Nombre exacto de la épica a la que pertenece"
} }
], ],
"summary": "Resumen del análisis" "summary": "Resumen de las HUs generadas"
}`, }`,
}, },
session: { session: {
+198
View File
@@ -0,0 +1,198 @@
import {
Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
HeadingLevel, AlignmentType, WidthType, ShadingType,
} from 'docx'
import type { EnrichedUserStory, EnrichedEpic } from '@/stores/workitems'
import { getSessionsByProject, getSessionSummary, getProjectState } from '@/services/transcriptions-db'
export interface ReportData {
projectId: number
projectName: string
epicCount: number
huCount: number
inProgressCount: number
doneCount: number
blockedCount: number
epics: EnrichedEpic[]
hus: EnrichedUserStory[]
totalSessions: number
metrics: {
totalSpPlanned: number
totalSpCompleted: number
velocityPerWeek: number
spi: number
estimatedEndDate: string
clientBlockedHours: number
totalBlockedHours: number
}
generatedAt: string
}
function cell(text: string, bold = false, shading?: string): TableCell {
return new TableCell({
children: [new Paragraph({
children: [new TextRun({ text, bold, size: '20pt' })],
spacing: { before: 40, after: 40 },
})],
shading: shading ? { type: ShadingType.CLEAR, fill: shading } : undefined,
})
}
function headerRow(...headers: string[]): TableRow {
return new TableRow({ children: headers.map(h => cell(h, true, 'F2F2F2')) })
}
function dataRow(...values: string[]): TableRow {
return new TableRow({ children: values.map(v => cell(v)) })
}
export async function generateReportDocx(data: ReportData): Promise<Blob> {
const sessions = await getSessionsByProject(data.projectId)
const state = await getProjectState(data.projectId)
const summary = state?.summary || 'Sin resumen disponible'
const doc = new Document({
sections: [{
children: [
// ═══════════ TITLE ═══════════
new Paragraph({
text: `${data.projectName} — Informe de Avance del Proyecto`,
heading: HeadingLevel.TITLE,
alignment: AlignmentType.CENTER,
spacing: { after: 100 },
}),
new Paragraph({
spacing: { after: 400 },
children: [
new TextRun({ text: `Fecha de corte: ${data.generatedAt}`, size: '20pt' }),
],
}),
// ═══════════ 1. ESTADO GENERAL ═══════════
new Paragraph({ text: '1. Estado General', heading: HeadingLevel.HEADING_1, spacing: { before: 400, after: 200 } }),
new Table({
rows: [
headerRow('Métrica', 'Valor'),
dataRow('Total Épicas', String(data.epicCount)),
dataRow('Total HUs', String(data.huCount)),
dataRow('En Progreso', String(data.inProgressCount)),
dataRow('Completadas', String(data.doneCount)),
dataRow('Bloqueadas', String(data.blockedCount)),
dataRow('SP Planificados', String(data.metrics.totalSpPlanned)),
dataRow('SP Completados', String(data.metrics.totalSpCompleted)),
],
width: { size: 100, type: WidthType.PERCENTAGE },
}),
// ═══════════ 2. RESUMEN EJECUTIVO ═══════════
new Paragraph({ text: '2. Resumen Ejecutivo', heading: HeadingLevel.HEADING_1, spacing: { before: 400, after: 200 } }),
new Paragraph({
spacing: { after: 200 },
children: [new TextRun({ text: summary, size: '20pt' })],
}),
...(data.metrics.totalBlockedHours > 0 ? [
new Paragraph({
spacing: { before: 100 },
children: [new TextRun({
text: `⏱ Horas bloqueadas: ${data.metrics.totalBlockedHours}h totales (${data.metrics.clientBlockedHours}h imputables al cliente)`,
size: '20pt',
bold: true,
})],
}),
] : []),
// ═══════════ 3. ÉPICAS ═══════════
new Paragraph({ text: '3. Épicas', heading: HeadingLevel.HEADING_1, spacing: { before: 400, after: 200 } }),
...data.epics.map(epic => new Paragraph({
spacing: { before: 80, after: 40 },
children: [
new TextRun({ text: `📦 ${(epic as any)._epicCode || `EP-${epic.id}`}`, bold: true, size: '20pt' }),
new TextRun({ text: ` ${(epic as any)._cleanName || epic.name || epic.title || ''}`, size: '20pt' }),
],
})),
// ═══════════ 4. HUs ═══════════
new Paragraph({ text: '4. HUs — Resumen', heading: HeadingLevel.HEADING_1, spacing: { before: 400, after: 200 } }),
new Table({
rows: [
headerRow('Código', 'Título', 'Estado', 'Prioridad', 'SP'),
...data.hus.slice(0, 50).map(hu => dataRow(
hu.code || `#${hu.id}`,
(hu._cleanTitle || hu.title).slice(0, 60),
hu._statusName || String(hu.status || '—'),
String(hu.priority || '—'),
String(hu.story_points ?? '—'),
)),
],
width: { size: 100, type: WidthType.PERCENTAGE },
}),
new Paragraph({
spacing: { before: 100 },
children: [new TextRun({ text: `Mostrando 50 de ${data.huCount} HUs`, size: '20pt', italics: true, color: '888888' })],
}),
// ═══════════ 5. HUs BLOQUEADAS ═══════════
...(data.blockedCount > 0 ? [
new Paragraph({ text: '5. HUs Bloqueadas', heading: HeadingLevel.HEADING_1, spacing: { before: 400, after: 200 } }),
...data.hus.filter(h => {
const s = String(h.status ?? '').toLowerCase()
return ['blocked', 'bloqueado'].includes(s)
}).map(hu => new Paragraph({
spacing: { before: 60, after: 60 },
children: [new TextRun({ text: `${hu._cleanTitle || hu.title}`, size: '20pt' })],
})),
] : []),
// ═══════════ 6. CURVA S ═══════════
new Paragraph({ text: '6. Curva S del Proyecto', heading: HeadingLevel.HEADING_1, spacing: { before: 400, after: 200 } }),
new Paragraph({
children: [new TextRun({
text: `La curva S muestra el avance planificado vs real. SPI actual: ${data.metrics.spi}. Velocidad: ${data.metrics.velocityPerWeek} SP/semana. Proyección de finalización: ${data.metrics.estimatedEndDate || 'N/A'}.`,
size: '20pt',
})],
spacing: { after: 200 },
}),
// ═══════════ 7. CURVA S ═══════════
new Paragraph({ text: '7. Carga de Trabajo Actual', heading: HeadingLevel.HEADING_1, spacing: { before: 400, after: 200 } }),
new Paragraph({
children: [new TextRun({
text: `${data.inProgressCount} HUs en progreso · ${data.blockedCount} bloqueadas · ${data.doneCount} completadas de ${data.huCount} totales.`,
size: '20pt',
})],
}),
// ═══════════ 8. AVANCES DE LA SEMANA ═══════════
...(sessions.length > 0 ? [
new Paragraph({ text: '8. Avances Registrados', heading: HeadingLevel.HEADING_1, spacing: { before: 400, after: 200 } }),
...(await Promise.all(sessions.slice(-10).reverse().map(async (s) => {
const sum = await getSessionSummary(s.id!)
return new Paragraph({
spacing: { before: 60, after: 60 },
children: [
new TextRun({ text: `📅 ${s.date}`, bold: true, size: '20pt' }),
new TextRun({ text: `${s.title}`, size: '20pt' }),
...(sum?.summary ? [new TextRun({ text: `\n${sum.summary.slice(0, 300)}`, size: '20pt', italics: true, color: '555555' })] : []),
],
})
}))),
] : []),
// ═══════════ FOOTER ═══════════
new Paragraph({
spacing: { before: 600 },
alignment: AlignmentType.CENTER,
children: [new TextRun({
text: `Documento generado automáticamente por Alpha — ${data.generatedAt}`,
size: '20pt', italics: true, color: '888888',
})],
}),
],
}],
})
return await Packer.toBlob(doc)
}
+237
View File
@@ -0,0 +1,237 @@
import type { EnrichedUserStory } from '@/stores/workitems'
import type { BlockerRecord } from '@/services/blocker-log'
export interface CurvePoint {
date: string // ISO date YYYY-MM-DD
cumulative: number // cumulative SP
}
export interface SCurveData {
planned: CurvePoint[]
actual: CurvePoint[]
projection: CurvePoint[]
metrics: SCurveMetrics
}
export interface SCurveMetrics {
totalSpPlanned: number
totalSpCompleted: number
velocityPerWeek: number // average SP completed per week
weeksElapsed: number
weeksRemaining: number // estimated weeks to completion
estimatedEndDate: string // projected completion date
spi: number // Schedule Performance Index
clientBlockedHours: number
totalBlockedHours: number
}
const WORKING_HOURS_PER_DAY = 8
const WORKING_DAYS_PER_WEEK = 5
const SP_PER_WEEK = WORKING_HOURS_PER_DAY * WORKING_DAYS_PER_WEEK // 40
/**
* Calcula la curva planificada: distribuye los SP de cada HU
* linealmente entre su start_date y end_date.
*/
export function calculatePlannedCurve(hus: EnrichedUserStory[]): CurvePoint[] {
const points: { date: string; sp: number }[] = []
for (const hu of hus) {
const sp = hu.story_points || 0
if (sp <= 0) continue
// Si no tiene fechas, asumimos que empieza hoy y dura 1 día por SP
const start = hu.initial_date || new Date().toISOString().split('T')[0]
const end = hu.end_date || calcEndDate(start, sp)
// Distribuir SP linealmente entre start y end
const startD = new Date(start)
const endD = new Date(end)
const totalDays = Math.max(1, Math.ceil((endD.getTime() - startD.getTime()) / (1000 * 60 * 60 * 24)) + 1)
const spPerDay = sp / totalDays
for (let i = 0; i < totalDays; i++) {
const d = new Date(startD)
d.setDate(d.getDate() + i)
// Skip weekends
if (d.getDay() === 0 || d.getDay() === 6) continue
const dateStr = d.toISOString().split('T')[0]
points.push({ date: dateStr, sp: spPerDay })
}
}
// Acumular por fecha
return accumulatePoints(points)
}
/**
* Calcula la curva real desde daily logs (datos de progreso real).
* Por ahora, usa el end_date + status para estimar progreso.
*/
export function calculateActualCurve(hus: EnrichedUserStory[]): CurvePoint[] {
const points: { date: string; sp: number }[] = []
for (const hu of hus) {
const sp = hu.story_points || 0
if (sp <= 0) continue
const s = String(hu.status || '').toLowerCase()
const isDone = ['done', 'completed', 'closed', 'finalizado', '5', '6', '7', 'qa-client', 'ready to deploy'].includes(s)
const isInProgress = ['in_progress', 'doing', 'wip', 'active', 'in progress', 'en progreso', 'true', '2'].includes(s)
if (isDone && hu.end_date) {
points.push({ date: hu.end_date, sp })
} else if (isInProgress && hu.end_date) {
// 50% completado (estimado)
const halfDate = hu.initial_date
? midpointDate(hu.initial_date, hu.end_date)
: hu.end_date
points.push({ date: halfDate, sp: Math.round(sp * 0.5) })
}
}
return accumulatePoints(points)
}
/**
* Calcula la proyección: extiende la curva real usando la velocidad actual.
*/
export function calculateProjection(
actual: CurvePoint[],
planned: CurvePoint[],
totalSpPlanned: number,
totalSpCompleted: number,
): { projection: CurvePoint[]; estimatedEndDate: string; weeksRemaining: number } {
if (actual.length === 0) {
return {
projection: [],
estimatedEndDate: planned[planned.length - 1]?.date || '',
weeksRemaining: 0,
}
}
// Calcular velocidad semanal
const firstActual = new Date(actual[0].date)
const lastActual = new Date(actual[actual.length - 1].date)
const daysElapsed = Math.max(1, Math.ceil((lastActual.getTime() - firstActual.getTime()) / (1000 * 60 * 60 * 24)))
const weeksElapsed = Math.max(1, daysElapsed / WORKING_DAYS_PER_WEEK)
const velocityPerWeek = totalSpCompleted / weeksElapsed
if (velocityPerWeek <= 0) {
return { projection: [], estimatedEndDate: '', weeksRemaining: 0 }
}
const remainingSp = totalSpPlanned - totalSpCompleted
const weeksRemaining = Math.ceil(remainingSp / velocityPerWeek)
const estimatedEnd = new Date(lastActual)
estimatedEnd.setDate(estimatedEnd.getDate() + weeksRemaining * WORKING_DAYS_PER_WEEK)
// Generar puntos de proyección
const projection: CurvePoint[] = []
const lastCumulative = actual[actual.length - 1]?.cumulative || 0
let projectedCumulative = lastCumulative
const projDate = new Date(lastActual)
for (let w = 0; w <= weeksRemaining; w++) {
projectedCumulative += velocityPerWeek * WORKING_DAYS_PER_WEEK / 7
if (projectedCumulative > totalSpPlanned) projectedCumulative = totalSpPlanned
projDate.setDate(projDate.getDate() + 7)
projection.push({
date: projDate.toISOString().split('T')[0],
cumulative: Math.round(projectedCumulative),
})
}
return {
projection,
estimatedEndDate: estimatedEnd.toISOString().split('T')[0],
weeksRemaining,
}
}
/**
* Calcula todas las métricas de la curva S.
*/
export function calculateSCurve(hus: EnrichedUserStory[], blockers: BlockerRecord[]): SCurveData {
const planned = calculatePlannedCurve(hus)
const actual = calculateActualCurve(hus)
const totalSpPlanned = hus.reduce((s, h) => s + (h.story_points || 0), 0)
const totalSpCompleted = actual.length > 0 ? actual[actual.length - 1].cumulative : 0
const firstActual = actual.length > 0 ? new Date(actual[0].date) : new Date()
const lastActual = actual.length > 0 ? new Date(actual[actual.length - 1].date) : new Date()
const daysElapsed = Math.max(1, Math.ceil((lastActual.getTime() - firstActual.getTime()) / (1000 * 60 * 60 * 24)))
const weeksElapsed = Math.max(0.5, daysElapsed / WORKING_DAYS_PER_WEEK)
const velocityPerWeek = weeksElapsed > 0 ? +(totalSpCompleted / weeksElapsed).toFixed(1) : 0
// SPI = SP completados / SP planeados en el mismo periodo
const plannedAtEnd = planned.filter(p => new Date(p.date) <= lastActual)
const plannedInPeriod = plannedAtEnd.length > 0 ? plannedAtEnd[plannedAtEnd.length - 1].cumulative : 0
const spi = plannedInPeriod > 0 ? +(totalSpCompleted / plannedInPeriod).toFixed(2) : 1
const { estimatedEndDate, weeksRemaining } = calculateProjection(actual, planned, totalSpPlanned, totalSpCompleted)
// Estadísticas de bloqueos
const clientBlockedHours = blockers
.filter(b => b.category === 'client')
.reduce((s, b) => s + (b.timeLostHours || 0), 0)
const totalBlockedHours = blockers.reduce((s, b) => s + (b.timeLostHours || 0), 0)
return {
planned,
actual,
projection: [],
metrics: {
totalSpPlanned,
totalSpCompleted,
velocityPerWeek,
weeksElapsed,
weeksRemaining,
estimatedEndDate,
spi,
clientBlockedHours,
totalBlockedHours,
},
}
}
// ─── Helpers ────────────────────────────────────────
function accumulatePoints(points: { date: string; sp: number }[]): CurvePoint[] {
if (points.length === 0) return []
const sorted = [...points].sort((a, b) => a.date.localeCompare(b.date))
const result: CurvePoint[] = []
let cumulative = 0
let currentDate = sorted[0].date
let daySum = 0
for (const p of sorted) {
if (p.date !== currentDate) {
cumulative += Math.round(daySum)
result.push({ date: currentDate, cumulative })
currentDate = p.date
daySum = 0
}
daySum += p.sp
}
// Último día
cumulative += Math.round(daySum)
result.push({ date: currentDate, cumulative })
return result
}
function calcEndDate(start: string, sp: number): string {
const d = new Date(start)
d.setDate(d.getDate() + sp)
return d.toISOString().split('T')[0]
}
function midpointDate(start: string, end: string): string {
const s = new Date(start)
const e = new Date(end)
const mid = new Date((s.getTime() + e.getTime()) / 2)
return mid.toISOString().split('T')[0]
}
+138
View File
@@ -0,0 +1,138 @@
import { storage } from '@/services/storage'
const STORAGE_KEY = 'teams_webhook_url'
export function getWebhookUrl(): string {
return storage.get(STORAGE_KEY) || ''
}
export function setWebhookUrl(url: string): void {
storage.set(STORAGE_KEY, url)
}
export function hasWebhook(): boolean {
return !!getWebhookUrl()
}
interface TeamsMessage {
title?: string
text: string
themeColor?: string
sections?: {
activityTitle?: string
activitySubtitle?: string
facts?: { name: string; value: string }[]
text?: string
}[]
}
/**
* Envía un mensaje a Teams via webhook.
* Formato: https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook
* Soporta tanto Tauri (plugin-http) como browser (fetch).
*/
export async function sendToTeams(message: TeamsMessage): Promise<boolean> {
const webhookUrl = getWebhookUrl()
if (!webhookUrl) {
console.warn('[Teams] No hay webhook configurado')
return false
}
const payload = {
'@type': 'MessageCard',
'@context': 'http://schema.org/extensions',
summary: message.title || message.text.slice(0, 50),
title: message.title,
text: message.text,
themeColor: message.themeColor || '0078D4',
sections: message.sections || [],
}
try {
// Intentar usar Tauri plugin HTTP primero
if (typeof window !== 'undefined' && (window as any).__TAURI_INTERNALS__) {
const { fetch } = await import('@tauri-apps/plugin-http')
const res = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
if (!res.ok) {
console.error(`[Teams] Error HTTP ${res.status}`)
return false
}
console.log('[Teams] Mensaje enviado via Tauri plugin')
return true
}
// Fallback browser fetch
const res = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
if (!res.ok) {
console.error(`[Teams] Error HTTP ${res.status}`)
return false
}
console.log('[Teams] Mensaje enviado via browser fetch')
return true
} catch (e: any) {
console.error('[Teams] Error al enviar mensaje:', e)
return false
}
}
/**
* Envía una notificación de HU creada.
*/
export async function notifyHUCreated(projectName: string, huTitle: string, huUrl?: string): Promise<boolean> {
return sendToTeams({
title: '🆕 Nueva HU creada',
text: `**Proyecto:** ${projectName}`,
themeColor: '0078D4',
sections: [{
facts: [
{ name: 'HU', value: huTitle },
{ name: 'Proyecto', value: projectName },
{ name: 'URL', value: huUrl || window.location.href },
],
}],
})
}
/**
* Envía una notificación de bloqueo/impedimento.
*/
export async function notifyBlockerLogged(
projectName: string,
huTitle: string,
category: string,
description: string,
hoursLost: number,
): Promise<boolean> {
return sendToTeams({
title: '⛔ Bloqueo registrado',
text: `**Proyecto:** ${projectName}`,
themeColor: 'FF4444',
sections: [{
facts: [
{ name: 'HU', value: huTitle },
{ name: 'Categoría', value: category },
{ name: 'Descripción', value: description },
{ name: 'Horas perdidas', value: `${hoursLost}h` },
],
}],
})
}
/**
* Envía un mensaje de prueba.
*/
export async function sendTestMessage(): Promise<boolean> {
return sendToTeams({
title: '🔔 Prueba de integración',
text: 'Este es un mensaje de prueba desde **Alpha**.\n\nSi ves esto, la integración con Teams funciona correctamente.',
themeColor: '00FF00',
})
}
+9 -1
View File
@@ -19,12 +19,15 @@ export interface EnrichedUserStory extends KappaUserStory {
_assignedName: string _assignedName: string
_statusName: string _statusName: string
_assignedEmployeeId: number | null _assignedEmployeeId: number | null
_epicCode: string | null
_itemCode: string | null
} }
export interface EnrichedEpic extends KappaEpicDevelopment { export interface EnrichedEpic extends KappaEpicDevelopment {
_itemType: ItemType _itemType: ItemType
_hierarchyPath: string | null _hierarchyPath: string | null
_cleanName: string _cleanName: string
_epicCode: string | null
} }
export const useWorkItemsStore = defineStore('workitems', () => { export const useWorkItemsStore = defineStore('workitems', () => {
@@ -61,6 +64,7 @@ export const useWorkItemsStore = defineStore('workitems', () => {
_itemType: h?.items[h.items.length - 1]?.type || 'E', _itemType: h?.items[h.items.length - 1]?.type || 'E',
_hierarchyPath: h?.fullPath ?? null, _hierarchyPath: h?.fullPath ?? null,
_cleanName: h ? stripHierarchy(title) : title, _cleanName: h ? stripHierarchy(title) : title,
_epicCode: h?.epicCode ?? null,
} }
} }
@@ -89,7 +93,7 @@ export const useWorkItemsStore = defineStore('workitems', () => {
return { id: null, name: '', employeeId: null } return { id: null, name: '', employeeId: null }
} }
function enrichHU(hu: { title?: string; id?: number; description?: string | null; status?: string | number | null; status_name?: string | null; priority?: string | number | null; story_points?: number | null; acceptance_criteria?: string | null; criterios_aceptacion?: string | null; assigned_to?: number | null; asignado_a?: number[] | string[] | null; asignado_a_names?: string[] | string | null; assigned_name?: string }, initiativeId: number): EnrichedUserStory { function enrichHU(hu: { title?: string; id?: number; description?: string | null; status?: string | number | null; status_name?: string | null; priority?: string | number | null; story_points?: number | null; end_date?: string | null; sprint?: number | string | null; acceptance_criteria?: string | null; criterios_aceptacion?: string | null; assigned_to?: number | null; asignado_a?: number[] | string[] | null; asignado_a_names?: string[] | string | null; assigned_name?: string }, initiativeId: number): EnrichedUserStory {
const h = parseHierarchy(hu.title || '') const h = parseHierarchy(hu.title || '')
const rawCriteria = hu.acceptance_criteria || hu.criterios_aceptacion || '' const rawCriteria = hu.acceptance_criteria || hu.criterios_aceptacion || ''
const criteriaList = rawCriteria ? parseQuillList(rawCriteria) : [] const criteriaList = rawCriteria ? parseQuillList(rawCriteria) : []
@@ -100,6 +104,8 @@ export const useWorkItemsStore = defineStore('workitems', () => {
status: String(hu.status ?? ''), status: String(hu.status ?? ''),
priority: String(hu.priority ?? ''), priority: String(hu.priority ?? ''),
story_points: hu.story_points ?? undefined, story_points: hu.story_points ?? undefined,
end_date: hu.end_date || undefined,
sprint: hu.sprint != null ? String(hu.sprint) : undefined,
description: hu.description || '', description: hu.description || '',
acceptance_criteria: rawCriteria, acceptance_criteria: rawCriteria,
criterios_aceptacion: rawCriteria, criterios_aceptacion: rawCriteria,
@@ -113,6 +119,8 @@ export const useWorkItemsStore = defineStore('workitems', () => {
_assignedName: assignedName, _assignedName: assignedName,
_statusName: hu.status_name || resolveStatusName(hu.status), _statusName: hu.status_name || resolveStatusName(hu.status),
_assignedEmployeeId: employeeId, _assignedEmployeeId: employeeId,
_epicCode: h?.epicCode ?? null,
_itemCode: h?.itemCode ?? null,
} }
} }
+3 -1
View File
@@ -48,7 +48,9 @@ export interface KappaUserStory {
priority?: string | number priority?: string | number
initiative: number | string initiative: number | string
story_points?: number story_points?: number
sprint?: string sprint?: number | string
end_date?: string
initial_date?: string | null
created_at?: string created_at?: string
assigned_to?: number | null assigned_to?: number | null
asignado_a?: number[] | string[] | null asignado_a?: number[] | string[] | null
+241 -61
View File
@@ -6,12 +6,15 @@ import { useWorkItemsStore } from '@/stores/workitems'
import { useUsersStore } from '@/stores/users' import { useUsersStore } from '@/stores/users'
import { storage } from '@/services/storage' import { storage } from '@/services/storage'
import { getTypeLabel, getTypeColor, getTypeIcon } from '@/services/hierarchy' import { getTypeLabel, getTypeColor, getTypeIcon } from '@/services/hierarchy'
import { Activity, FileText, Layers, Clock, Info, AlertTriangle, Plus, Brain, Sparkles, Loader2, CheckCircle2, XCircle, Send, ChevronDown, Eye } from 'lucide-vue-next' import { Activity, FileText, Layers, Clock, Info, AlertTriangle, Plus, Brain, Sparkles, Loader2, CheckCircle2, XCircle, Send, ChevronDown, Eye, TrendingUp } from 'lucide-vue-next'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import HuDrafts from '@/components/HuDrafts.vue' import HuDrafts from '@/components/HuDrafts.vue'
import AiProjectChat from '@/components/AiProjectChat.vue' import AiProjectChat from '@/components/AiProjectChat.vue'
import { analyzeProject, saveAsDrafts } from '@/services/project-analyzer' import PrioritizerCard from '@/components/PrioritizerCard.vue'
import SCurveChart from '@/components/SCurveChart.vue'
// import { generateReportDocx } from '@/services/report-export' // Sidecar Python pending
import { analyzeProjectEpics, analyzeProjectHUs, saveEpicDrafts, saveHUDrafts } from '@/services/project-analyzer'
import { getDrafts, deleteDraft, type HuDraftRecord } from '@/services/hu-drafts-db' import { getDrafts, deleteDraft, type HuDraftRecord } from '@/services/hu-drafts-db'
import { kappa } from '@/services/kappa-api' import { kappa } from '@/services/kappa-api'
import { generateAndSavePlan, getQAPlans, type HUQAPlan } from '@/services/qa-analyzer' import { generateAndSavePlan, getQAPlans, type HUQAPlan } from '@/services/qa-analyzer'
@@ -40,10 +43,10 @@ const usersStore = useUsersStore()
const project = computed(() => projects.selected) const project = computed(() => projects.selected)
// Inline notification (funciona en Tauri) // Inline notification (funciona en Tauri)
const notification = ref<{ type: 'success' | 'error'; message: string } | null>(null) const notification = ref<{ type: 'success' | 'error' | 'info'; message: string } | null>(null)
let notifTimeout: ReturnType<typeof setTimeout> | null = null let notifTimeout: ReturnType<typeof setTimeout> | null = null
function showNotif(type: 'success' | 'error', message: string) { function showNotif(type: 'success' | 'error' | 'info', message: string) {
if (notifTimeout) clearTimeout(notifTimeout) if (notifTimeout) clearTimeout(notifTimeout)
notification.value = { type, message } notification.value = { type, message }
notifTimeout = setTimeout(() => { notification.value = null }, 5000) notifTimeout = setTimeout(() => { notification.value = null }, 5000)
@@ -154,9 +157,9 @@ function assignedName(hu: { _assignedName?: string; _assignedEmployeeId?: number
// Priority helpers // Priority helpers
const PRIORITY_MAP: Record<string, { label: string; variant: string }> = { const PRIORITY_MAP: Record<string, { label: string; variant: string }> = {
'1': { label: 'Alta', variant: 'destructive' }, '1': { label: 'Baja', variant: 'secondary' },
'2': { label: 'Media', variant: 'default' }, '2': { label: 'Media', variant: 'default' },
'3': { label: 'Baja', variant: 'secondary' }, '3': { label: 'Alta', variant: 'destructive' },
'alta': { label: 'Alta', variant: 'destructive' }, 'alta': { label: 'Alta', variant: 'destructive' },
'high': { label: 'Alta', variant: 'destructive' }, 'high': { label: 'Alta', variant: 'destructive' },
'critical': { label: 'Crítica', variant: 'destructive' }, 'critical': { label: 'Crítica', variant: 'destructive' },
@@ -270,6 +273,22 @@ function getEpicLinkedHUs(d: HuDraftRecord): string[] {
return meta.linkedHuTitles || [] return meta.linkedHuTitles || []
} catch { return [] } } catch { return [] }
} }
function getEpicCode(d: HuDraftRecord): string {
try {
const meta = JSON.parse(d.metadata || '{}')
return meta.epicCode || ''
} catch { return '' }
}
function getItemCode(d: HuDraftRecord): string {
const match = d.title.match(/\[([^\]]+)\]/)
return match ? match[1] : ''
}
function stripEpicTitle(title: string): string {
return title.replace(/^\[[^\]]+\]\s*/, '')
}
const drafts = ref<HuDraftRecord[]>([]) const drafts = ref<HuDraftRecord[]>([])
const pushingDraftId = ref<string | null>(null) const pushingDraftId = ref<string | null>(null)
@@ -289,7 +308,7 @@ async function pushDraft(d: HuDraftRecord) {
let endpoint: string, body: string let endpoint: string, body: string
if (d.type === 'E') { if (d.type === 'E') {
// Push épica // Push épica primero KAPPA asigna el ID
const meta = JSON.parse(d.metadata || '{}') const meta = JSON.parse(d.metadata || '{}')
const linkedHuIds: number[] = [] const linkedHuIds: number[] = []
for (const huTitle of meta.linkedHuTitles || []) { for (const huTitle of meta.linkedHuTitles || []) {
@@ -308,24 +327,51 @@ async function pushDraft(d: HuDraftRecord) {
status: false, status: false,
}) })
} else { } else {
// Push HU // Push HU con epic_development si está vinculada a una épica
const meta = JSON.parse(d.metadata || '{}')
const epicDevId = meta.epicDevelopment || null
endpoint = '/api/userstorys/create/' endpoint = '/api/userstorys/create/'
body = JSON.stringify({ body = JSON.stringify({
initiative: String(d.projectId), title: d.title, initiative: String(d.projectId),
title: d.title,
description: d.description ? `<p>${d.description.replace(/\n/g, '</p><p>')}</p>` : '', description: d.description ? `<p>${d.description.replace(/\n/g, '</p><p>')}</p>` : '',
criterios_aceptacion: d.acceptanceCriteria ? `<p>${d.acceptanceCriteria.replace(/\n/g, '</p><p>')}</p>` : '', criterios_aceptacion: d.acceptanceCriteria ? `<p>${d.acceptanceCriteria.replace(/\n/g, '</p><p>')}</p>` : '',
story_points: String(d.story_points ?? ''), story_points: d.story_points ?? null,
priority: d.priority === 'Alta' ? '1' : d.priority === 'Baja' ? '3' : '2', priority: d.priority === 'Alta' ? '3' : d.priority === 'Baja' ? '1' : '2',
sprint: '', asignado_a: [], client_taker: null, sprint: meta.sprint ?? '',
characterization_hu: '', has_impairment: false, asignado_a: meta.asignado_a ?? [],
epic_development: null, feature: '', client_taker: meta.clientTaker ?? null,
initial_date: null, end_date: null, characterization_hu: meta.characterizationHu ?? '',
has_impairment: false,
epic_development: epicDevId,
feature: meta.feature ?? '',
initial_date: null,
end_date: meta.endDate ?? null,
status: meta.status ?? 1,
is_planned: false,
}) })
} }
const res = await fetch(endpoint, { method: 'POST', headers, body }) const res = await fetch(endpoint, { method: 'POST', headers, body })
if (res.ok) { if (res.ok) {
if (d.type !== 'E') { if (d.type === 'E') {
// Épica creada capturar ID y vincular HUs pendientes
const created = await res.json()
const epicKappaId = created.id
const meta = JSON.parse(d.metadata || '{}')
for (const huTitle of meta.linkedHuTitles || []) {
const linkedDraft = drafts.value.find(x =>
x.type !== 'E' && x.syncStatus === 'draft' &&
x.title.toLowerCase().trim() === huTitle.toLowerCase().trim()
)
if (linkedDraft) {
const linkedMeta = JSON.parse(linkedDraft.metadata || '{}')
linkedMeta.epicDevelopment = String(epicKappaId)
linkedDraft.metadata = JSON.stringify(linkedMeta)
await dbSaveDraft(linkedDraft)
}
}
} else {
const created = await res.json() const created = await res.json()
d.kappaId = created.id || undefined d.kappaId = created.id || undefined
} }
@@ -365,44 +411,121 @@ async function discardDraft(id: string) {
await loadDrafts() await loadDrafts()
} }
// Project analysis // Export report (pending Python sidecar)
// Project analysis Two-phase
const phase = ref<'idle' | 'epics' | 'hus' | 'done'>('idle')
const analyzing = ref(false) const analyzing = ref(false)
const analysisAbort = ref<AbortController | null>(null) const analysisAbort = ref<AbortController | null>(null)
const analysisMessage = ref('')
const analysisResult = ref<{ saved: number; skipped: number } | null>(null) const analysisResult = ref<{ saved: number; skipped: number } | null>(null)
const analysisSummary = ref('')
function cancelAnalysis() { function cancelAnalysis() {
analysisAbort.value?.abort() analysisAbort.value?.abort()
analyzing.value = false analyzing.value = false
analysisAbort.value = null analysisAbort.value = null
analysisSummary.value = 'Análisis cancelado' analysisMessage.value = ''
phase.value = 'idle'
} }
async function runAnalysis() { // Obtener épicas que ya existen en KAPPA
function getExistingEpicNames(): string[] {
return workItems.epics.map(e => (e._cleanName || e.name || e.title || '').toLowerCase().trim()).filter(Boolean)
}
// FASE 1: Generar épicas
async function runPhaseEpics() {
if (!project.value) return if (!project.value) return
phase.value = 'epics'
analyzing.value = true analyzing.value = true
analysisAbort.value = new AbortController() analysisAbort.value = new AbortController()
analysisResult.value = null analysisResult.value = null
analysisSummary.value = '' analysisMessage.value = 'Analizando contexto y generando épicas...'
try { try {
const result = await analyzeProject(project.value.id, project.value.name || '', workItems.userStories, analysisAbort.value?.signal) const existingEpics = getExistingEpicNames()
analysisSummary.value = result.summary const result = await analyzeProjectEpics(
project.value.id, project.value.name || '',
workItems.userStories, existingEpics,
analysisAbort.value?.signal,
workItems.epics,
)
analysisMessage.value = result.rationale
if (result.hus.length > 0) { if (result.epics.length > 0) {
const outcome = await saveAsDrafts(project.value.id, result, workItems.userStories) const outcome = await saveEpicDrafts(project.value.id, result.epics, workItems.userStories)
analysisResult.value = outcome analysisResult.value = outcome
await loadDrafts()
} else { } else {
analysisResult.value = { saved: 0, skipped: 0 } analysisResult.value = { saved: 0, skipped: 0 }
analysisMessage.value = 'No se identificaron nuevas épicas necesarias.'
} }
} catch (e: any) { } catch (e: any) {
if (e.name === 'AbortError' || e.message?.includes('aborted')) { if (e.name === 'AbortError' || e.message?.includes('aborted')) {
analysisSummary.value = 'Análisis cancelado' analysisMessage.value = 'Análisis cancelado'
} else { } else {
console.error('[Alpha] Analysis error:', e) console.error('[Alpha] Phase 1 error:', e)
analysisSummary.value = `Error: ${e.message}` analysisMessage.value = `Error: ${e.message}`
} }
analysisResult.value = { saved: 0, skipped: 0 } analysisResult.value = { saved: 0, skipped: 0 }
phase.value = 'idle'
} finally {
analyzing.value = false
}
}
// FASE 2: Generar HUs dentro de las épicas
async function runPhaseHUs() {
if (!project.value) return
phase.value = 'hus'
analyzing.value = true
analysisAbort.value = new AbortController()
analysisResult.value = null
analysisMessage.value = 'Generando HUs dentro de las épicas...'
try {
// Obtener las épicas confirmadas desde los drafts de tipo épica
const epicDrafts = drafts.value.filter(d => d.type === 'E')
const confirmedEpics = epicDrafts.map(d => ({
name: d.title,
description: d.description,
linkedHuTitles: (() => { try { return JSON.parse(d.metadata || '{}').linkedHuTitles || [] } catch { return [] } })(),
}))
if (confirmedEpics.length === 0) {
analysisMessage.value = 'No hay épicas en borrador. Primero generá y enviá las épicas a KAPPA.'
analysisResult.value = { saved: 0, skipped: 0 }
phase.value = 'idle'
analyzing.value = false
return
}
const result = await analyzeProjectHUs(
project.value.id, project.value.name || '',
workItems.userStories, confirmedEpics,
analysisAbort.value?.signal,
)
analysisMessage.value = result.summary
if (result.hus.length > 0) {
const outcome = await saveHUDrafts(project.value.id, result.hus, workItems.userStories)
analysisResult.value = outcome
await loadDrafts()
phase.value = 'done'
} else {
analysisResult.value = { saved: 0, skipped: 0 }
analysisMessage.value = 'No se identificaron HUs nuevas. Todo parece cubierto.'
phase.value = 'done'
}
} catch (e: any) {
if (e.name === 'AbortError' || e.message?.includes('aborted')) {
analysisMessage.value = 'Análisis cancelado'
} else {
console.error('[Alpha] Phase 2 error:', e)
analysisMessage.value = `Error: ${e.message}`
}
analysisResult.value = { saved: 0, skipped: 0 }
phase.value = 'idle'
} finally { } finally {
analyzing.value = false analyzing.value = false
} }
@@ -421,6 +544,32 @@ watch(
{ immediate: true } { immediate: true }
) )
// Epic progress bar
const epicProgress = computed(() => {
const map = new Map<string, { total: number; done: number }>()
for (const hu of workItems.userStories) {
const epicCode = hu._epicCode || ''
if (!epicCode) continue
if (!map.has(epicCode)) map.set(epicCode, { total: 0, done: 0 })
const entry = map.get(epicCode)!
entry.total++
const s = String(hu.status ?? '').toLowerCase()
if (['done', 'completed', 'closed', 'finalizado', '5', '6', '7', 'qa-client', 'ready to deploy'].includes(s)) {
entry.done++
}
}
const result: Record<string, number> = {}
for (const [code, { total, done }] of map) {
result[code] = total > 0 ? Math.round((done / total) * 100) : 0
}
return result
})
function getEpicProgress(epic: any): number {
const code = epic._epicCode
return code ? epicProgress.value[code] ?? 0 : 0
}
const statusVariant = (status: unknown) => { const statusVariant = (status: unknown) => {
const s = String(status ?? '').toLowerCase() const s = String(status ?? '').toLowerCase()
if (['done', 'completed', 'closed', 'finalizado'].includes(s)) return 'secondary' if (['done', 'completed', 'closed', 'finalizado'].includes(s)) return 'secondary'
@@ -453,6 +602,10 @@ const statusLabel = (status: unknown) => {
</h1> </h1>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Badge v-if="project.key" variant="outline" class="text-xs">{{ project.key }}</Badge> <Badge v-if="project.key" variant="outline" class="text-xs">{{ project.key }}</Badge>
<!-- Export button pending Python sidecar
<button class="ml-auto text-[11px] text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1" title="Exportar informe DOCX (próximamente)">
<FileText class="size-3.5" /> Exportar
</button> -->
</div> </div>
</div> </div>
@@ -519,6 +672,9 @@ const statusLabel = (status: unknown) => {
</Card> </Card>
</div> </div>
<!-- Priorizador Diario -->
<PrioritizerCard />
<!-- AI Chat --> <!-- AI Chat -->
<AiProjectChat <AiProjectChat
:project-id="project.id" :project-id="project.id"
@@ -529,45 +685,59 @@ const statusLabel = (status: unknown) => {
@navigate-settings="emit('navigate-settings')" @navigate-settings="emit('navigate-settings')"
/> />
<!-- Project Analysis --> <!-- Project Analysis Two-phase -->
<Card id="dashboard-analysis" class="border-dashed"> <Card id="dashboard-analysis" class="border-dashed">
<CardHeader class="pb-3 flex flex-row items-center justify-between"> <CardHeader class="pb-3 flex flex-row items-center justify-between">
<CardTitle class="text-sm font-medium flex items-center gap-2"> <CardTitle class="text-sm font-medium flex items-center gap-2">
<Sparkles class="size-4" /> <Sparkles class="size-4" />
Análisis completo del proyecto Análisis del proyecto
</CardTitle> </CardTitle>
<div class="flex gap-2"> <div v-if="!analyzing" class="flex gap-2">
<Button <Button
v-if="analyzing"
size="sm" size="sm"
variant="outline" variant="outline"
@click="cancelAnalysis()" :disabled="analyzing || phase === 'done'"
@click="runPhaseEpics()"
> >
Cancelar <Sparkles class="size-3 mr-1" />
1. Generar Épicas
</Button> </Button>
<Button <Button
size="sm" size="sm"
:disabled="analyzing" :disabled="analyzing || phase === 'idle'"
@click="runAnalysis()" @click="runPhaseHUs()"
> >
<Loader2 v-if="analyzing" class="size-4 mr-1 animate-spin" /> <Sparkles class="size-3 mr-1" />
<Sparkles v-else class="size-4 mr-1" /> 2. Generar HUs
{{ analyzing ? 'Analizando...' : 'Generar HUs faltantes' }}
</Button> </Button>
</div> </div>
<Button
v-if="analyzing"
size="sm"
variant="outline"
@click="cancelAnalysis()"
>
Cancelar
</Button>
</CardHeader> </CardHeader>
<CardContent v-if="analysisResult" class="space-y-2 text-sm"> <CardContent class="space-y-3">
<p class="text-muted-foreground">{{ analysisSummary }}</p> <div v-if="analyzing" class="flex items-center gap-2 text-sm text-muted-foreground">
<div class="flex items-center gap-3 text-xs"> <Loader2 class="size-4 animate-spin" />
<span v-if="analysisResult.saved > 0" class="text-green-600 dark:text-green-400 flex items-center gap-1"> {{ analysisMessage }}
<CheckCircle2 class="size-3" /> {{ analysisResult.saved }} borradores guardados </div>
</span> <div v-if="analysisResult" class="space-y-2 text-sm">
<span v-if="analysisResult.skipped > 0" class="text-amber-600 dark:text-amber-400 flex items-center gap-1"> <p class="text-muted-foreground">{{ analysisMessage }}</p>
{{ analysisResult.skipped }} duplicadas saltadas <div class="flex items-center gap-3 text-xs">
</span> <span v-if="analysisResult.saved > 0" class="text-green-600 dark:text-green-400 flex items-center gap-1">
<span v-if="analysisResult.saved === 0 && analysisResult.skipped === 0" class="text-muted-foreground"> <CheckCircle2 class="size-3" /> {{ analysisResult.saved }} borradores guardados
Todo ya está cubierto. No se requieren nuevas HUs. </span>
</span> <span v-if="analysisResult.skipped > 0" class="text-amber-600 dark:text-amber-400 flex items-center gap-1">
{{ analysisResult.skipped }} duplicadas saltadas
</span>
<span v-if="analysisResult.saved === 0 && analysisResult.skipped === 0" class="text-muted-foreground">
{{ phase === 'epics' ? 'No se requieren nuevas épicas.' : phase === 'hus' ? 'No se requieren nuevas HUs.' : '' }}
</span>
</div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -584,7 +754,9 @@ const statusLabel = (status: unknown) => {
<Badge variant="outline" class="text-[10px]" :class="d.type === 'E' ? 'border-purple-300 text-purple-600' : ''"> <Badge variant="outline" class="text-[10px]" :class="d.type === 'E' ? 'border-purple-300 text-purple-600' : ''">
{{ d.type === 'E' ? 'Épica' : 'HU' }} {{ d.type === 'E' ? 'Épica' : 'HU' }}
</Badge> </Badge>
<p class="font-medium">{{ d.title }}</p> <span v-if="d.type === 'E'" class="font-mono text-[10px] text-muted-foreground shrink-0">[{{ getEpicCode(d) }}]</span>
<span v-else class="font-mono text-[10px] text-muted-foreground shrink-0">{{ getItemCode(d) }}</span>
<p class="font-medium">{{ d.type === 'E' ? stripEpicTitle(d.title) : d.title }}</p>
</div> </div>
<div v-if="d.type === 'E' && d.metadata" class="text-xs text-muted-foreground mt-0.5"> <div v-if="d.type === 'E' && d.metadata" class="text-xs text-muted-foreground mt-0.5">
<span v-if="getEpicLinkedHUs(d).length > 0"> <span v-if="getEpicLinkedHUs(d).length > 0">
@@ -637,13 +809,12 @@ const statusLabel = (status: unknown) => {
<TableHead class="w-[60px]">{{ t('users.role') }}</TableHead> <TableHead class="w-[60px]">{{ t('users.role') }}</TableHead>
<TableHead>{{ t('dashboard.title') }}</TableHead> <TableHead>{{ t('dashboard.title') }}</TableHead>
<TableHead class="w-[60px] text-center">Desc</TableHead> <TableHead class="w-[60px] text-center">Desc</TableHead>
<TableHead class="w-[90px] text-center">{{ t('dashboard.assignedTo') }}</TableHead> <TableHead class="w-[140px]">Progreso</TableHead>
<TableHead class="w-[100px]">{{ t('dashboard.status') }}</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
<TableRow v-for="epic in workItems.epics" :key="epic.id"> <TableRow v-for="epic in workItems.epics" :key="epic.id">
<TableCell class="font-mono text-xs text-muted-foreground">{{ epic.code || `EP-${epic.id}` }}</TableCell> <TableCell class="font-mono text-xs text-muted-foreground">{{ epic._epicCode || epic.code || `EP-${epic.id}` }}</TableCell>
<TableCell> <TableCell>
<span class="inline-flex items-center rounded px-1 py-0.5 text-[10px] font-bold" :class="getTypeColor(epic._itemType)">{{ getTypeLabel(epic._itemType) }}</span> <span class="inline-flex items-center rounded px-1 py-0.5 text-[10px] font-bold" :class="getTypeColor(epic._itemType)">{{ getTypeLabel(epic._itemType) }}</span>
</TableCell> </TableCell>
@@ -658,9 +829,17 @@ const statusLabel = (status: unknown) => {
</Dialog> </Dialog>
<span v-else class="text-xs text-muted-foreground/40"></span> <span v-else class="text-xs text-muted-foreground/40"></span>
</TableCell> </TableCell>
<TableCell class="text-center text-xs text-muted-foreground"></TableCell> <TableCell class="text-xs">
<TableCell> <div class="flex items-center gap-2">
<Badge :variant="statusVariant(epic.status || '')" class="text-xs">{{ statusLabel(epic.status || '') }}</Badge> <div class="flex-1 h-2 rounded-full bg-muted overflow-hidden">
<div
class="h-full rounded-full transition-all duration-300"
:class="getEpicProgress(epic) >= 100 ? 'bg-green-500' : getEpicProgress(epic) > 0 ? 'bg-primary' : ''"
:style="{ width: getEpicProgress(epic) + '%' }"
/>
</div>
<span class="font-mono text-[10px] text-muted-foreground w-8 text-right">{{ getEpicProgress(epic) }}%</span>
</div>
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
@@ -734,9 +913,9 @@ const statusLabel = (status: unknown) => {
<SelectItem value="baja" class="text-xs">Baja</SelectItem> <SelectItem value="baja" class="text-xs">Baja</SelectItem>
<SelectItem value="critical" class="text-xs">Crítica</SelectItem> <SelectItem value="critical" class="text-xs">Crítica</SelectItem>
<SelectItem value="urgente" class="text-xs">Urgente</SelectItem> <SelectItem value="urgente" class="text-xs">Urgente</SelectItem>
<SelectItem value="1" class="text-xs">Alta (1)</SelectItem> <SelectItem value="3" class="text-xs">Alta (3)</SelectItem>
<SelectItem value="2" class="text-xs">Media (2)</SelectItem> <SelectItem value="2" class="text-xs">Media (2)</SelectItem>
<SelectItem value="3" class="text-xs">Baja (3)</SelectItem> <SelectItem value="1" class="text-xs">Baja (1)</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<Select v-model="filterAssigned"> <Select v-model="filterAssigned">
@@ -813,9 +992,10 @@ const statusLabel = (status: unknown) => {
<div <div
v-if="notification" v-if="notification"
class="fixed bottom-4 right-4 z-50 flex items-start gap-3 p-3 rounded-lg border bg-card border-l-[3px] text-xs shadow-lg max-w-sm" class="fixed bottom-4 right-4 z-50 flex items-start gap-3 p-3 rounded-lg border bg-card border-l-[3px] text-xs shadow-lg max-w-sm"
:class="notification.type === 'success' ? 'border-l-green-500' : 'border-l-red-500'" :class="notification.type === 'success' ? 'border-l-green-500' : notification.type === 'error' ? 'border-l-red-500' : 'border-l-blue-500'"
> >
<CheckCircle2 v-if="notification.type === 'success'" class="size-4 text-green-500 shrink-0 mt-0.5" /> <CheckCircle2 v-if="notification.type === 'success'" class="size-4 text-green-500 shrink-0 mt-0.5" />
<svg v-else-if="notification.type === 'info'" class="size-4 text-blue-500 shrink-0 mt-0.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
<XCircle v-else class="size-4 text-red-500 shrink-0 mt-0.5" /> <XCircle v-else class="size-4 text-red-500 shrink-0 mt-0.5" />
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<p class="font-medium text-foreground">{{ notification.type === 'success' ? 'Completado' : 'Error' }}</p> <p class="font-medium text-foreground">{{ notification.type === 'success' ? 'Completado' : 'Error' }}</p>
+134 -30
View File
@@ -3,7 +3,8 @@ import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useSettingsStore, PROVIDER_CONFIG, type AIProvider } from '@/stores/settings' import { useSettingsStore, PROVIDER_CONFIG, type AIProvider } from '@/stores/settings'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { getAllPromptKeys, getPrompt, savePrompt, resetPrompt, type PromptKey } from '@/services/prompts-db' import { getAllPromptKeys, getPrompt, savePrompt, resetPrompt, getDefaultPrompt, type PromptKey } from '@/services/prompts-db'
import PromptEditorModal from '@/components/PromptEditorModal.vue'
import { import {
Card, Card,
CardContent, CardContent,
@@ -16,6 +17,7 @@ import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Separator } from '@/components/ui/separator' import { Separator } from '@/components/ui/separator'
import { getWebhookUrl, setWebhookUrl, sendTestMessage } from '@/services/teams'
import { import {
Brain, Brain,
Key, Key,
@@ -26,6 +28,7 @@ import {
LogOut, LogOut,
Sparkles, Sparkles,
ChevronRight, ChevronRight,
MessageSquare,
} from 'lucide-vue-next' } from 'lucide-vue-next'
const { t } = useI18n() const { t } = useI18n()
@@ -66,8 +69,61 @@ function markDirty(key: string) {
promptDirty.value[key] = true promptDirty.value[key] = true
} }
// Prompt editor modal
const editingPrompt = ref<{ key: PromptKey; label: string } | null>(null)
const editingContent = ref('')
const promptModalOpen = ref(false)
function openPromptEditor(key: PromptKey, label: string) {
editingPrompt.value = { key, label }
editingContent.value = promptContents.value[key] || ''
promptModalOpen.value = true
}
function savePromptFromEditor(content: string) {
if (!editingPrompt.value) return
const key = editingPrompt.value.key
promptContents.value[key] = content
promptDirty.value[key] = true
savePromptHandler(key)
}
function resetPromptFromEditor() {
if (!editingPrompt.value) return
const key = editingPrompt.value.key
resetPromptHandler(key)
editingContent.value = promptContents.value[key] || ''
}
onMounted(loadPrompts) onMounted(loadPrompts)
// Teams
const teamsUrl = ref(getWebhookUrl())
const teamsSending = ref(false)
const teamsStatus = ref<'idle' | 'ok' | 'error'>('idle')
const teamsStatusMsg = ref('')
function teamsUrlChanged() {
setWebhookUrl(teamsUrl.value)
teamsStatus.value = 'idle'
}
async function testTeams() {
teamsSending.value = true
teamsStatus.value = 'idle'
teamsStatusMsg.value = ''
try {
const ok = await sendTestMessage()
teamsStatus.value = ok ? 'ok' : 'error'
teamsStatusMsg.value = ok ? 'Mensaje enviado a Teams' : 'Error al enviar. Verifica la URL del webhook.'
} catch (e: any) {
teamsStatus.value = 'error'
teamsStatusMsg.value = e.message
} finally {
teamsSending.value = false
}
}
const newKey = ref('') const newKey = ref('')
const keySaved = ref(false) const keySaved = ref(false)
@@ -283,6 +339,44 @@ const tierColors: Record<string, string> = {
</CardContent> </CardContent>
</Card> </Card>
<!-- Teams -->
<Card id="settings-teams">
<CardHeader>
<CardTitle class="text-sm font-medium flex items-center gap-2">
<MessageSquare class="size-4" />
Microsoft Teams
</CardTitle>
<CardDescription class="text-xs">
Configurá un webhook para recibir notificaciones de Alpha en Teams.
</CardDescription>
</CardHeader>
<CardContent class="space-y-3">
<div class="space-y-1.5">
<Label class="text-xs font-medium">URL del Webhook</Label>
<Input
v-model="teamsUrl"
placeholder="https://...office.com/webhookb2/..."
class="text-xs font-mono"
@input="teamsUrlChanged"
/>
<p class="text-[10px] text-muted-foreground">
Creá un webhook en Teams: Canal ... Conectores Webhook entrante
</p>
</div>
<div v-if="teamsUrl" class="flex items-center gap-2">
<Button size="sm" variant="outline" class="text-xs" :disabled="teamsSending" @click="testTeams">
{{ teamsSending ? 'Enviando...' : 'Enviar prueba' }}
</Button>
<span v-if="teamsStatus === 'ok'" class="text-xs text-green-600 flex items-center gap-1">
<CheckCircle2 class="size-3" /> {{ teamsStatusMsg }}
</span>
<span v-if="teamsStatus === 'error'" class="text-xs text-red-600 flex items-center gap-1">
<XCircle class="size-3" /> {{ teamsStatusMsg }}
</span>
</div>
</CardContent>
</Card>
<!-- Prompts --> <!-- Prompts -->
<Card id="settings-prompts" class="border-dashed"> <Card id="settings-prompts" class="border-dashed">
<CardHeader> <CardHeader>
@@ -294,40 +388,50 @@ const tierColors: Record<string, string> = {
Editá los prompts que usa la IA para cada función. Los cambios se aplican inmediatamente. Editá los prompts que usa la IA para cada función. Los cambios se aplican inmediatamente.
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent class="space-y-4"> <CardContent class="space-y-2">
<div v-for="{ key, label } in promptKeys" :key="key" class="space-y-1.5"> <div
<div class="flex items-center justify-between"> v-for="{ key, label } in promptKeys"
<Label class="text-xs font-medium">{{ label }}</Label> :key="key"
<div class="flex gap-1"> class="flex items-center gap-3 p-3 rounded-lg border hover:border-primary/50 transition-colors cursor-pointer"
<Button @click="openPromptEditor(key as PromptKey, label)"
v-if="promptDirty[key]" >
size="sm" <div class="size-8 rounded-lg bg-muted flex items-center justify-center shrink-0">
variant="default" <span class="text-xs font-bold text-muted-foreground">{{ label.slice(0, 2).toUpperCase() }}</span>
class="text-xs h-6 px-2" </div>
:disabled="promptSaving[key]" <div class="flex-1 min-w-0">
@click="savePromptHandler(key as PromptKey)" <p class="text-sm font-medium">{{ label }}</p>
> <p class="text-xs text-muted-foreground truncate mt-0.5">{{ (promptContents[key] || '').slice(0, 80) }}...</p>
{{ promptSaving[key] ? 'Guardando...' : 'Guardar' }} </div>
</Button> <div class="flex gap-1 shrink-0">
<Button <Button
size="sm" v-if="promptDirty[key]"
variant="ghost" size="sm"
class="text-xs h-6 px-2 text-muted-foreground" variant="default"
@click="resetPromptHandler(key as PromptKey)" class="text-xs h-7 px-2"
> :disabled="promptSaving[key]"
Restaurar @click.stop="savePromptHandler(key as PromptKey)"
</Button> >
</div> {{ promptSaving[key] ? '...' : 'Guardar' }}
</Button>
<Button size="sm" variant="ghost" class="text-xs h-7 px-2 text-muted-foreground" @click.stop="openPromptEditor(key as PromptKey, label)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-3.5"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
</Button>
</div> </div>
<textarea
:value="promptContents[key]"
@input="(e: any) => { promptContents[key] = e.target.value; markDirty(key) }"
class="w-full h-32 rounded-md border border-input bg-background p-2 text-xs font-mono outline-none focus:border-ring focus:ring-1 focus:ring-ring resize-y"
/>
</div> </div>
<p class="text-[10px] text-muted-foreground text-center pt-1">Haz clic en un prompt para editarlo con el editor enriquecido</p>
</CardContent> </CardContent>
</Card> </Card>
<!-- Prompt Editor Modal -->
<PromptEditorModal
:open="promptModalOpen"
:title="editingPrompt?.label || ''"
:content="editingContent"
@update:open="promptModalOpen = $event"
@save="savePromptFromEditor"
@reset="resetPromptFromEditor"
/>
<!-- Account --> <!-- Account -->
<Card id="settings-account"> <Card id="settings-account">
<CardHeader> <CardHeader>