@jiggai/kitchen 0.3.14 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +2 -2
  3. package/.next/server/app/_global-error.html +2 -2
  4. package/.next/server/app/_global-error.rsc +1 -1
  5. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  6. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/server/app/_not-found.html +1 -1
  11. package/.next/server/app/_not-found.rsc +2 -2
  12. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  13. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  14. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  18. package/.next/server/app/channels.html +2 -2
  19. package/.next/server/app/channels.rsc +2 -2
  20. package/.next/server/app/channels.segments/_full.segment.rsc +2 -2
  21. package/.next/server/app/channels.segments/_head.segment.rsc +1 -1
  22. package/.next/server/app/channels.segments/_index.segment.rsc +1 -1
  23. package/.next/server/app/channels.segments/_tree.segment.rsc +1 -1
  24. package/.next/server/app/channels.segments/channels/__PAGE__.segment.rsc +1 -1
  25. package/.next/server/app/channels.segments/channels.segment.rsc +1 -1
  26. package/.next/server/app/goals/new.html +2 -2
  27. package/.next/server/app/goals/new.rsc +2 -2
  28. package/.next/server/app/goals/new.segments/_full.segment.rsc +2 -2
  29. package/.next/server/app/goals/new.segments/_head.segment.rsc +1 -1
  30. package/.next/server/app/goals/new.segments/_index.segment.rsc +1 -1
  31. package/.next/server/app/goals/new.segments/_tree.segment.rsc +1 -1
  32. package/.next/server/app/goals/new.segments/goals/new/__PAGE__.segment.rsc +1 -1
  33. package/.next/server/app/goals/new.segments/goals/new.segment.rsc +1 -1
  34. package/.next/server/app/goals/new.segments/goals.segment.rsc +1 -1
  35. package/.next/server/app/goals.html +1 -1
  36. package/.next/server/app/goals.rsc +2 -2
  37. package/.next/server/app/goals.segments/_full.segment.rsc +2 -2
  38. package/.next/server/app/goals.segments/_head.segment.rsc +1 -1
  39. package/.next/server/app/goals.segments/_index.segment.rsc +1 -1
  40. package/.next/server/app/goals.segments/_tree.segment.rsc +1 -1
  41. package/.next/server/app/goals.segments/goals/__PAGE__.segment.rsc +1 -1
  42. package/.next/server/app/goals.segments/goals.segment.rsc +1 -1
  43. package/.next/server/app/settings.html +1 -1
  44. package/.next/server/app/settings.rsc +2 -2
  45. package/.next/server/app/settings.segments/_full.segment.rsc +2 -2
  46. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  47. package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  48. package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  49. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
  50. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  51. package/.next/server/app/teams/[teamId]/page_client-reference-manifest.js +1 -1
  52. package/.next/server/app/teams/[teamId]/tickets/[ticket]/page_client-reference-manifest.js +1 -1
  53. package/.next/server/app/teams/[teamId]/tickets/page_client-reference-manifest.js +1 -1
  54. package/.next/server/app/tickets/[ticket]/page_client-reference-manifest.js +1 -1
  55. package/.next/server/app/tickets/page_client-reference-manifest.js +1 -1
  56. package/.next/server/chunks/[root-of-the-server]__05f5e243._.js +1 -1
  57. package/.next/server/chunks/[root-of-the-server]__05f5e243._.js.map +1 -1
  58. package/.next/server/chunks/[root-of-the-server]__47eb3111._.js +1 -1
  59. package/.next/server/chunks/[root-of-the-server]__47eb3111._.js.map +1 -1
  60. package/.next/server/chunks/[root-of-the-server]__542a081b._.js +1 -1
  61. package/.next/server/chunks/[root-of-the-server]__542a081b._.js.map +1 -1
  62. package/.next/server/chunks/[root-of-the-server]__5ceff0f3._.js +3 -3
  63. package/.next/server/chunks/[root-of-the-server]__5ceff0f3._.js.map +1 -1
  64. package/.next/server/chunks/[root-of-the-server]__5ea38a67._.js +1 -1
  65. package/.next/server/chunks/[root-of-the-server]__5ea38a67._.js.map +1 -1
  66. package/.next/server/chunks/[root-of-the-server]__b002764e._.js +1 -1
  67. package/.next/server/chunks/[root-of-the-server]__b002764e._.js.map +1 -1
  68. package/.next/server/chunks/[root-of-the-server]__d924e5ac._.js +1 -1
  69. package/.next/server/chunks/[root-of-the-server]__d924e5ac._.js.map +1 -1
  70. package/.next/server/chunks/[root-of-the-server]__e063a986._.js +1 -1
  71. package/.next/server/chunks/[root-of-the-server]__e063a986._.js.map +1 -1
  72. package/.next/server/chunks/[root-of-the-server]__ff69832c._.js +2 -2
  73. package/.next/server/chunks/[root-of-the-server]__ff69832c._.js.map +1 -1
  74. package/.next/server/chunks/ssr/[root-of-the-server]__17b046ea._.js +1 -1
  75. package/.next/server/chunks/ssr/[root-of-the-server]__17b046ea._.js.map +1 -1
  76. package/.next/server/chunks/ssr/[root-of-the-server]__3b880807._.js +1 -1
  77. package/.next/server/chunks/ssr/[root-of-the-server]__3b880807._.js.map +1 -1
  78. package/.next/server/chunks/ssr/[root-of-the-server]__49281b4c._.js +1 -1
  79. package/.next/server/chunks/ssr/[root-of-the-server]__49281b4c._.js.map +1 -1
  80. package/.next/server/chunks/ssr/[root-of-the-server]__9a7d79aa._.js +1 -1
  81. package/.next/server/chunks/ssr/[root-of-the-server]__9a7d79aa._.js.map +1 -1
  82. package/.next/server/chunks/ssr/[root-of-the-server]__c777e326._.js +1 -1
  83. package/.next/server/chunks/ssr/[root-of-the-server]__c777e326._.js.map +1 -1
  84. package/.next/server/chunks/ssr/src_59477309._.js +1 -1
  85. package/.next/server/chunks/ssr/src_59477309._.js.map +1 -1
  86. package/.next/server/chunks/ssr/src_app_teams_[teamId]_team-editor_index_tsx_e68e9cbc._.js +1 -1
  87. package/.next/server/chunks/ssr/src_app_teams_[teamId]_team-editor_index_tsx_e68e9cbc._.js.map +1 -1
  88. package/.next/server/chunks/ssr/src_app_tickets_TicketsBoardClient_tsx_5e156ef3._.js +1 -1
  89. package/.next/server/chunks/ssr/src_app_tickets_TicketsBoardClient_tsx_5e156ef3._.js.map +1 -1
  90. package/.next/server/pages/404.html +1 -1
  91. package/.next/server/pages/500.html +2 -2
  92. package/.next/static/chunks/4a6dcf80b293d7a4.js +1 -0
  93. package/.next/static/chunks/{5f77499ca309ab9d.js → a719c5f36ffeec2a.js} +1 -1
  94. package/.next/static/chunks/{febbbb81927c8042.js → e083bcf4a197beae.js} +1 -1
  95. package/openclaw.plugin.json +1 -1
  96. package/package.json +1 -1
  97. package/.next/static/chunks/be12a66bd736fad8.js +0 -1
  98. /package/.next/static/{cp-U4VbwzSh2raGh9eqcl → Wr2xJ4v7YSX2YxupoHWtl}/_buildManifest.js +0 -0
  99. /package/.next/static/{cp-U4VbwzSh2raGh9eqcl → Wr2xJ4v7YSX2YxupoHWtl}/_clientMiddlewareManifest.json +0 -0
  100. /package/.next/static/{cp-U4VbwzSh2raGh9eqcl → Wr2xJ4v7YSX2YxupoHWtl}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/lib/tickets.ts","../../../../src/app/tickets/TicketDetailClient.tsx/__nextjs-internal-proxy.mjs","../../../../src/app/tickets/%5Bticket%5D/page.tsx"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type TicketStage = \"backlog\" | \"in-progress\" | \"testing\" | \"done\";\n\nexport interface TicketSummary {\n teamId: string;\n number: number;\n id: string;\n title: string;\n owner: string | null;\n stage: TicketStage;\n file: string;\n updatedAt: string; // ISO\n ageHours: number;\n}\n\nfunction assertSafeTeamId(teamId: string) {\n // Conservative: matches OpenClaw team ids like \"development-team\".\n if (!/^[a-z0-9][a-z0-9-]{1,63}$/.test(teamId)) {\n throw new Error(`Invalid teamId \"${teamId}\"`);\n }\n}\n\nfunction isPathLike(s: string) {\n return s.includes(\"/\") || s.includes(\"\\\\\");\n}\n\nexport function teamWorkspace(teamId: string) {\n assertSafeTeamId(teamId);\n return path.join(os.homedir(), \".openclaw\", `workspace-${teamId}`);\n}\n\n/**\n * Back-compat helper for older non-team-scoped code paths.\n * Prefer passing explicit teamId into APIs instead.\n */\nexport function getTeamWorkspaceDir(teamId?: string): string {\n if (teamId) return teamWorkspace(teamId);\n return process.env.CK_TEAM_WORKSPACE_DIR ?? teamWorkspace(process.env.CK_TEAM_ID ?? \"development-team\");\n}\n\nexport function stageDir(stage: TicketStage, teamIdOrDir: string = \"development-team\") {\n const map: Record<TicketStage, string> = {\n backlog: \"work/backlog\",\n \"in-progress\": \"work/in-progress\",\n testing: \"work/testing\",\n done: \"work/done\",\n };\n\n const base = isPathLike(teamIdOrDir) ? teamIdOrDir : teamWorkspace(teamIdOrDir);\n return path.join(base, map[stage]);\n}\n\nexport function parseTitle(md: string) {\n // Ticket markdown files typically start with: # 0033-some-slug\n const firstLine = md.split(\"\\n\")[0] ?? \"\";\n const header = firstLine.startsWith(\"# \") ? firstLine.slice(2).trim() : \"\";\n\n // If header is like: \"<id> <title...>\" keep the explicit title portion.\n const firstSpace = header.indexOf(\" \");\n if (firstSpace > 0) {\n const afterSpace = header.slice(firstSpace + 1).trim();\n if (afterSpace) return afterSpace;\n }\n\n // Otherwise derive from the slug: strip leading number + hyphen, then de-kebab.\n const derivedRaw = header\n .replace(/^\\d{4}-/, \"\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n\n const ACRONYMS = new Set([\"api\", \"cli\", \"ui\", \"ux\", \"gpu\", \"cpu\", \"npm\", \"pr\", \"ci\", \"cd\", \"json\", \"yaml\", \"md\"]);\n const titleCase = (s: string) =>\n s\n .split(\" \")\n .filter(Boolean)\n .map((w) => {\n const lower = w.toLowerCase();\n if (ACRONYMS.has(lower)) return w.toUpperCase();\n if (lower.startsWith(\"v\") && /^\\d/.test(lower.slice(1))) return w; // version-like\n if (/^[\\d.]+$/.test(w)) return w; // numbers/semver\n return w.slice(0, 1).toUpperCase() + w.slice(1);\n })\n .join(\" \");\n\n const derived = derivedRaw ? titleCase(derivedRaw) : \"\";\n\n if (derived) return derived;\n return header || \"(untitled)\";\n}\n\nfunction parseField(md: string, field: string): string | null {\n const re = new RegExp(`^${field}:\\\\s*(.*)$`, \"mi\");\n const m = md.match(re);\n return m?.[1]?.trim() || null;\n}\n\nexport function parseNumberFromFilename(filename: string): number | null {\n const m = filename.match(/^(\\d{4})-/);\n if (!m) return null;\n return Number(m[1]);\n}\n\nfunction teamIdFromTeamDir(teamDir: string): string {\n const base = path.basename(teamDir);\n if (base === \"workspace\") return \"main\";\n if (base.startsWith(\"workspace-\")) return base.slice(\"workspace-\".length);\n return base;\n}\n\nexport async function discoverTeamIds(): Promise<string[]> {\n // Convention: ~/.openclaw/workspace-<teamId>\n const root = path.join(os.homedir(), \".openclaw\");\n let entries: string[] = [];\n try {\n entries = await fs.readdir(root);\n } catch {\n return [];\n }\n\n const teamIds = entries\n .filter((e) => e.startsWith(\"workspace-\"))\n .map((e) => e.slice(\"workspace-\".length))\n .filter((id) => Boolean(id) && id !== \"workspace\");\n\n // Also include personal workspace scope.\n teamIds.push(\"main\");\n\n return Array.from(new Set(teamIds)).sort();\n}\n\n/**\n * List tickets for a specific team (recommended).\n * - listTickets(\"development-team\")\n * - listTickets(teamWorkspaceDir)\n */\nexport async function listTickets(teamIdOrDir: string = \"development-team\"): Promise<TicketSummary[]> {\n const teamId = isPathLike(teamIdOrDir) ? teamIdFromTeamDir(teamIdOrDir) : teamIdOrDir;\n const stages: TicketStage[] = [\"backlog\", \"in-progress\", \"testing\", \"done\"];\n const all: TicketSummary[] = [];\n\n for (const stage of stages) {\n let files: string[] = [];\n try {\n files = await fs.readdir(stageDir(stage, teamIdOrDir));\n } catch {\n files = [];\n }\n\n for (const f of files) {\n if (!f.endsWith(\".md\")) continue;\n const number = parseNumberFromFilename(f);\n if (number == null) continue;\n\n const file = path.join(stageDir(stage, teamIdOrDir), f);\n const [md, stat] = await Promise.all([fs.readFile(file, \"utf8\"), fs.stat(file)]);\n\n const title = parseTitle(md);\n const owner = parseField(md, \"Owner\");\n const updatedAt = stat.mtime.toISOString();\n const ageHours = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);\n\n all.push({\n teamId,\n number,\n id: f.replace(/\\.md$/, \"\"),\n title,\n owner,\n stage,\n file,\n updatedAt,\n ageHours,\n });\n }\n }\n\n all.sort((a, b) => a.number - b.number);\n return all;\n}\n\n/**\n * List tickets across all discovered teams (used for /tickets?team=all).\n */\nexport async function listAllTeamsTickets(): Promise<TicketSummary[]> {\n const teamIds = await discoverTeamIds();\n const all: TicketSummary[] = [];\n\n for (const teamId of teamIds) {\n all.push(...(await listTickets(teamId)));\n }\n\n all.sort((a, b) => (a.teamId === b.teamId ? a.number - b.number : a.teamId.localeCompare(b.teamId)));\n return all;\n}\n\n/**\n * Back-compat helper used by some API routes.\n */\nexport async function getTicketByIdOrNumber(ticketIdOrNumber: string, teamIdOrDir: string = \"development-team\") {\n const tickets = await listTickets(teamIdOrDir);\n const normalized = ticketIdOrNumber.trim();\n\n const byNumber = normalized.match(/^\\d+$/) ? tickets.find((t) => t.number === Number(normalized)) : null;\n const byId = tickets.find((t) => t.id === normalized);\n return byId ?? byNumber ?? null;\n}\n\nexport async function resolveTicket(teamId: string, ticketIdOrNumber: string): Promise<TicketSummary | null> {\n return getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n}\n\n/**\n * getTicketMarkdown(teamId, ticketIdOrNumber)\n */\nexport async function getTicketMarkdown(\n teamId: string,\n ticketIdOrNumber: string,\n): Promise<{ teamId: string; id: string; file: string; markdown: string; owner: string | null; stage: TicketStage } | null> {\n const hit = await getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n if (!hit) return null;\n\n return {\n teamId: hit.teamId,\n id: hit.id,\n file: hit.file,\n markdown: await fs.readFile(hit.file, \"utf8\"),\n owner: hit.owner,\n stage: hit.stage,\n };\n}\n","// This file is generated by next-core EcmascriptClientReferenceModule.\nimport { registerClientReference } from \"react-server-dom-turbopack/server\";\nexport const TicketDetailClient = registerClientReference(\n function() { throw new Error(\"Attempted to call TicketDetailClient() from the server but TicketDetailClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\"); },\n \"[project]/src/app/tickets/TicketDetailClient.tsx\",\n \"TicketDetailClient\",\n);\nexport const isPostCommentDisabled = registerClientReference(\n function() { throw new Error(\"Attempted to call isPostCommentDisabled() from the server but isPostCommentDisabled is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\"); },\n \"[project]/src/app/tickets/TicketDetailClient.tsx\",\n \"isPostCommentDisabled\",\n);\n","import { getTicketMarkdown } from \"@/lib/tickets\";\nimport { TicketDetailClient } from \"@/app/tickets/TicketDetailClient\";\n\n// Ticket detail should always reflect current stage/file; do not cache.\nexport const dynamic = \"force-dynamic\";\n\nexport default async function TicketDetailPage({\n params,\n searchParams,\n}: {\n params: Promise<{ ticket: string }>;\n searchParams: Promise<Record<string, string | string[] | undefined>>;\n}) {\n const { ticket } = await params;\n const sp = await searchParams;\n const team = typeof sp.team === \"string\" ? sp.team.trim() : \"\";\n const teamId = team || \"development-team\";\n\n const data = await getTicketMarkdown(teamId, ticket);\n\n if (!data) {\n return (\n <div className=\"ck-glass p-6\">\n <h1 className=\"text-xl font-semibold tracking-tight\">Ticket not found</h1>\n <p className=\"mt-3 text-sm text-[color:var(--ck-text-secondary)]\">\n Couldn’t locate “{ticket}” in backlog/in-progress/testing/done.\n </p>\n </div>\n );\n }\n\n return (\n <TicketDetailClient\n teamId={teamId}\n ticketId={data.id}\n file={data.file}\n markdown={data.markdown}\n stage={data.stage}\n currentOwner={data.owner}\n />\n );\n}\n"],"names":[],"mappings":"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","ignoreList":[1]}
1
+ {"version":3,"sources":["../../../../src/lib/tickets.ts","../../../../src/app/tickets/TicketDetailClient.tsx/__nextjs-internal-proxy.mjs","../../../../src/app/tickets/%5Bticket%5D/page.tsx"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type TicketStage = \"backlog\" | \"in-progress\" | \"testing\" | \"done\";\n\nexport interface TicketSummary {\n teamId: string;\n number: number;\n id: string;\n title: string;\n owner: string | null;\n stage: TicketStage;\n file: string;\n updatedAt: string; // ISO\n ageHours: number;\n}\n\nfunction assertSafeTeamId(teamId: string) {\n // Conservative: matches OpenClaw team ids like \"development-team\".\n if (!/^[a-z0-9][a-z0-9-]{1,63}$/.test(teamId)) {\n throw new Error(`Invalid teamId \"${teamId}\"`);\n }\n}\n\nfunction isPathLike(s: string) {\n return s.includes(\"/\") || s.includes(\"\\\\\");\n}\n\nexport function teamWorkspace(teamId: string) {\n assertSafeTeamId(teamId);\n return path.join(os.homedir(), \".openclaw\", `workspace-${teamId}`);\n}\n\n/**\n * Back-compat helper for older non-team-scoped code paths.\n * Prefer passing explicit teamId into APIs instead.\n */\nexport function getTeamWorkspaceDir(teamId?: string): string {\n if (teamId) return teamWorkspace(teamId);\n const envTeam = process.env.CK_TEAM_ID;\n if (!envTeam && !process.env.CK_TEAM_WORKSPACE_DIR) {\n throw new Error(\"No team specified. Pass a teamId or set CK_TEAM_ID / CK_TEAM_WORKSPACE_DIR.\");\n }\n return process.env.CK_TEAM_WORKSPACE_DIR ?? teamWorkspace(envTeam!);\n}\n\nexport function stageDir(stage: TicketStage, teamIdOrDir: string) {\n const map: Record<TicketStage, string> = {\n backlog: \"work/backlog\",\n \"in-progress\": \"work/in-progress\",\n testing: \"work/testing\",\n done: \"work/done\",\n };\n\n const base = isPathLike(teamIdOrDir) ? teamIdOrDir : teamWorkspace(teamIdOrDir);\n return path.join(base, map[stage]);\n}\n\nexport function parseTitle(md: string) {\n // Ticket markdown files typically start with: # 0033-some-slug\n const firstLine = md.split(\"\\n\")[0] ?? \"\";\n const header = firstLine.startsWith(\"# \") ? firstLine.slice(2).trim() : \"\";\n\n // If header is like: \"<id> <title...>\" keep the explicit title portion.\n const firstSpace = header.indexOf(\" \");\n if (firstSpace > 0) {\n const afterSpace = header.slice(firstSpace + 1).trim();\n if (afterSpace) return afterSpace;\n }\n\n // Otherwise derive from the slug: strip leading number + hyphen, then de-kebab.\n const derivedRaw = header\n .replace(/^\\d{4}-/, \"\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n\n const ACRONYMS = new Set([\"api\", \"cli\", \"ui\", \"ux\", \"gpu\", \"cpu\", \"npm\", \"pr\", \"ci\", \"cd\", \"json\", \"yaml\", \"md\"]);\n const titleCase = (s: string) =>\n s\n .split(\" \")\n .filter(Boolean)\n .map((w) => {\n const lower = w.toLowerCase();\n if (ACRONYMS.has(lower)) return w.toUpperCase();\n if (lower.startsWith(\"v\") && /^\\d/.test(lower.slice(1))) return w; // version-like\n if (/^[\\d.]+$/.test(w)) return w; // numbers/semver\n return w.slice(0, 1).toUpperCase() + w.slice(1);\n })\n .join(\" \");\n\n const derived = derivedRaw ? titleCase(derivedRaw) : \"\";\n\n if (derived) return derived;\n return header || \"(untitled)\";\n}\n\nfunction parseField(md: string, field: string): string | null {\n const re = new RegExp(`^${field}:\\\\s*(.*)$`, \"mi\");\n const m = md.match(re);\n return m?.[1]?.trim() || null;\n}\n\nexport function parseNumberFromFilename(filename: string): number | null {\n const m = filename.match(/^(\\d{4})-/);\n if (!m) return null;\n return Number(m[1]);\n}\n\nfunction teamIdFromTeamDir(teamDir: string): string {\n const base = path.basename(teamDir);\n if (base === \"workspace\") return \"main\";\n if (base.startsWith(\"workspace-\")) return base.slice(\"workspace-\".length);\n return base;\n}\n\nexport async function discoverTeamIds(): Promise<string[]> {\n // Convention: ~/.openclaw/workspace-<teamId>\n const root = path.join(os.homedir(), \".openclaw\");\n let entries: string[] = [];\n try {\n entries = await fs.readdir(root);\n } catch {\n return [];\n }\n\n const teamIds = entries\n .filter((e) => e.startsWith(\"workspace-\"))\n .map((e) => e.slice(\"workspace-\".length))\n .filter((id) => Boolean(id) && id !== \"workspace\");\n\n // Also include personal workspace scope.\n teamIds.push(\"main\");\n\n return Array.from(new Set(teamIds)).sort();\n}\n\n/**\n * List tickets for a specific team (recommended).\n * - listTickets(\"my-team\")\n * - listTickets(teamWorkspaceDir)\n */\nexport async function listTickets(teamIdOrDir: string): Promise<TicketSummary[]> {\n const teamId = isPathLike(teamIdOrDir) ? teamIdFromTeamDir(teamIdOrDir) : teamIdOrDir;\n const stages: TicketStage[] = [\"backlog\", \"in-progress\", \"testing\", \"done\"];\n const all: TicketSummary[] = [];\n\n for (const stage of stages) {\n let files: string[] = [];\n try {\n files = await fs.readdir(stageDir(stage, teamIdOrDir));\n } catch {\n files = [];\n }\n\n for (const f of files) {\n if (!f.endsWith(\".md\")) continue;\n const number = parseNumberFromFilename(f);\n if (number == null) continue;\n\n const file = path.join(stageDir(stage, teamIdOrDir), f);\n const [md, stat] = await Promise.all([fs.readFile(file, \"utf8\"), fs.stat(file)]);\n\n const title = parseTitle(md);\n const owner = parseField(md, \"Owner\");\n const updatedAt = stat.mtime.toISOString();\n const ageHours = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);\n\n all.push({\n teamId,\n number,\n id: f.replace(/\\.md$/, \"\"),\n title,\n owner,\n stage,\n file,\n updatedAt,\n ageHours,\n });\n }\n }\n\n all.sort((a, b) => a.number - b.number);\n return all;\n}\n\n/**\n * List tickets across all discovered teams (used for /tickets?team=all).\n */\nexport async function listAllTeamsTickets(): Promise<TicketSummary[]> {\n const teamIds = await discoverTeamIds();\n const all: TicketSummary[] = [];\n\n for (const teamId of teamIds) {\n all.push(...(await listTickets(teamId)));\n }\n\n all.sort((a, b) => (a.teamId === b.teamId ? a.number - b.number : a.teamId.localeCompare(b.teamId)));\n return all;\n}\n\n/**\n * Back-compat helper used by some API routes.\n */\nexport async function getTicketByIdOrNumber(ticketIdOrNumber: string, teamIdOrDir: string) {\n const tickets = await listTickets(teamIdOrDir);\n const normalized = ticketIdOrNumber.trim();\n\n const byNumber = normalized.match(/^\\d+$/) ? tickets.find((t) => t.number === Number(normalized)) : null;\n const byId = tickets.find((t) => t.id === normalized);\n return byId ?? byNumber ?? null;\n}\n\nexport async function resolveTicket(teamId: string, ticketIdOrNumber: string): Promise<TicketSummary | null> {\n return getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n}\n\n/**\n * getTicketMarkdown(teamId, ticketIdOrNumber)\n */\nexport async function getTicketMarkdown(\n teamId: string,\n ticketIdOrNumber: string,\n): Promise<{ teamId: string; id: string; file: string; markdown: string; owner: string | null; stage: TicketStage } | null> {\n const hit = await getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n if (!hit) return null;\n\n return {\n teamId: hit.teamId,\n id: hit.id,\n file: hit.file,\n markdown: await fs.readFile(hit.file, \"utf8\"),\n owner: hit.owner,\n stage: hit.stage,\n };\n}\n","// This file is generated by next-core EcmascriptClientReferenceModule.\nimport { registerClientReference } from \"react-server-dom-turbopack/server\";\nexport const TicketDetailClient = registerClientReference(\n function() { throw new Error(\"Attempted to call TicketDetailClient() from the server but TicketDetailClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\"); },\n \"[project]/src/app/tickets/TicketDetailClient.tsx\",\n \"TicketDetailClient\",\n);\nexport const isPostCommentDisabled = registerClientReference(\n function() { throw new Error(\"Attempted to call isPostCommentDisabled() from the server but isPostCommentDisabled is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\"); },\n \"[project]/src/app/tickets/TicketDetailClient.tsx\",\n \"isPostCommentDisabled\",\n);\n","import { getTicketMarkdown } from \"@/lib/tickets\";\nimport { TicketDetailClient } from \"@/app/tickets/TicketDetailClient\";\n\n// Ticket detail should always reflect current stage/file; do not cache.\nexport const dynamic = \"force-dynamic\";\n\nexport default async function TicketDetailPage({\n params,\n searchParams,\n}: {\n params: Promise<{ ticket: string }>;\n searchParams: Promise<Record<string, string | string[] | undefined>>;\n}) {\n const { ticket } = await params;\n const sp = await searchParams;\n const team = typeof sp.team === \"string\" ? sp.team.trim() : \"\";\n if (!team) {\n return (\n <div className=\"ck-glass p-6\">\n <h1 className=\"text-xl font-semibold tracking-tight\">No team selected</h1>\n <p className=\"mt-3 text-sm text-[color:var(--ck-text-secondary)]\">\n Select a team from the sidebar to view tickets.\n </p>\n </div>\n );\n }\n const teamId = team;\n\n const data = await getTicketMarkdown(teamId, ticket);\n\n if (!data) {\n return (\n <div className=\"ck-glass p-6\">\n <h1 className=\"text-xl font-semibold tracking-tight\">Ticket not found</h1>\n <p className=\"mt-3 text-sm text-[color:var(--ck-text-secondary)]\">\n Couldn’t locate “{ticket}” in backlog/in-progress/testing/done.\n </p>\n </div>\n );\n }\n\n return (\n <TicketDetailClient\n teamId={teamId}\n ticketId={data.id}\n file={data.file}\n markdown={data.markdown}\n stage={data.stage}\n currentOwner={data.owner}\n />\n );\n}\n"],"names":[],"mappings":"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","ignoreList":[1]}
@@ -1,3 +1,3 @@
1
- module.exports=[93695,(a,b,c)=>{b.exports=a.x("next/dist/shared/lib/no-fallback-error.external.js",()=>require("next/dist/shared/lib/no-fallback-error.external.js"))},12714,(a,b,c)=>{b.exports=a.x("node:fs/promises",()=>require("node:fs/promises"))},50227,(a,b,c)=>{b.exports=a.x("node:path",()=>require("node:path"))},60526,(a,b,c)=>{b.exports=a.x("node:os",()=>require("node:os"))},69264,a=>{a.n(a.i(54832))},50645,a=>{a.n(a.i(27572))},17537,a=>{a.n(a.i(3363))},13718,a=>{a.n(a.i(85523))},18198,a=>{a.n(a.i(45518))},37941,a=>{"use strict";var b=a.i(12714),c=a.i(60526),d=a.i(50227);function e(a){return a.includes("/")||a.includes("\\")}function f(a,b="development-team"){let g=e(b)?b:function(a){if(!/^[a-z0-9][a-z0-9-]{1,63}$/.test(a))throw Error(`Invalid teamId "${a}"`);return d.default.join(c.default.homedir(),".openclaw",`workspace-${a}`)}(b);return d.default.join(g,{backlog:"work/backlog","in-progress":"work/in-progress",testing:"work/testing",done:"work/done"}[a])}async function g(a="development-team"){let c,h=e(a)?"workspace"===(c=d.default.basename(a))?"main":c.startsWith("workspace-")?c.slice(10):c:a,i=[];for(let c of["backlog","in-progress","testing","done"]){let e=[];try{e=await b.default.readdir(f(c,a))}catch{e=[]}for(let g of e){if(!g.endsWith(".md"))continue;let e=function(a){let b=a.match(/^(\d{4})-/);return b?Number(b[1]):null}(g);if(null==e)continue;let j=d.default.join(f(c,a),g),[k,l]=await Promise.all([b.default.readFile(j,"utf8"),b.default.stat(j)]),m=function(a){let b=a.split("\n")[0]??"",c=b.startsWith("# ")?b.slice(2).trim():"",d=c.indexOf(" ");if(d>0){let a=c.slice(d+1).trim();if(a)return a}let e=c.replace(/^\d{4}-/,"").replace(/[-_]+/g," ").replace(/\s+/g," ").trim(),f=new Set(["api","cli","ui","ux","gpu","cpu","npm","pr","ci","cd","json","yaml","md"]),g=e?e.split(" ").filter(Boolean).map(a=>{let b=a.toLowerCase();return f.has(b)?a.toUpperCase():b.startsWith("v")&&/^\d/.test(b.slice(1))||/^[\d.]+$/.test(a)?a:a.slice(0,1).toUpperCase()+a.slice(1)}).join(" "):"";return g||c||"(untitled)"}(k),n=function(a,b){let c=RegExp(`^${b}:\\s*(.*)$`,"mi"),d=a.match(c);return d?.[1]?.trim()||null}(k,"Owner"),o=l.mtime.toISOString(),p=(Date.now()-l.mtimeMs)/36e5;i.push({teamId:h,number:e,id:g.replace(/\.md$/,""),title:m,owner:n,stage:c,file:j,updatedAt:o,ageHours:p})}}return i.sort((a,b)=>a.number-b.number),i}async function h(a,b="development-team"){let c=await g(b),d=a.trim(),e=d.match(/^\d+$/)?c.find(a=>a.number===Number(d)):null;return c.find(a=>a.id===d)??e??null}async function i(a,c){let d=await h(c,a);return d?{teamId:d.teamId,id:d.id,file:d.file,markdown:await b.default.readFile(d.file,"utf8"),owner:d.owner,stage:d.stage}:null}a.s(["getTicketMarkdown",()=>i,"listTickets",()=>g])},65892,a=>{"use strict";a.s(["TicketDetailClient",()=>c,"isPostCommentDisabled",()=>d]);var b=a.i(11857);let c=(0,b.registerClientReference)(function(){throw Error("Attempted to call TicketDetailClient() from the server but TicketDetailClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketDetailClient.tsx <module evaluation>","TicketDetailClient"),d=(0,b.registerClientReference)(function(){throw Error("Attempted to call isPostCommentDisabled() from the server but isPostCommentDisabled is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketDetailClient.tsx <module evaluation>","isPostCommentDisabled")},95433,a=>{"use strict";a.s(["TicketDetailClient",()=>c,"isPostCommentDisabled",()=>d]);var b=a.i(11857);let c=(0,b.registerClientReference)(function(){throw Error("Attempted to call TicketDetailClient() from the server but TicketDetailClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketDetailClient.tsx","TicketDetailClient"),d=(0,b.registerClientReference)(function(){throw Error("Attempted to call isPostCommentDisabled() from the server but isPostCommentDisabled is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketDetailClient.tsx","isPostCommentDisabled")},18192,a=>{"use strict";a.i(65892);var b=a.i(95433);a.n(b)},44612,a=>{"use strict";var b=a.i(7997),c=a.i(18192),d=a.i(37941);async function e({params:a}){let{teamId:e,ticket:f}=await a,g=await (0,d.getTicketMarkdown)(e,f);return g?(0,b.jsx)(c.TicketDetailClient,{teamId:e,ticketId:g.id,file:g.file,markdown:g.markdown,stage:g.stage,backHref:`/teams/${encodeURIComponent(e)}/tickets`,currentOwner:g.owner}):(0,b.jsxs)("div",{className:"ck-glass p-6",children:[(0,b.jsx)("h1",{className:"text-xl font-semibold tracking-tight",children:"Ticket not found"}),(0,b.jsxs)("p",{className:"mt-3 text-sm text-[color:var(--ck-text-secondary)]",children:["Couldn’t locate “",f,"” in backlog/in-progress/testing/done for team “",e,"”."]})]})}a.s(["default",()=>e,"dynamic",0,"force-dynamic"])}];
1
+ module.exports=[93695,(a,b,c)=>{b.exports=a.x("next/dist/shared/lib/no-fallback-error.external.js",()=>require("next/dist/shared/lib/no-fallback-error.external.js"))},12714,(a,b,c)=>{b.exports=a.x("node:fs/promises",()=>require("node:fs/promises"))},50227,(a,b,c)=>{b.exports=a.x("node:path",()=>require("node:path"))},60526,(a,b,c)=>{b.exports=a.x("node:os",()=>require("node:os"))},69264,a=>{a.n(a.i(54832))},50645,a=>{a.n(a.i(27572))},17537,a=>{a.n(a.i(3363))},13718,a=>{a.n(a.i(85523))},18198,a=>{a.n(a.i(45518))},37941,a=>{"use strict";var b=a.i(12714),c=a.i(60526),d=a.i(50227);function e(a){return a.includes("/")||a.includes("\\")}function f(a,b){let f=e(b)?b:function(a){if(!/^[a-z0-9][a-z0-9-]{1,63}$/.test(a))throw Error(`Invalid teamId "${a}"`);return d.default.join(c.default.homedir(),".openclaw",`workspace-${a}`)}(b);return d.default.join(f,{backlog:"work/backlog","in-progress":"work/in-progress",testing:"work/testing",done:"work/done"}[a])}async function g(){let a=d.default.join(c.default.homedir(),".openclaw"),e=[];try{e=await b.default.readdir(a)}catch{return[]}let f=e.filter(a=>a.startsWith("workspace-")).map(a=>a.slice(10)).filter(a=>!!a&&"workspace"!==a);return f.push("main"),Array.from(new Set(f)).sort()}async function h(a){let c,g=e(a)?"workspace"===(c=d.default.basename(a))?"main":c.startsWith("workspace-")?c.slice(10):c:a,h=[];for(let c of["backlog","in-progress","testing","done"]){let e=[];try{e=await b.default.readdir(f(c,a))}catch{e=[]}for(let i of e){if(!i.endsWith(".md"))continue;let e=function(a){let b=a.match(/^(\d{4})-/);return b?Number(b[1]):null}(i);if(null==e)continue;let j=d.default.join(f(c,a),i),[k,l]=await Promise.all([b.default.readFile(j,"utf8"),b.default.stat(j)]),m=function(a){let b=a.split("\n")[0]??"",c=b.startsWith("# ")?b.slice(2).trim():"",d=c.indexOf(" ");if(d>0){let a=c.slice(d+1).trim();if(a)return a}let e=c.replace(/^\d{4}-/,"").replace(/[-_]+/g," ").replace(/\s+/g," ").trim(),f=new Set(["api","cli","ui","ux","gpu","cpu","npm","pr","ci","cd","json","yaml","md"]),g=e?e.split(" ").filter(Boolean).map(a=>{let b=a.toLowerCase();return f.has(b)?a.toUpperCase():b.startsWith("v")&&/^\d/.test(b.slice(1))||/^[\d.]+$/.test(a)?a:a.slice(0,1).toUpperCase()+a.slice(1)}).join(" "):"";return g||c||"(untitled)"}(k),n=function(a,b){let c=RegExp(`^${b}:\\s*(.*)$`,"mi"),d=a.match(c);return d?.[1]?.trim()||null}(k,"Owner"),o=l.mtime.toISOString(),p=(Date.now()-l.mtimeMs)/36e5;h.push({teamId:g,number:e,id:i.replace(/\.md$/,""),title:m,owner:n,stage:c,file:j,updatedAt:o,ageHours:p})}}return h.sort((a,b)=>a.number-b.number),h}async function i(){let a=await g(),b=[];for(let c of a)b.push(...await h(c));return b.sort((a,b)=>a.teamId===b.teamId?a.number-b.number:a.teamId.localeCompare(b.teamId)),b}async function j(a,b){let c=await h(b),d=a.trim(),e=d.match(/^\d+$/)?c.find(a=>a.number===Number(d)):null;return c.find(a=>a.id===d)??e??null}async function k(a,c){let d=await j(c,a);return d?{teamId:d.teamId,id:d.id,file:d.file,markdown:await b.default.readFile(d.file,"utf8"),owner:d.owner,stage:d.stage}:null}a.s(["getTicketMarkdown",()=>k,"listAllTeamsTickets",()=>i,"listTickets",()=>h])},65892,a=>{"use strict";a.s(["TicketDetailClient",()=>c,"isPostCommentDisabled",()=>d]);var b=a.i(11857);let c=(0,b.registerClientReference)(function(){throw Error("Attempted to call TicketDetailClient() from the server but TicketDetailClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketDetailClient.tsx <module evaluation>","TicketDetailClient"),d=(0,b.registerClientReference)(function(){throw Error("Attempted to call isPostCommentDisabled() from the server but isPostCommentDisabled is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketDetailClient.tsx <module evaluation>","isPostCommentDisabled")},95433,a=>{"use strict";a.s(["TicketDetailClient",()=>c,"isPostCommentDisabled",()=>d]);var b=a.i(11857);let c=(0,b.registerClientReference)(function(){throw Error("Attempted to call TicketDetailClient() from the server but TicketDetailClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketDetailClient.tsx","TicketDetailClient"),d=(0,b.registerClientReference)(function(){throw Error("Attempted to call isPostCommentDisabled() from the server but isPostCommentDisabled is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketDetailClient.tsx","isPostCommentDisabled")},18192,a=>{"use strict";a.i(65892);var b=a.i(95433);a.n(b)},44612,a=>{"use strict";var b=a.i(7997),c=a.i(18192),d=a.i(37941);async function e({params:a}){let{teamId:e,ticket:f}=await a,g=await (0,d.getTicketMarkdown)(e,f);return g?(0,b.jsx)(c.TicketDetailClient,{teamId:e,ticketId:g.id,file:g.file,markdown:g.markdown,stage:g.stage,backHref:`/teams/${encodeURIComponent(e)}/tickets`,currentOwner:g.owner}):(0,b.jsxs)("div",{className:"ck-glass p-6",children:[(0,b.jsx)("h1",{className:"text-xl font-semibold tracking-tight",children:"Ticket not found"}),(0,b.jsxs)("p",{className:"mt-3 text-sm text-[color:var(--ck-text-secondary)]",children:["Couldn’t locate “",f,"” in backlog/in-progress/testing/done for team “",e,"”."]})]})}a.s(["default",()=>e,"dynamic",0,"force-dynamic"])}];
2
2
 
3
3
  //# sourceMappingURL=%5Broot-of-the-server%5D__9a7d79aa._.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/lib/tickets.ts","../../../../src/app/tickets/TicketDetailClient.tsx/__nextjs-internal-proxy.mjs","../../../../src/app/teams/%5BteamId%5D/tickets/%5Bticket%5D/page.tsx"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type TicketStage = \"backlog\" | \"in-progress\" | \"testing\" | \"done\";\n\nexport interface TicketSummary {\n teamId: string;\n number: number;\n id: string;\n title: string;\n owner: string | null;\n stage: TicketStage;\n file: string;\n updatedAt: string; // ISO\n ageHours: number;\n}\n\nfunction assertSafeTeamId(teamId: string) {\n // Conservative: matches OpenClaw team ids like \"development-team\".\n if (!/^[a-z0-9][a-z0-9-]{1,63}$/.test(teamId)) {\n throw new Error(`Invalid teamId \"${teamId}\"`);\n }\n}\n\nfunction isPathLike(s: string) {\n return s.includes(\"/\") || s.includes(\"\\\\\");\n}\n\nexport function teamWorkspace(teamId: string) {\n assertSafeTeamId(teamId);\n return path.join(os.homedir(), \".openclaw\", `workspace-${teamId}`);\n}\n\n/**\n * Back-compat helper for older non-team-scoped code paths.\n * Prefer passing explicit teamId into APIs instead.\n */\nexport function getTeamWorkspaceDir(teamId?: string): string {\n if (teamId) return teamWorkspace(teamId);\n return process.env.CK_TEAM_WORKSPACE_DIR ?? teamWorkspace(process.env.CK_TEAM_ID ?? \"development-team\");\n}\n\nexport function stageDir(stage: TicketStage, teamIdOrDir: string = \"development-team\") {\n const map: Record<TicketStage, string> = {\n backlog: \"work/backlog\",\n \"in-progress\": \"work/in-progress\",\n testing: \"work/testing\",\n done: \"work/done\",\n };\n\n const base = isPathLike(teamIdOrDir) ? teamIdOrDir : teamWorkspace(teamIdOrDir);\n return path.join(base, map[stage]);\n}\n\nexport function parseTitle(md: string) {\n // Ticket markdown files typically start with: # 0033-some-slug\n const firstLine = md.split(\"\\n\")[0] ?? \"\";\n const header = firstLine.startsWith(\"# \") ? firstLine.slice(2).trim() : \"\";\n\n // If header is like: \"<id> <title...>\" keep the explicit title portion.\n const firstSpace = header.indexOf(\" \");\n if (firstSpace > 0) {\n const afterSpace = header.slice(firstSpace + 1).trim();\n if (afterSpace) return afterSpace;\n }\n\n // Otherwise derive from the slug: strip leading number + hyphen, then de-kebab.\n const derivedRaw = header\n .replace(/^\\d{4}-/, \"\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n\n const ACRONYMS = new Set([\"api\", \"cli\", \"ui\", \"ux\", \"gpu\", \"cpu\", \"npm\", \"pr\", \"ci\", \"cd\", \"json\", \"yaml\", \"md\"]);\n const titleCase = (s: string) =>\n s\n .split(\" \")\n .filter(Boolean)\n .map((w) => {\n const lower = w.toLowerCase();\n if (ACRONYMS.has(lower)) return w.toUpperCase();\n if (lower.startsWith(\"v\") && /^\\d/.test(lower.slice(1))) return w; // version-like\n if (/^[\\d.]+$/.test(w)) return w; // numbers/semver\n return w.slice(0, 1).toUpperCase() + w.slice(1);\n })\n .join(\" \");\n\n const derived = derivedRaw ? titleCase(derivedRaw) : \"\";\n\n if (derived) return derived;\n return header || \"(untitled)\";\n}\n\nfunction parseField(md: string, field: string): string | null {\n const re = new RegExp(`^${field}:\\\\s*(.*)$`, \"mi\");\n const m = md.match(re);\n return m?.[1]?.trim() || null;\n}\n\nexport function parseNumberFromFilename(filename: string): number | null {\n const m = filename.match(/^(\\d{4})-/);\n if (!m) return null;\n return Number(m[1]);\n}\n\nfunction teamIdFromTeamDir(teamDir: string): string {\n const base = path.basename(teamDir);\n if (base === \"workspace\") return \"main\";\n if (base.startsWith(\"workspace-\")) return base.slice(\"workspace-\".length);\n return base;\n}\n\nexport async function discoverTeamIds(): Promise<string[]> {\n // Convention: ~/.openclaw/workspace-<teamId>\n const root = path.join(os.homedir(), \".openclaw\");\n let entries: string[] = [];\n try {\n entries = await fs.readdir(root);\n } catch {\n return [];\n }\n\n const teamIds = entries\n .filter((e) => e.startsWith(\"workspace-\"))\n .map((e) => e.slice(\"workspace-\".length))\n .filter((id) => Boolean(id) && id !== \"workspace\");\n\n // Also include personal workspace scope.\n teamIds.push(\"main\");\n\n return Array.from(new Set(teamIds)).sort();\n}\n\n/**\n * List tickets for a specific team (recommended).\n * - listTickets(\"development-team\")\n * - listTickets(teamWorkspaceDir)\n */\nexport async function listTickets(teamIdOrDir: string = \"development-team\"): Promise<TicketSummary[]> {\n const teamId = isPathLike(teamIdOrDir) ? teamIdFromTeamDir(teamIdOrDir) : teamIdOrDir;\n const stages: TicketStage[] = [\"backlog\", \"in-progress\", \"testing\", \"done\"];\n const all: TicketSummary[] = [];\n\n for (const stage of stages) {\n let files: string[] = [];\n try {\n files = await fs.readdir(stageDir(stage, teamIdOrDir));\n } catch {\n files = [];\n }\n\n for (const f of files) {\n if (!f.endsWith(\".md\")) continue;\n const number = parseNumberFromFilename(f);\n if (number == null) continue;\n\n const file = path.join(stageDir(stage, teamIdOrDir), f);\n const [md, stat] = await Promise.all([fs.readFile(file, \"utf8\"), fs.stat(file)]);\n\n const title = parseTitle(md);\n const owner = parseField(md, \"Owner\");\n const updatedAt = stat.mtime.toISOString();\n const ageHours = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);\n\n all.push({\n teamId,\n number,\n id: f.replace(/\\.md$/, \"\"),\n title,\n owner,\n stage,\n file,\n updatedAt,\n ageHours,\n });\n }\n }\n\n all.sort((a, b) => a.number - b.number);\n return all;\n}\n\n/**\n * List tickets across all discovered teams (used for /tickets?team=all).\n */\nexport async function listAllTeamsTickets(): Promise<TicketSummary[]> {\n const teamIds = await discoverTeamIds();\n const all: TicketSummary[] = [];\n\n for (const teamId of teamIds) {\n all.push(...(await listTickets(teamId)));\n }\n\n all.sort((a, b) => (a.teamId === b.teamId ? a.number - b.number : a.teamId.localeCompare(b.teamId)));\n return all;\n}\n\n/**\n * Back-compat helper used by some API routes.\n */\nexport async function getTicketByIdOrNumber(ticketIdOrNumber: string, teamIdOrDir: string = \"development-team\") {\n const tickets = await listTickets(teamIdOrDir);\n const normalized = ticketIdOrNumber.trim();\n\n const byNumber = normalized.match(/^\\d+$/) ? tickets.find((t) => t.number === Number(normalized)) : null;\n const byId = tickets.find((t) => t.id === normalized);\n return byId ?? byNumber ?? null;\n}\n\nexport async function resolveTicket(teamId: string, ticketIdOrNumber: string): Promise<TicketSummary | null> {\n return getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n}\n\n/**\n * getTicketMarkdown(teamId, ticketIdOrNumber)\n */\nexport async function getTicketMarkdown(\n teamId: string,\n ticketIdOrNumber: string,\n): Promise<{ teamId: string; id: string; file: string; markdown: string; owner: string | null; stage: TicketStage } | null> {\n const hit = await getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n if (!hit) return null;\n\n return {\n teamId: hit.teamId,\n id: hit.id,\n file: hit.file,\n markdown: await fs.readFile(hit.file, \"utf8\"),\n owner: hit.owner,\n stage: hit.stage,\n };\n}\n","// This file is generated by next-core EcmascriptClientReferenceModule.\nimport { registerClientReference } from \"react-server-dom-turbopack/server\";\nexport const TicketDetailClient = registerClientReference(\n function() { throw new Error(\"Attempted to call TicketDetailClient() from the server but TicketDetailClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\"); },\n \"[project]/src/app/tickets/TicketDetailClient.tsx\",\n \"TicketDetailClient\",\n);\nexport const isPostCommentDisabled = registerClientReference(\n function() { throw new Error(\"Attempted to call isPostCommentDisabled() from the server but isPostCommentDisabled is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\"); },\n \"[project]/src/app/tickets/TicketDetailClient.tsx\",\n \"isPostCommentDisabled\",\n);\n","import { TicketDetailClient } from \"@/app/tickets/TicketDetailClient\";\nimport { getTicketMarkdown } from \"@/lib/tickets\";\n\nexport const dynamic = \"force-dynamic\";\n\nexport default async function TeamTicketDetailPage({\n params,\n}: {\n params: Promise<{ teamId: string; ticket: string }>;\n}) {\n const { teamId, ticket } = await params;\n const data = await getTicketMarkdown(teamId, ticket);\n\n if (!data) {\n return (\n <div className=\"ck-glass p-6\">\n <h1 className=\"text-xl font-semibold tracking-tight\">Ticket not found</h1>\n <p className=\"mt-3 text-sm text-[color:var(--ck-text-secondary)]\">\n Couldn’t locate “{ticket}” in backlog/in-progress/testing/done for team “{teamId}”.\n </p>\n </div>\n );\n }\n\n return (\n <TicketDetailClient\n teamId={teamId}\n ticketId={data.id}\n file={data.file}\n markdown={data.markdown}\n stage={data.stage}\n backHref={`/teams/${encodeURIComponent(teamId)}/tickets`}\n currentOwner={data.owner}\n />\n );\n}\n"],"names":[],"mappings":"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","ignoreList":[1]}
1
+ {"version":3,"sources":["../../../../src/lib/tickets.ts","../../../../src/app/tickets/TicketDetailClient.tsx/__nextjs-internal-proxy.mjs","../../../../src/app/teams/%5BteamId%5D/tickets/%5Bticket%5D/page.tsx"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type TicketStage = \"backlog\" | \"in-progress\" | \"testing\" | \"done\";\n\nexport interface TicketSummary {\n teamId: string;\n number: number;\n id: string;\n title: string;\n owner: string | null;\n stage: TicketStage;\n file: string;\n updatedAt: string; // ISO\n ageHours: number;\n}\n\nfunction assertSafeTeamId(teamId: string) {\n // Conservative: matches OpenClaw team ids like \"development-team\".\n if (!/^[a-z0-9][a-z0-9-]{1,63}$/.test(teamId)) {\n throw new Error(`Invalid teamId \"${teamId}\"`);\n }\n}\n\nfunction isPathLike(s: string) {\n return s.includes(\"/\") || s.includes(\"\\\\\");\n}\n\nexport function teamWorkspace(teamId: string) {\n assertSafeTeamId(teamId);\n return path.join(os.homedir(), \".openclaw\", `workspace-${teamId}`);\n}\n\n/**\n * Back-compat helper for older non-team-scoped code paths.\n * Prefer passing explicit teamId into APIs instead.\n */\nexport function getTeamWorkspaceDir(teamId?: string): string {\n if (teamId) return teamWorkspace(teamId);\n const envTeam = process.env.CK_TEAM_ID;\n if (!envTeam && !process.env.CK_TEAM_WORKSPACE_DIR) {\n throw new Error(\"No team specified. Pass a teamId or set CK_TEAM_ID / CK_TEAM_WORKSPACE_DIR.\");\n }\n return process.env.CK_TEAM_WORKSPACE_DIR ?? teamWorkspace(envTeam!);\n}\n\nexport function stageDir(stage: TicketStage, teamIdOrDir: string) {\n const map: Record<TicketStage, string> = {\n backlog: \"work/backlog\",\n \"in-progress\": \"work/in-progress\",\n testing: \"work/testing\",\n done: \"work/done\",\n };\n\n const base = isPathLike(teamIdOrDir) ? teamIdOrDir : teamWorkspace(teamIdOrDir);\n return path.join(base, map[stage]);\n}\n\nexport function parseTitle(md: string) {\n // Ticket markdown files typically start with: # 0033-some-slug\n const firstLine = md.split(\"\\n\")[0] ?? \"\";\n const header = firstLine.startsWith(\"# \") ? firstLine.slice(2).trim() : \"\";\n\n // If header is like: \"<id> <title...>\" keep the explicit title portion.\n const firstSpace = header.indexOf(\" \");\n if (firstSpace > 0) {\n const afterSpace = header.slice(firstSpace + 1).trim();\n if (afterSpace) return afterSpace;\n }\n\n // Otherwise derive from the slug: strip leading number + hyphen, then de-kebab.\n const derivedRaw = header\n .replace(/^\\d{4}-/, \"\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n\n const ACRONYMS = new Set([\"api\", \"cli\", \"ui\", \"ux\", \"gpu\", \"cpu\", \"npm\", \"pr\", \"ci\", \"cd\", \"json\", \"yaml\", \"md\"]);\n const titleCase = (s: string) =>\n s\n .split(\" \")\n .filter(Boolean)\n .map((w) => {\n const lower = w.toLowerCase();\n if (ACRONYMS.has(lower)) return w.toUpperCase();\n if (lower.startsWith(\"v\") && /^\\d/.test(lower.slice(1))) return w; // version-like\n if (/^[\\d.]+$/.test(w)) return w; // numbers/semver\n return w.slice(0, 1).toUpperCase() + w.slice(1);\n })\n .join(\" \");\n\n const derived = derivedRaw ? titleCase(derivedRaw) : \"\";\n\n if (derived) return derived;\n return header || \"(untitled)\";\n}\n\nfunction parseField(md: string, field: string): string | null {\n const re = new RegExp(`^${field}:\\\\s*(.*)$`, \"mi\");\n const m = md.match(re);\n return m?.[1]?.trim() || null;\n}\n\nexport function parseNumberFromFilename(filename: string): number | null {\n const m = filename.match(/^(\\d{4})-/);\n if (!m) return null;\n return Number(m[1]);\n}\n\nfunction teamIdFromTeamDir(teamDir: string): string {\n const base = path.basename(teamDir);\n if (base === \"workspace\") return \"main\";\n if (base.startsWith(\"workspace-\")) return base.slice(\"workspace-\".length);\n return base;\n}\n\nexport async function discoverTeamIds(): Promise<string[]> {\n // Convention: ~/.openclaw/workspace-<teamId>\n const root = path.join(os.homedir(), \".openclaw\");\n let entries: string[] = [];\n try {\n entries = await fs.readdir(root);\n } catch {\n return [];\n }\n\n const teamIds = entries\n .filter((e) => e.startsWith(\"workspace-\"))\n .map((e) => e.slice(\"workspace-\".length))\n .filter((id) => Boolean(id) && id !== \"workspace\");\n\n // Also include personal workspace scope.\n teamIds.push(\"main\");\n\n return Array.from(new Set(teamIds)).sort();\n}\n\n/**\n * List tickets for a specific team (recommended).\n * - listTickets(\"my-team\")\n * - listTickets(teamWorkspaceDir)\n */\nexport async function listTickets(teamIdOrDir: string): Promise<TicketSummary[]> {\n const teamId = isPathLike(teamIdOrDir) ? teamIdFromTeamDir(teamIdOrDir) : teamIdOrDir;\n const stages: TicketStage[] = [\"backlog\", \"in-progress\", \"testing\", \"done\"];\n const all: TicketSummary[] = [];\n\n for (const stage of stages) {\n let files: string[] = [];\n try {\n files = await fs.readdir(stageDir(stage, teamIdOrDir));\n } catch {\n files = [];\n }\n\n for (const f of files) {\n if (!f.endsWith(\".md\")) continue;\n const number = parseNumberFromFilename(f);\n if (number == null) continue;\n\n const file = path.join(stageDir(stage, teamIdOrDir), f);\n const [md, stat] = await Promise.all([fs.readFile(file, \"utf8\"), fs.stat(file)]);\n\n const title = parseTitle(md);\n const owner = parseField(md, \"Owner\");\n const updatedAt = stat.mtime.toISOString();\n const ageHours = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);\n\n all.push({\n teamId,\n number,\n id: f.replace(/\\.md$/, \"\"),\n title,\n owner,\n stage,\n file,\n updatedAt,\n ageHours,\n });\n }\n }\n\n all.sort((a, b) => a.number - b.number);\n return all;\n}\n\n/**\n * List tickets across all discovered teams (used for /tickets?team=all).\n */\nexport async function listAllTeamsTickets(): Promise<TicketSummary[]> {\n const teamIds = await discoverTeamIds();\n const all: TicketSummary[] = [];\n\n for (const teamId of teamIds) {\n all.push(...(await listTickets(teamId)));\n }\n\n all.sort((a, b) => (a.teamId === b.teamId ? a.number - b.number : a.teamId.localeCompare(b.teamId)));\n return all;\n}\n\n/**\n * Back-compat helper used by some API routes.\n */\nexport async function getTicketByIdOrNumber(ticketIdOrNumber: string, teamIdOrDir: string) {\n const tickets = await listTickets(teamIdOrDir);\n const normalized = ticketIdOrNumber.trim();\n\n const byNumber = normalized.match(/^\\d+$/) ? tickets.find((t) => t.number === Number(normalized)) : null;\n const byId = tickets.find((t) => t.id === normalized);\n return byId ?? byNumber ?? null;\n}\n\nexport async function resolveTicket(teamId: string, ticketIdOrNumber: string): Promise<TicketSummary | null> {\n return getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n}\n\n/**\n * getTicketMarkdown(teamId, ticketIdOrNumber)\n */\nexport async function getTicketMarkdown(\n teamId: string,\n ticketIdOrNumber: string,\n): Promise<{ teamId: string; id: string; file: string; markdown: string; owner: string | null; stage: TicketStage } | null> {\n const hit = await getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n if (!hit) return null;\n\n return {\n teamId: hit.teamId,\n id: hit.id,\n file: hit.file,\n markdown: await fs.readFile(hit.file, \"utf8\"),\n owner: hit.owner,\n stage: hit.stage,\n };\n}\n","// This file is generated by next-core EcmascriptClientReferenceModule.\nimport { registerClientReference } from \"react-server-dom-turbopack/server\";\nexport const TicketDetailClient = registerClientReference(\n function() { throw new Error(\"Attempted to call TicketDetailClient() from the server but TicketDetailClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\"); },\n \"[project]/src/app/tickets/TicketDetailClient.tsx\",\n \"TicketDetailClient\",\n);\nexport const isPostCommentDisabled = registerClientReference(\n function() { throw new Error(\"Attempted to call isPostCommentDisabled() from the server but isPostCommentDisabled is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\"); },\n \"[project]/src/app/tickets/TicketDetailClient.tsx\",\n \"isPostCommentDisabled\",\n);\n","import { TicketDetailClient } from \"@/app/tickets/TicketDetailClient\";\nimport { getTicketMarkdown } from \"@/lib/tickets\";\n\nexport const dynamic = \"force-dynamic\";\n\nexport default async function TeamTicketDetailPage({\n params,\n}: {\n params: Promise<{ teamId: string; ticket: string }>;\n}) {\n const { teamId, ticket } = await params;\n const data = await getTicketMarkdown(teamId, ticket);\n\n if (!data) {\n return (\n <div className=\"ck-glass p-6\">\n <h1 className=\"text-xl font-semibold tracking-tight\">Ticket not found</h1>\n <p className=\"mt-3 text-sm text-[color:var(--ck-text-secondary)]\">\n Couldn’t locate “{ticket}” in backlog/in-progress/testing/done for team “{teamId}”.\n </p>\n </div>\n );\n }\n\n return (\n <TicketDetailClient\n teamId={teamId}\n ticketId={data.id}\n file={data.file}\n markdown={data.markdown}\n stage={data.stage}\n backHref={`/teams/${encodeURIComponent(teamId)}/tickets`}\n currentOwner={data.owner}\n />\n );\n}\n"],"names":[],"mappings":"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","ignoreList":[1]}
@@ -1,3 +1,3 @@
1
- module.exports=[93695,(a,b,c)=>{b.exports=a.x("next/dist/shared/lib/no-fallback-error.external.js",()=>require("next/dist/shared/lib/no-fallback-error.external.js"))},12714,(a,b,c)=>{b.exports=a.x("node:fs/promises",()=>require("node:fs/promises"))},50227,(a,b,c)=>{b.exports=a.x("node:path",()=>require("node:path"))},60526,(a,b,c)=>{b.exports=a.x("node:os",()=>require("node:os"))},69264,a=>{a.n(a.i(54832))},50645,a=>{a.n(a.i(27572))},17537,a=>{a.n(a.i(3363))},13718,a=>{a.n(a.i(85523))},18198,a=>{a.n(a.i(45518))},28215,a=>{"use strict";var b=a.i(12714),c=a.i(60526),d=a.i(50227);async function e(){let a=d.default.join(c.default.homedir(),".openclaw","openclaw.json");return JSON.parse(await b.default.readFile(a,"utf8"))}async function f(){let a=await e(),b=a.agents?.defaults?.workspace;if(!b)throw Error("agents.defaults.workspace is not set in ~/.openclaw/openclaw.json");return b}async function g(a){let b=c.default.homedir();if(!b)throw Error("Could not resolve home directory");return d.default.join(b,".openclaw",`workspace-${a}`)}a.s(["getTeamWorkspaceDir",()=>g,"getWorkspaceDir",()=>f])},37941,a=>{"use strict";var b=a.i(12714),c=a.i(60526),d=a.i(50227);function e(a){return a.includes("/")||a.includes("\\")}function f(a,b="development-team"){let g=e(b)?b:function(a){if(!/^[a-z0-9][a-z0-9-]{1,63}$/.test(a))throw Error(`Invalid teamId "${a}"`);return d.default.join(c.default.homedir(),".openclaw",`workspace-${a}`)}(b);return d.default.join(g,{backlog:"work/backlog","in-progress":"work/in-progress",testing:"work/testing",done:"work/done"}[a])}async function g(a="development-team"){let c,h=e(a)?"workspace"===(c=d.default.basename(a))?"main":c.startsWith("workspace-")?c.slice(10):c:a,i=[];for(let c of["backlog","in-progress","testing","done"]){let e=[];try{e=await b.default.readdir(f(c,a))}catch{e=[]}for(let g of e){if(!g.endsWith(".md"))continue;let e=function(a){let b=a.match(/^(\d{4})-/);return b?Number(b[1]):null}(g);if(null==e)continue;let j=d.default.join(f(c,a),g),[k,l]=await Promise.all([b.default.readFile(j,"utf8"),b.default.stat(j)]),m=function(a){let b=a.split("\n")[0]??"",c=b.startsWith("# ")?b.slice(2).trim():"",d=c.indexOf(" ");if(d>0){let a=c.slice(d+1).trim();if(a)return a}let e=c.replace(/^\d{4}-/,"").replace(/[-_]+/g," ").replace(/\s+/g," ").trim(),f=new Set(["api","cli","ui","ux","gpu","cpu","npm","pr","ci","cd","json","yaml","md"]),g=e?e.split(" ").filter(Boolean).map(a=>{let b=a.toLowerCase();return f.has(b)?a.toUpperCase():b.startsWith("v")&&/^\d/.test(b.slice(1))||/^[\d.]+$/.test(a)?a:a.slice(0,1).toUpperCase()+a.slice(1)}).join(" "):"";return g||c||"(untitled)"}(k),n=function(a,b){let c=RegExp(`^${b}:\\s*(.*)$`,"mi"),d=a.match(c);return d?.[1]?.trim()||null}(k,"Owner"),o=l.mtime.toISOString(),p=(Date.now()-l.mtimeMs)/36e5;i.push({teamId:h,number:e,id:g.replace(/\.md$/,""),title:m,owner:n,stage:c,file:j,updatedAt:o,ageHours:p})}}return i.sort((a,b)=>a.number-b.number),i}async function h(a,b="development-team"){let c=await g(b),d=a.trim(),e=d.match(/^\d+$/)?c.find(a=>a.number===Number(d)):null;return c.find(a=>a.id===d)??e??null}async function i(a,c){let d=await h(c,a);return d?{teamId:d.teamId,id:d.id,file:d.file,markdown:await b.default.readFile(d.file,"utf8"),owner:d.owner,stage:d.stage}:null}a.s(["getTicketMarkdown",()=>i,"listTickets",()=>g])},55468,a=>{"use strict";a.s(["TicketsBoardClient",()=>b]);let b=(0,a.i(11857).registerClientReference)(function(){throw Error("Attempted to call TicketsBoardClient() from the server but TicketsBoardClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketsBoardClient.tsx <module evaluation>","TicketsBoardClient")},46566,a=>{"use strict";a.s(["TicketsBoardClient",()=>b]);let b=(0,a.i(11857).registerClientReference)(function(){throw Error("Attempted to call TicketsBoardClient() from the server but TicketsBoardClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketsBoardClient.tsx","TicketsBoardClient")},26306,a=>{"use strict";a.i(55468);var b=a.i(46566);a.n(b)},119,a=>{"use strict";var b=a.i(7997),c=a.i(26306),d=a.i(37941),e=a.i(28215);async function f({params:a}){let{teamId:f}=await a,g="main"===f?await (0,e.getWorkspaceDir)():f,h=await (0,d.listTickets)(g);return(0,b.jsx)(c.TicketsBoardClient,{tickets:h,basePath:`/teams/${encodeURIComponent(f)}/tickets`,selectedTeamId:f})}a.s(["default",()=>f,"dynamic",0,"force-dynamic"])}];
1
+ module.exports=[93695,(a,b,c)=>{b.exports=a.x("next/dist/shared/lib/no-fallback-error.external.js",()=>require("next/dist/shared/lib/no-fallback-error.external.js"))},12714,(a,b,c)=>{b.exports=a.x("node:fs/promises",()=>require("node:fs/promises"))},50227,(a,b,c)=>{b.exports=a.x("node:path",()=>require("node:path"))},60526,(a,b,c)=>{b.exports=a.x("node:os",()=>require("node:os"))},69264,a=>{a.n(a.i(54832))},50645,a=>{a.n(a.i(27572))},17537,a=>{a.n(a.i(3363))},13718,a=>{a.n(a.i(85523))},18198,a=>{a.n(a.i(45518))},28215,a=>{"use strict";var b=a.i(12714),c=a.i(60526),d=a.i(50227);async function e(){let a=d.default.join(c.default.homedir(),".openclaw","openclaw.json");return JSON.parse(await b.default.readFile(a,"utf8"))}async function f(){let a=await e(),b=a.agents?.defaults?.workspace;if(!b)throw Error("agents.defaults.workspace is not set in ~/.openclaw/openclaw.json");return b}async function g(a){let b=c.default.homedir();if(!b)throw Error("Could not resolve home directory");return d.default.join(b,".openclaw",`workspace-${a}`)}a.s(["getTeamWorkspaceDir",()=>g,"getWorkspaceDir",()=>f])},37941,a=>{"use strict";var b=a.i(12714),c=a.i(60526),d=a.i(50227);function e(a){return a.includes("/")||a.includes("\\")}function f(a,b){let f=e(b)?b:function(a){if(!/^[a-z0-9][a-z0-9-]{1,63}$/.test(a))throw Error(`Invalid teamId "${a}"`);return d.default.join(c.default.homedir(),".openclaw",`workspace-${a}`)}(b);return d.default.join(f,{backlog:"work/backlog","in-progress":"work/in-progress",testing:"work/testing",done:"work/done"}[a])}async function g(){let a=d.default.join(c.default.homedir(),".openclaw"),e=[];try{e=await b.default.readdir(a)}catch{return[]}let f=e.filter(a=>a.startsWith("workspace-")).map(a=>a.slice(10)).filter(a=>!!a&&"workspace"!==a);return f.push("main"),Array.from(new Set(f)).sort()}async function h(a){let c,g=e(a)?"workspace"===(c=d.default.basename(a))?"main":c.startsWith("workspace-")?c.slice(10):c:a,h=[];for(let c of["backlog","in-progress","testing","done"]){let e=[];try{e=await b.default.readdir(f(c,a))}catch{e=[]}for(let i of e){if(!i.endsWith(".md"))continue;let e=function(a){let b=a.match(/^(\d{4})-/);return b?Number(b[1]):null}(i);if(null==e)continue;let j=d.default.join(f(c,a),i),[k,l]=await Promise.all([b.default.readFile(j,"utf8"),b.default.stat(j)]),m=function(a){let b=a.split("\n")[0]??"",c=b.startsWith("# ")?b.slice(2).trim():"",d=c.indexOf(" ");if(d>0){let a=c.slice(d+1).trim();if(a)return a}let e=c.replace(/^\d{4}-/,"").replace(/[-_]+/g," ").replace(/\s+/g," ").trim(),f=new Set(["api","cli","ui","ux","gpu","cpu","npm","pr","ci","cd","json","yaml","md"]),g=e?e.split(" ").filter(Boolean).map(a=>{let b=a.toLowerCase();return f.has(b)?a.toUpperCase():b.startsWith("v")&&/^\d/.test(b.slice(1))||/^[\d.]+$/.test(a)?a:a.slice(0,1).toUpperCase()+a.slice(1)}).join(" "):"";return g||c||"(untitled)"}(k),n=function(a,b){let c=RegExp(`^${b}:\\s*(.*)$`,"mi"),d=a.match(c);return d?.[1]?.trim()||null}(k,"Owner"),o=l.mtime.toISOString(),p=(Date.now()-l.mtimeMs)/36e5;h.push({teamId:g,number:e,id:i.replace(/\.md$/,""),title:m,owner:n,stage:c,file:j,updatedAt:o,ageHours:p})}}return h.sort((a,b)=>a.number-b.number),h}async function i(){let a=await g(),b=[];for(let c of a)b.push(...await h(c));return b.sort((a,b)=>a.teamId===b.teamId?a.number-b.number:a.teamId.localeCompare(b.teamId)),b}async function j(a,b){let c=await h(b),d=a.trim(),e=d.match(/^\d+$/)?c.find(a=>a.number===Number(d)):null;return c.find(a=>a.id===d)??e??null}async function k(a,c){let d=await j(c,a);return d?{teamId:d.teamId,id:d.id,file:d.file,markdown:await b.default.readFile(d.file,"utf8"),owner:d.owner,stage:d.stage}:null}a.s(["getTicketMarkdown",()=>k,"listAllTeamsTickets",()=>i,"listTickets",()=>h])},55468,a=>{"use strict";a.s(["TicketsBoardClient",()=>b]);let b=(0,a.i(11857).registerClientReference)(function(){throw Error("Attempted to call TicketsBoardClient() from the server but TicketsBoardClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketsBoardClient.tsx <module evaluation>","TicketsBoardClient")},46566,a=>{"use strict";a.s(["TicketsBoardClient",()=>b]);let b=(0,a.i(11857).registerClientReference)(function(){throw Error("Attempted to call TicketsBoardClient() from the server but TicketsBoardClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"[project]/src/app/tickets/TicketsBoardClient.tsx","TicketsBoardClient")},26306,a=>{"use strict";a.i(55468);var b=a.i(46566);a.n(b)},119,a=>{"use strict";var b=a.i(7997),c=a.i(26306),d=a.i(37941),e=a.i(28215);async function f({params:a}){let{teamId:f}=await a,g="main"===f?await (0,e.getWorkspaceDir)():f,h=await (0,d.listTickets)(g);return(0,b.jsx)(c.TicketsBoardClient,{tickets:h,basePath:`/teams/${encodeURIComponent(f)}/tickets`,selectedTeamId:f})}a.s(["default",()=>f,"dynamic",0,"force-dynamic"])}];
2
2
 
3
3
  //# sourceMappingURL=%5Broot-of-the-server%5D__c777e326._.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/lib/paths.ts","../../../../src/lib/tickets.ts","../../../../src/app/tickets/TicketsBoardClient.tsx/__nextjs-internal-proxy.mjs","../../../../src/app/teams/%5BteamId%5D/tickets/page.tsx"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\ntype OpenClawConfig = {\n agents?: { defaults?: { workspace?: string } };\n gateway?: { port?: number; auth?: { token?: string } };\n tools?: {\n agentToAgent?: {\n enabled?: boolean;\n allow?: string[];\n };\n };\n plugins?: {\n installs?: { recipes?: { installPath?: string; sourcePath?: string } };\n load?: { paths?: string[] };\n };\n // OpenClaw message routing bindings (used for HITL approvals, etc.)\n bindings?: unknown[];\n};\n\nexport async function readOpenClawConfig(): Promise<OpenClawConfig> {\n const p = path.join(os.homedir(), \".openclaw\", \"openclaw.json\");\n const text = await fs.readFile(p, \"utf8\");\n return JSON.parse(text) as OpenClawConfig;\n}\n\nexport async function getWorkspaceDir() {\n const cfg = await readOpenClawConfig();\n const ws = cfg.agents?.defaults?.workspace;\n if (!ws) throw new Error(\"agents.defaults.workspace is not set in ~/.openclaw/openclaw.json\");\n return ws;\n}\n\nexport async function getWorkspaceRecipesDir() {\n const ws = await getWorkspaceDir();\n return path.join(ws, \"recipes\");\n}\n\nexport async function getWorkspaceGoalsDir() {\n const ws = await getWorkspaceDir();\n\n // Back-compat: some installs keep goals at <workspace>/goals.\n // Prefer that when present, otherwise fall back to <workspace>/notes/goals.\n const direct = path.join(ws, \"goals\");\n try {\n const st = await fs.stat(direct);\n if (st.isDirectory()) return direct;\n } catch {\n // ignore\n }\n\n return path.join(ws, \"notes\", \"goals\");\n}\n\nexport async function getTeamWorkspaceDir(teamId: string) {\n const home = os.homedir();\n if (!home) throw new Error(\"Could not resolve home directory\");\n return path.join(home, \".openclaw\", `workspace-${teamId}`);\n}\n\n/** Team workspace dir derived from agents.defaults.workspace (sibling: .. / workspace-{teamId}) */\nexport function teamDirFromBaseWorkspace(baseWorkspace: string, teamId: string) {\n // Special-case: treat \"main\" as the personal workspace (agents.defaults.workspace).\n // This lets Kitchen expose a selectable \"main\" scope without creating a separate\n // ~/.openclaw/workspace-main folder.\n if (teamId === \"main\") return path.resolve(baseWorkspace);\n return path.resolve(baseWorkspace, \"..\", `workspace-${teamId}`);\n}\n\n/** Rejects path traversal and empty names; returns normalized name. */\nexport function assertSafeRelativeFileName(name: string): string {\n const n = name.replace(/\\\\/g, \"/\");\n if (!n || n.startsWith(\"/\") || n.includes(\"..\")) throw new Error(\"Invalid file name\");\n return n;\n}\n\nexport async function getBuiltinRecipesDir() {\n const cfg = await readOpenClawConfig();\n const p =\n cfg.plugins?.installs?.recipes?.installPath ||\n cfg.plugins?.installs?.recipes?.sourcePath ||\n cfg.plugins?.load?.paths?.[0];\n if (!p) throw new Error(\"Could not determine recipes plugin install path from ~/.openclaw/openclaw.json\");\n return path.join(p, \"recipes\", \"default\");\n}\n","import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type TicketStage = \"backlog\" | \"in-progress\" | \"testing\" | \"done\";\n\nexport interface TicketSummary {\n teamId: string;\n number: number;\n id: string;\n title: string;\n owner: string | null;\n stage: TicketStage;\n file: string;\n updatedAt: string; // ISO\n ageHours: number;\n}\n\nfunction assertSafeTeamId(teamId: string) {\n // Conservative: matches OpenClaw team ids like \"development-team\".\n if (!/^[a-z0-9][a-z0-9-]{1,63}$/.test(teamId)) {\n throw new Error(`Invalid teamId \"${teamId}\"`);\n }\n}\n\nfunction isPathLike(s: string) {\n return s.includes(\"/\") || s.includes(\"\\\\\");\n}\n\nexport function teamWorkspace(teamId: string) {\n assertSafeTeamId(teamId);\n return path.join(os.homedir(), \".openclaw\", `workspace-${teamId}`);\n}\n\n/**\n * Back-compat helper for older non-team-scoped code paths.\n * Prefer passing explicit teamId into APIs instead.\n */\nexport function getTeamWorkspaceDir(teamId?: string): string {\n if (teamId) return teamWorkspace(teamId);\n return process.env.CK_TEAM_WORKSPACE_DIR ?? teamWorkspace(process.env.CK_TEAM_ID ?? \"development-team\");\n}\n\nexport function stageDir(stage: TicketStage, teamIdOrDir: string = \"development-team\") {\n const map: Record<TicketStage, string> = {\n backlog: \"work/backlog\",\n \"in-progress\": \"work/in-progress\",\n testing: \"work/testing\",\n done: \"work/done\",\n };\n\n const base = isPathLike(teamIdOrDir) ? teamIdOrDir : teamWorkspace(teamIdOrDir);\n return path.join(base, map[stage]);\n}\n\nexport function parseTitle(md: string) {\n // Ticket markdown files typically start with: # 0033-some-slug\n const firstLine = md.split(\"\\n\")[0] ?? \"\";\n const header = firstLine.startsWith(\"# \") ? firstLine.slice(2).trim() : \"\";\n\n // If header is like: \"<id> <title...>\" keep the explicit title portion.\n const firstSpace = header.indexOf(\" \");\n if (firstSpace > 0) {\n const afterSpace = header.slice(firstSpace + 1).trim();\n if (afterSpace) return afterSpace;\n }\n\n // Otherwise derive from the slug: strip leading number + hyphen, then de-kebab.\n const derivedRaw = header\n .replace(/^\\d{4}-/, \"\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n\n const ACRONYMS = new Set([\"api\", \"cli\", \"ui\", \"ux\", \"gpu\", \"cpu\", \"npm\", \"pr\", \"ci\", \"cd\", \"json\", \"yaml\", \"md\"]);\n const titleCase = (s: string) =>\n s\n .split(\" \")\n .filter(Boolean)\n .map((w) => {\n const lower = w.toLowerCase();\n if (ACRONYMS.has(lower)) return w.toUpperCase();\n if (lower.startsWith(\"v\") && /^\\d/.test(lower.slice(1))) return w; // version-like\n if (/^[\\d.]+$/.test(w)) return w; // numbers/semver\n return w.slice(0, 1).toUpperCase() + w.slice(1);\n })\n .join(\" \");\n\n const derived = derivedRaw ? titleCase(derivedRaw) : \"\";\n\n if (derived) return derived;\n return header || \"(untitled)\";\n}\n\nfunction parseField(md: string, field: string): string | null {\n const re = new RegExp(`^${field}:\\\\s*(.*)$`, \"mi\");\n const m = md.match(re);\n return m?.[1]?.trim() || null;\n}\n\nexport function parseNumberFromFilename(filename: string): number | null {\n const m = filename.match(/^(\\d{4})-/);\n if (!m) return null;\n return Number(m[1]);\n}\n\nfunction teamIdFromTeamDir(teamDir: string): string {\n const base = path.basename(teamDir);\n if (base === \"workspace\") return \"main\";\n if (base.startsWith(\"workspace-\")) return base.slice(\"workspace-\".length);\n return base;\n}\n\nexport async function discoverTeamIds(): Promise<string[]> {\n // Convention: ~/.openclaw/workspace-<teamId>\n const root = path.join(os.homedir(), \".openclaw\");\n let entries: string[] = [];\n try {\n entries = await fs.readdir(root);\n } catch {\n return [];\n }\n\n const teamIds = entries\n .filter((e) => e.startsWith(\"workspace-\"))\n .map((e) => e.slice(\"workspace-\".length))\n .filter((id) => Boolean(id) && id !== \"workspace\");\n\n // Also include personal workspace scope.\n teamIds.push(\"main\");\n\n return Array.from(new Set(teamIds)).sort();\n}\n\n/**\n * List tickets for a specific team (recommended).\n * - listTickets(\"development-team\")\n * - listTickets(teamWorkspaceDir)\n */\nexport async function listTickets(teamIdOrDir: string = \"development-team\"): Promise<TicketSummary[]> {\n const teamId = isPathLike(teamIdOrDir) ? teamIdFromTeamDir(teamIdOrDir) : teamIdOrDir;\n const stages: TicketStage[] = [\"backlog\", \"in-progress\", \"testing\", \"done\"];\n const all: TicketSummary[] = [];\n\n for (const stage of stages) {\n let files: string[] = [];\n try {\n files = await fs.readdir(stageDir(stage, teamIdOrDir));\n } catch {\n files = [];\n }\n\n for (const f of files) {\n if (!f.endsWith(\".md\")) continue;\n const number = parseNumberFromFilename(f);\n if (number == null) continue;\n\n const file = path.join(stageDir(stage, teamIdOrDir), f);\n const [md, stat] = await Promise.all([fs.readFile(file, \"utf8\"), fs.stat(file)]);\n\n const title = parseTitle(md);\n const owner = parseField(md, \"Owner\");\n const updatedAt = stat.mtime.toISOString();\n const ageHours = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);\n\n all.push({\n teamId,\n number,\n id: f.replace(/\\.md$/, \"\"),\n title,\n owner,\n stage,\n file,\n updatedAt,\n ageHours,\n });\n }\n }\n\n all.sort((a, b) => a.number - b.number);\n return all;\n}\n\n/**\n * List tickets across all discovered teams (used for /tickets?team=all).\n */\nexport async function listAllTeamsTickets(): Promise<TicketSummary[]> {\n const teamIds = await discoverTeamIds();\n const all: TicketSummary[] = [];\n\n for (const teamId of teamIds) {\n all.push(...(await listTickets(teamId)));\n }\n\n all.sort((a, b) => (a.teamId === b.teamId ? a.number - b.number : a.teamId.localeCompare(b.teamId)));\n return all;\n}\n\n/**\n * Back-compat helper used by some API routes.\n */\nexport async function getTicketByIdOrNumber(ticketIdOrNumber: string, teamIdOrDir: string = \"development-team\") {\n const tickets = await listTickets(teamIdOrDir);\n const normalized = ticketIdOrNumber.trim();\n\n const byNumber = normalized.match(/^\\d+$/) ? tickets.find((t) => t.number === Number(normalized)) : null;\n const byId = tickets.find((t) => t.id === normalized);\n return byId ?? byNumber ?? null;\n}\n\nexport async function resolveTicket(teamId: string, ticketIdOrNumber: string): Promise<TicketSummary | null> {\n return getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n}\n\n/**\n * getTicketMarkdown(teamId, ticketIdOrNumber)\n */\nexport async function getTicketMarkdown(\n teamId: string,\n ticketIdOrNumber: string,\n): Promise<{ teamId: string; id: string; file: string; markdown: string; owner: string | null; stage: TicketStage } | null> {\n const hit = await getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n if (!hit) return null;\n\n return {\n teamId: hit.teamId,\n id: hit.id,\n file: hit.file,\n markdown: await fs.readFile(hit.file, \"utf8\"),\n owner: hit.owner,\n stage: hit.stage,\n };\n}\n","// This file is generated by next-core EcmascriptClientReferenceModule.\nimport { registerClientReference } from \"react-server-dom-turbopack/server\";\nexport const TicketsBoardClient = registerClientReference(\n function() { throw new Error(\"Attempted to call TicketsBoardClient() from the server but TicketsBoardClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\"); },\n \"[project]/src/app/tickets/TicketsBoardClient.tsx\",\n \"TicketsBoardClient\",\n);\n","import { TicketsBoardClient } from \"@/app/tickets/TicketsBoardClient\";\nimport { listTickets } from \"@/lib/tickets\";\nimport { getWorkspaceDir } from \"@/lib/paths\";\n\nexport const dynamic = \"force-dynamic\";\n\nexport default async function TeamTicketsPage({\n params,\n}: {\n params: Promise<{ teamId: string }>;\n}) {\n const { teamId } = await params;\n const scope = teamId === \"main\" ? await getWorkspaceDir() : teamId;\n const tickets = await listTickets(scope);\n\n return (\n <TicketsBoardClient\n tickets={tickets}\n basePath={`/teams/${encodeURIComponent(teamId)}/tickets`}\n selectedTeamId={teamId}\n />\n );\n}\n"],"names":[],"mappings":"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","ignoreList":[2]}
1
+ {"version":3,"sources":["../../../../src/lib/paths.ts","../../../../src/lib/tickets.ts","../../../../src/app/tickets/TicketsBoardClient.tsx/__nextjs-internal-proxy.mjs","../../../../src/app/teams/%5BteamId%5D/tickets/page.tsx"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\ntype OpenClawConfig = {\n agents?: { defaults?: { workspace?: string } };\n gateway?: { port?: number; auth?: { token?: string } };\n tools?: {\n agentToAgent?: {\n enabled?: boolean;\n allow?: string[];\n };\n };\n plugins?: {\n installs?: { recipes?: { installPath?: string; sourcePath?: string } };\n load?: { paths?: string[] };\n };\n // OpenClaw message routing bindings (used for HITL approvals, etc.)\n bindings?: unknown[];\n};\n\nexport async function readOpenClawConfig(): Promise<OpenClawConfig> {\n const p = path.join(os.homedir(), \".openclaw\", \"openclaw.json\");\n const text = await fs.readFile(p, \"utf8\");\n return JSON.parse(text) as OpenClawConfig;\n}\n\nexport async function getWorkspaceDir() {\n const cfg = await readOpenClawConfig();\n const ws = cfg.agents?.defaults?.workspace;\n if (!ws) throw new Error(\"agents.defaults.workspace is not set in ~/.openclaw/openclaw.json\");\n return ws;\n}\n\nexport async function getWorkspaceRecipesDir() {\n const ws = await getWorkspaceDir();\n return path.join(ws, \"recipes\");\n}\n\nexport async function getWorkspaceGoalsDir() {\n const ws = await getWorkspaceDir();\n\n // Back-compat: some installs keep goals at <workspace>/goals.\n // Prefer that when present, otherwise fall back to <workspace>/notes/goals.\n const direct = path.join(ws, \"goals\");\n try {\n const st = await fs.stat(direct);\n if (st.isDirectory()) return direct;\n } catch {\n // ignore\n }\n\n return path.join(ws, \"notes\", \"goals\");\n}\n\nexport async function getTeamWorkspaceDir(teamId: string) {\n const home = os.homedir();\n if (!home) throw new Error(\"Could not resolve home directory\");\n return path.join(home, \".openclaw\", `workspace-${teamId}`);\n}\n\n/** Team workspace dir derived from agents.defaults.workspace (sibling: .. / workspace-{teamId}) */\nexport function teamDirFromBaseWorkspace(baseWorkspace: string, teamId: string) {\n // Special-case: treat \"main\" as the personal workspace (agents.defaults.workspace).\n // This lets Kitchen expose a selectable \"main\" scope without creating a separate\n // ~/.openclaw/workspace-main folder.\n if (teamId === \"main\") return path.resolve(baseWorkspace);\n return path.resolve(baseWorkspace, \"..\", `workspace-${teamId}`);\n}\n\n/** Rejects path traversal and empty names; returns normalized name. */\nexport function assertSafeRelativeFileName(name: string): string {\n const n = name.replace(/\\\\/g, \"/\");\n if (!n || n.startsWith(\"/\") || n.includes(\"..\")) throw new Error(\"Invalid file name\");\n return n;\n}\n\nexport async function getBuiltinRecipesDir() {\n const cfg = await readOpenClawConfig();\n const p =\n cfg.plugins?.installs?.recipes?.installPath ||\n cfg.plugins?.installs?.recipes?.sourcePath ||\n cfg.plugins?.load?.paths?.[0];\n if (!p) throw new Error(\"Could not determine recipes plugin install path from ~/.openclaw/openclaw.json\");\n return path.join(p, \"recipes\", \"default\");\n}\n","import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type TicketStage = \"backlog\" | \"in-progress\" | \"testing\" | \"done\";\n\nexport interface TicketSummary {\n teamId: string;\n number: number;\n id: string;\n title: string;\n owner: string | null;\n stage: TicketStage;\n file: string;\n updatedAt: string; // ISO\n ageHours: number;\n}\n\nfunction assertSafeTeamId(teamId: string) {\n // Conservative: matches OpenClaw team ids like \"development-team\".\n if (!/^[a-z0-9][a-z0-9-]{1,63}$/.test(teamId)) {\n throw new Error(`Invalid teamId \"${teamId}\"`);\n }\n}\n\nfunction isPathLike(s: string) {\n return s.includes(\"/\") || s.includes(\"\\\\\");\n}\n\nexport function teamWorkspace(teamId: string) {\n assertSafeTeamId(teamId);\n return path.join(os.homedir(), \".openclaw\", `workspace-${teamId}`);\n}\n\n/**\n * Back-compat helper for older non-team-scoped code paths.\n * Prefer passing explicit teamId into APIs instead.\n */\nexport function getTeamWorkspaceDir(teamId?: string): string {\n if (teamId) return teamWorkspace(teamId);\n const envTeam = process.env.CK_TEAM_ID;\n if (!envTeam && !process.env.CK_TEAM_WORKSPACE_DIR) {\n throw new Error(\"No team specified. Pass a teamId or set CK_TEAM_ID / CK_TEAM_WORKSPACE_DIR.\");\n }\n return process.env.CK_TEAM_WORKSPACE_DIR ?? teamWorkspace(envTeam!);\n}\n\nexport function stageDir(stage: TicketStage, teamIdOrDir: string) {\n const map: Record<TicketStage, string> = {\n backlog: \"work/backlog\",\n \"in-progress\": \"work/in-progress\",\n testing: \"work/testing\",\n done: \"work/done\",\n };\n\n const base = isPathLike(teamIdOrDir) ? teamIdOrDir : teamWorkspace(teamIdOrDir);\n return path.join(base, map[stage]);\n}\n\nexport function parseTitle(md: string) {\n // Ticket markdown files typically start with: # 0033-some-slug\n const firstLine = md.split(\"\\n\")[0] ?? \"\";\n const header = firstLine.startsWith(\"# \") ? firstLine.slice(2).trim() : \"\";\n\n // If header is like: \"<id> <title...>\" keep the explicit title portion.\n const firstSpace = header.indexOf(\" \");\n if (firstSpace > 0) {\n const afterSpace = header.slice(firstSpace + 1).trim();\n if (afterSpace) return afterSpace;\n }\n\n // Otherwise derive from the slug: strip leading number + hyphen, then de-kebab.\n const derivedRaw = header\n .replace(/^\\d{4}-/, \"\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n\n const ACRONYMS = new Set([\"api\", \"cli\", \"ui\", \"ux\", \"gpu\", \"cpu\", \"npm\", \"pr\", \"ci\", \"cd\", \"json\", \"yaml\", \"md\"]);\n const titleCase = (s: string) =>\n s\n .split(\" \")\n .filter(Boolean)\n .map((w) => {\n const lower = w.toLowerCase();\n if (ACRONYMS.has(lower)) return w.toUpperCase();\n if (lower.startsWith(\"v\") && /^\\d/.test(lower.slice(1))) return w; // version-like\n if (/^[\\d.]+$/.test(w)) return w; // numbers/semver\n return w.slice(0, 1).toUpperCase() + w.slice(1);\n })\n .join(\" \");\n\n const derived = derivedRaw ? titleCase(derivedRaw) : \"\";\n\n if (derived) return derived;\n return header || \"(untitled)\";\n}\n\nfunction parseField(md: string, field: string): string | null {\n const re = new RegExp(`^${field}:\\\\s*(.*)$`, \"mi\");\n const m = md.match(re);\n return m?.[1]?.trim() || null;\n}\n\nexport function parseNumberFromFilename(filename: string): number | null {\n const m = filename.match(/^(\\d{4})-/);\n if (!m) return null;\n return Number(m[1]);\n}\n\nfunction teamIdFromTeamDir(teamDir: string): string {\n const base = path.basename(teamDir);\n if (base === \"workspace\") return \"main\";\n if (base.startsWith(\"workspace-\")) return base.slice(\"workspace-\".length);\n return base;\n}\n\nexport async function discoverTeamIds(): Promise<string[]> {\n // Convention: ~/.openclaw/workspace-<teamId>\n const root = path.join(os.homedir(), \".openclaw\");\n let entries: string[] = [];\n try {\n entries = await fs.readdir(root);\n } catch {\n return [];\n }\n\n const teamIds = entries\n .filter((e) => e.startsWith(\"workspace-\"))\n .map((e) => e.slice(\"workspace-\".length))\n .filter((id) => Boolean(id) && id !== \"workspace\");\n\n // Also include personal workspace scope.\n teamIds.push(\"main\");\n\n return Array.from(new Set(teamIds)).sort();\n}\n\n/**\n * List tickets for a specific team (recommended).\n * - listTickets(\"my-team\")\n * - listTickets(teamWorkspaceDir)\n */\nexport async function listTickets(teamIdOrDir: string): Promise<TicketSummary[]> {\n const teamId = isPathLike(teamIdOrDir) ? teamIdFromTeamDir(teamIdOrDir) : teamIdOrDir;\n const stages: TicketStage[] = [\"backlog\", \"in-progress\", \"testing\", \"done\"];\n const all: TicketSummary[] = [];\n\n for (const stage of stages) {\n let files: string[] = [];\n try {\n files = await fs.readdir(stageDir(stage, teamIdOrDir));\n } catch {\n files = [];\n }\n\n for (const f of files) {\n if (!f.endsWith(\".md\")) continue;\n const number = parseNumberFromFilename(f);\n if (number == null) continue;\n\n const file = path.join(stageDir(stage, teamIdOrDir), f);\n const [md, stat] = await Promise.all([fs.readFile(file, \"utf8\"), fs.stat(file)]);\n\n const title = parseTitle(md);\n const owner = parseField(md, \"Owner\");\n const updatedAt = stat.mtime.toISOString();\n const ageHours = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);\n\n all.push({\n teamId,\n number,\n id: f.replace(/\\.md$/, \"\"),\n title,\n owner,\n stage,\n file,\n updatedAt,\n ageHours,\n });\n }\n }\n\n all.sort((a, b) => a.number - b.number);\n return all;\n}\n\n/**\n * List tickets across all discovered teams (used for /tickets?team=all).\n */\nexport async function listAllTeamsTickets(): Promise<TicketSummary[]> {\n const teamIds = await discoverTeamIds();\n const all: TicketSummary[] = [];\n\n for (const teamId of teamIds) {\n all.push(...(await listTickets(teamId)));\n }\n\n all.sort((a, b) => (a.teamId === b.teamId ? a.number - b.number : a.teamId.localeCompare(b.teamId)));\n return all;\n}\n\n/**\n * Back-compat helper used by some API routes.\n */\nexport async function getTicketByIdOrNumber(ticketIdOrNumber: string, teamIdOrDir: string) {\n const tickets = await listTickets(teamIdOrDir);\n const normalized = ticketIdOrNumber.trim();\n\n const byNumber = normalized.match(/^\\d+$/) ? tickets.find((t) => t.number === Number(normalized)) : null;\n const byId = tickets.find((t) => t.id === normalized);\n return byId ?? byNumber ?? null;\n}\n\nexport async function resolveTicket(teamId: string, ticketIdOrNumber: string): Promise<TicketSummary | null> {\n return getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n}\n\n/**\n * getTicketMarkdown(teamId, ticketIdOrNumber)\n */\nexport async function getTicketMarkdown(\n teamId: string,\n ticketIdOrNumber: string,\n): Promise<{ teamId: string; id: string; file: string; markdown: string; owner: string | null; stage: TicketStage } | null> {\n const hit = await getTicketByIdOrNumber(ticketIdOrNumber, teamId);\n if (!hit) return null;\n\n return {\n teamId: hit.teamId,\n id: hit.id,\n file: hit.file,\n markdown: await fs.readFile(hit.file, \"utf8\"),\n owner: hit.owner,\n stage: hit.stage,\n };\n}\n","// This file is generated by next-core EcmascriptClientReferenceModule.\nimport { registerClientReference } from \"react-server-dom-turbopack/server\";\nexport const TicketsBoardClient = registerClientReference(\n function() { throw new Error(\"Attempted to call TicketsBoardClient() from the server but TicketsBoardClient is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.\"); },\n \"[project]/src/app/tickets/TicketsBoardClient.tsx\",\n \"TicketsBoardClient\",\n);\n","import { TicketsBoardClient } from \"@/app/tickets/TicketsBoardClient\";\nimport { listTickets } from \"@/lib/tickets\";\nimport { getWorkspaceDir } from \"@/lib/paths\";\n\nexport const dynamic = \"force-dynamic\";\n\nexport default async function TeamTicketsPage({\n params,\n}: {\n params: Promise<{ teamId: string }>;\n}) {\n const { teamId } = await params;\n const scope = teamId === \"main\" ? await getWorkspaceDir() : teamId;\n const tickets = await listTickets(scope);\n\n return (\n <TicketsBoardClient\n tickets={tickets}\n basePath={`/teams/${encodeURIComponent(teamId)}/tickets`}\n selectedTeamId={teamId}\n />\n );\n}\n"],"names":[],"mappings":"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","ignoreList":[2]}
@@ -1,3 +1,3 @@
1
- module.exports=[74621,a=>{"use strict";function b(a){return a instanceof Error?a.message:String(a)}a.s(["errorMessage",()=>b])},16627,a=>{"use strict";var b=a.i(87924),c=a.i(38246),d=a.i(50944),e=a.i(72131),f=a.i(61889),g=a.i(74621),h=a.i(51200);let i=new Set(["lead","dev","qa","tester","agent","system"]);function j({teamId:a,ticket:c,currentOwner:f}){let g=(0,d.useRouter)(),[h,i]=(0,e.useState)([]),[j,k]=(0,e.useState)(f??""),[l,m]=(0,e.useState)(!1),[n,o]=(0,e.useState)(null);(0,e.useEffect)(()=>{let b=!1;return(async()=>{try{let c=a?`/api/teams/${encodeURIComponent(a)}/tickets/assignees`:"/api/tickets/assignees",d=await fetch(c,{cache:"no-store"}),e=await d.json();if(b)return;i(Array.isArray(e.assignees)?e.assignees:[])}catch{if(b)return;i([])}})(),()=>{b=!0}},[]);let p=(0,e.useMemo)(()=>{let a=new Set(h);return f&&a.add(f),Array.from(a).sort()},[h,f]);async function q(){m(!0),o(null);try{let b=a?`/api/teams/${encodeURIComponent(a)}/tickets/assign`:"/api/tickets/assign",d=await fetch(b,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:c,assignee:j})}),e=await d.json();if(!d.ok)return void o(e.error||"Assign failed");g.refresh()}catch{o("Assign failed")}finally{m(!1)}}return(0,b.jsxs)("div",{className:"ck-glass p-4",children:[(0,b.jsxs)("div",{className:"flex flex-wrap items-center gap-3",children:[(0,b.jsx)("div",{className:"text-xs font-medium text-[color:var(--ck-text-tertiary)]",children:"Assignee"}),(0,b.jsxs)("select",{className:"rounded-md border border-white/10 bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)]",value:j,onChange:a=>k(a.target.value),children:[(0,b.jsx)("option",{value:"",disabled:!0,children:"Select…"}),p.map(a=>(0,b.jsx)("option",{value:a,children:a},a))]}),(0,b.jsx)("button",{type:"button",onClick:q,disabled:l||!j||j===(f??""),className:"rounded-md bg-emerald-600 px-3 py-2 text-sm font-semibold text-white hover:bg-emerald-500 disabled:cursor-not-allowed disabled:opacity-50",children:l?"Assigning…":"Assign"}),f?(0,b.jsxs)("div",{className:"text-xs text-[color:var(--ck-text-tertiary)]",children:["Currently: ",f]}):null]}),n?(0,b.jsx)("div",{className:"mt-2 text-xs text-red-300",children:n}):null]})}function k(a,b){return b||0==a.trim().length}function l(a){let l=(0,d.useRouter)(),m=(0,f.useToast)(),[n,o]=(0,e.useTransition)(),[p,q]=(0,e.useState)(null),[r,s]=(0,e.useState)(null),t=(0,e.useMemo)(()=>(function(a){let b=function(a){let b=/^## Comments\s*$/gim.exec(a);if(!b)return null;let c=b.index+b[0].length,d=a.slice(c).search(/^##\s+/m),e=-1===d?a.length:c+d;return{start:c,end:e}}(a);if(!b)return[];let c=a.slice(b.start,b.end).replace(/^\s+/,"").split("\n"),d=[],e=null,f=/^- \*\*(.+?)\*\*\s+—\s+(.*)$/;for(let a of c){let b=a.replace(/\r$/,""),c=b.match(f);if(c){e&&d.push({...e,body:e.body.trimEnd()});let a=c[1].trim(),b=c[2].trim(),f=b.indexOf(":"),g=(f>=0?b.slice(0,f):b).trim()||"unknown",h=f>=0?b.slice(f+1).trimStart():"",j=function(a){let b=a?.trim()||"unknown",c=b.match(/^(.*?)\s*\((.*?)\)\s*$/);if(c){let a=c[1].trim()||"unknown",d=c[2].trim();return{name:a,role:d&&d.toLowerCase()!=a.toLowerCase()?d:void 0,raw:b}}for(let a of[" — "," - "," | "]){if(!b.includes(a))continue;let[c,d]=b.split(a).map(a=>a.trim()).filter(Boolean);if(!c||!d)break;let e=i.has(c.toLowerCase()),f=i.has(d.toLowerCase());if(e&&!f)return{name:d,role:c,raw:b};return{name:c,role:d,raw:b}}return{name:b,raw:b}}(g);e={timestamp:a,authorName:j.name,authorRole:j.role,authorRaw:j.raw,body:h?h+"\n":""};continue}e&&(e.body+=b.replace(/^\s{0,2}/,"")+"\n")}return e&&d.push({...e,body:e.body.trimEnd()}),d})(a.markdown),[a.markdown]),[u,v]=(0,e.useState)(""),[w,x]=(0,e.useState)("");async function y(){q(null),await (0,h.fetchJson)(`/api/teams/${encodeURIComponent(a.teamId)}/tickets/move-to-goals`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:a.ticketId})})}async function z(){q(null),await (0,h.fetchJson)(`/api/teams/${encodeURIComponent(a.teamId)}/tickets/delete`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:a.ticketId})})}async function A(b){q(null),await (0,h.fetchJson)(`/api/teams/${encodeURIComponent(a.teamId)}/tickets/move`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:a.ticketId,to:b})})}async function B(){q(null);let b=w.trim();if(!b)throw Error("Comment cannot be empty.");await (0,h.fetchJson)(`/api/teams/${encodeURIComponent(a.teamId)}/tickets/comment`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:a.ticketId,author:u.trim()||void 0,comment:b})}),x(""),m.push({kind:"success",message:"Comment added."}),l.refresh()}return(0,b.jsxs)("div",{className:"space-y-4",children:[(0,b.jsxs)("div",{className:"flex items-center justify-between gap-3",children:[(0,b.jsx)(c.default,{href:a.backHref??"/tickets",className:"text-sm font-medium hover:underline",children:"← Back"}),(0,b.jsx)("span",{className:"text-xs text-[color:var(--ck-text-tertiary)]",children:a.file})]}),p?(0,b.jsx)("div",{className:"ck-glass border border-[color:var(--ck-border-strong)] p-3 text-sm text-[color:var(--ck-text-primary)]",children:p}):null,(0,b.jsxs)("div",{className:"ck-glass p-6",children:[(0,b.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[(0,b.jsx)("div",{className:"text-sm font-semibold text-[color:var(--ck-text-primary)]",children:"Ticket"}),(0,b.jsxs)("div",{className:"flex flex-wrap items-center justify-end gap-2",children:[(0,b.jsxs)("label",{className:"flex items-center gap-2 text-xs text-[color:var(--ck-text-secondary)]",children:[(0,b.jsx)("span",{children:"Status"}),(0,b.jsxs)("select",{className:"rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-2 py-1 text-xs text-[color:var(--ck-text-primary)]",disabled:n,value:a.stage,onChange:b=>{let c=b.target.value;if(c!==a.stage){if("done"===c&&!window.confirm("Mark this ticket done? This will move the file to work/done and add an audit comment."))return;o(()=>{A(c).then(()=>{m.push({kind:"success",message:`Status updated to ${c}.`}),l.refresh()}).catch(a=>q((0,g.errorMessage)(a)))})}},children:[(0,b.jsx)("option",{value:"backlog",children:"backlog"}),(0,b.jsx)("option",{value:"in-progress",children:"in-progress"}),(0,b.jsx)("option",{value:"testing",children:"testing"}),(0,b.jsx)("option",{value:"done",children:"done"})]})]}),(0,b.jsx)("button",{className:"rounded border border-[color:var(--ck-border-subtle)] px-3 py-1.5 text-xs font-medium text-[color:var(--ck-text-secondary)] hover:border-[color:var(--ck-border-strong)]",onClick:()=>s({kind:"goals"}),disabled:n,children:"Move to Goals"}),(0,b.jsx)("button",{className:"rounded border border-[color:var(--ck-accent-red)] bg-[color:var(--ck-accent-red-soft)] px-3 py-1.5 text-xs font-medium text-[color:var(--ck-accent-red)] hover:bg-[color:var(--ck-accent-red-soft-strong)]",onClick:()=>s({kind:"delete"}),disabled:n,children:"Delete Ticket"})]})]}),void 0!==a.currentOwner?(0,b.jsx)("div",{className:"mt-4",children:(0,b.jsx)(j,{teamId:a.teamId,ticket:a.ticketId,currentOwner:a.currentOwner??null})}):null,(0,b.jsxs)("div",{className:"mt-6 border-t border-[color:var(--ck-border-subtle)] pt-6",children:[(0,b.jsxs)("div",{className:"flex items-center justify-between gap-3",children:[(0,b.jsx)("div",{className:"text-sm font-semibold text-[color:var(--ck-text-primary)]",children:"Comments"}),(0,b.jsx)("div",{className:"text-xs text-[color:var(--ck-text-tertiary)]",children:t.length})]}),t.length?(0,b.jsx)("div",{className:"mt-3 space-y-3",children:t.map((a,c)=>(0,b.jsxs)("div",{className:"rounded-[var(--ck-radius-sm)] border border-[color:var(--ck-border-subtle)] bg-black/10 p-3",children:[(0,b.jsxs)("div",{className:"flex flex-wrap items-baseline justify-between gap-2",children:[(0,b.jsxs)("div",{className:"flex flex-wrap items-baseline gap-2",children:[(0,b.jsx)("div",{className:"text-xs font-medium text-[color:var(--ck-text-primary)]",children:a.authorName}),a.authorRole?(0,b.jsx)("div",{className:"rounded border border-[color:var(--ck-border-subtle)] bg-black/10 px-1.5 py-0.5 text-[10px] font-medium text-[color:var(--ck-text-tertiary)]",children:a.authorRole}):null]}),(0,b.jsx)("div",{className:"text-xs text-[color:var(--ck-text-tertiary)]",children:a.timestamp})]}),(0,b.jsx)("div",{className:"mt-2 whitespace-pre-wrap break-words text-sm leading-6 text-[color:var(--ck-text-secondary)]",children:a.body})]},`${a.timestamp}-${c}`))}):(0,b.jsx)("div",{className:"mt-3 text-sm text-[color:var(--ck-text-secondary)]",children:"No comments yet."}),(0,b.jsxs)("div",{className:"mt-4 border-t border-[color:var(--ck-border-subtle)] pt-4",children:[(0,b.jsx)("div",{className:"text-sm font-semibold text-[color:var(--ck-text-primary)]",children:"Add comment"}),(0,b.jsxs)("div",{className:"mt-3 grid gap-3",children:[(0,b.jsxs)("div",{className:"grid gap-1",children:[(0,b.jsx)("label",{className:"text-xs font-medium text-[color:var(--ck-text-secondary)]",htmlFor:"ck-comment-author",children:"Author (optional)"}),(0,b.jsx)("input",{id:"ck-comment-author",value:u,onChange:a=>v(a.target.value),placeholder:"unknown",className:"w-full rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)] outline-none focus:border-[color:var(--ck-border-strong)]",disabled:n})]}),(0,b.jsxs)("div",{className:"grid gap-1",children:[(0,b.jsx)("label",{className:"text-xs font-medium text-[color:var(--ck-text-secondary)]",htmlFor:"ck-comment-body",children:"Comment"}),(0,b.jsx)("textarea",{id:"ck-comment-body",value:w,onChange:a=>x(a.target.value),rows:4,className:"w-full rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)] outline-none focus:border-[color:var(--ck-border-strong)]",disabled:n})]}),(0,b.jsx)("div",{className:"flex items-center justify-end",children:(0,b.jsx)("button",{type:"button","aria-busy":n,className:"rounded-[var(--ck-radius-sm)] bg-[color:var(--ck-accent)] px-3 py-2 text-xs font-semibold text-black transition hover:brightness-110 active:brightness-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--ck-border-strong)] focus-visible:ring-offset-2 focus-visible:ring-offset-black/30 disabled:cursor-not-allowed disabled:border disabled:border-[color:var(--ck-border-subtle)] disabled:bg-[color:var(--ck-bg-glass)] disabled:text-[color:var(--ck-text-tertiary)]",onClick:()=>{o(()=>{B().catch(a=>q((0,g.errorMessage)(a)))})},disabled:k(w,n),children:n?"Posting…":"Post comment"})})]})]})]})]}),(0,b.jsx)("div",{className:"ck-glass p-6",children:(0,b.jsx)("pre",{className:"whitespace-pre-wrap break-words text-sm leading-6 text-[color:var(--ck-text-primary)]",children:a.markdown})}),r?(0,b.jsx)("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4",children:(0,b.jsxs)("div",{className:"ck-glass w-full max-w-lg rounded-[var(--ck-radius-md)] border border-[color:var(--ck-border-strong)] p-4",children:[(0,b.jsx)("div",{className:"text-sm font-semibold text-[color:var(--ck-text-primary)]",children:"goals"===r.kind?"Move to Goals":"Delete ticket"}),(0,b.jsx)("div",{className:"mt-2 text-sm text-[color:var(--ck-text-secondary)]",children:"goals"===r.kind?(0,b.jsxs)(b.Fragment,{children:["This will move the ticket out of the work lanes into ",(0,b.jsx)("code",{children:"work/goals/"})," so it won’t be picked up by automation."]}):(0,b.jsx)(b.Fragment,{children:"This will permanently remove the ticket markdown file. Assignment stubs (if any) will be archived."})}),(0,b.jsxs)("div",{className:"mt-4 flex items-center justify-end gap-2",children:[(0,b.jsx)("button",{className:"rounded border border-[color:var(--ck-border-subtle)] px-3 py-1.5 text-xs text-[color:var(--ck-text-secondary)]",onClick:()=>s(null),disabled:n,children:"Cancel"}),(0,b.jsx)("button",{className:"delete"===r.kind?"rounded bg-[color:var(--ck-accent-red)] px-3 py-1.5 text-xs font-medium text-white":"rounded bg-[color:var(--ck-accent)] px-3 py-1.5 text-xs font-medium text-black",onClick:()=>{let b=r.kind;s(null),o(()=>{("goals"===b?y():z()).then(()=>{m.push({kind:"success",message:"delete"===b?"Ticket deleted.":"Moved to Goals."}),"delete"===b?l.push(a.backHref??"/tickets"):l.refresh()}).catch(a=>q((0,g.errorMessage)(a)))})},disabled:n,children:"Confirm"})]})]})}):null]})}a.s(["TicketDetailClient",()=>l,"isPostCommentDisabled",()=>k],16627)}];
1
+ module.exports=[74621,a=>{"use strict";function b(a){return a instanceof Error?a.message:String(a)}a.s(["errorMessage",()=>b])},16627,a=>{"use strict";var b=a.i(87924),c=a.i(38246),d=a.i(50944),e=a.i(72131),f=a.i(61889),g=a.i(74621),h=a.i(51200);let i=new Set(["lead","dev","qa","tester","agent","system"]);function j({teamId:a,ticket:c,currentOwner:f}){let g=(0,d.useRouter)(),[h,i]=(0,e.useState)([]),[j,k]=(0,e.useState)(f??""),[l,m]=(0,e.useState)(!1),[n,o]=(0,e.useState)(null);(0,e.useEffect)(()=>{let b=!1;return(async()=>{try{let c=a?`/api/teams/${encodeURIComponent(a)}/tickets/assignees`:"/api/tickets/assignees",d=await fetch(c,{cache:"no-store"}),e=await d.json();if(b)return;i(Array.isArray(e.assignees)?e.assignees:[])}catch{if(b)return;i([])}})(),()=>{b=!0}},[]);let p=(0,e.useMemo)(()=>{let a=new Set(h);return f&&a.add(f),Array.from(a).sort()},[h,f]);async function q(){m(!0),o(null);try{let b=a?`/api/teams/${encodeURIComponent(a)}/tickets/assign`:"/api/tickets/assign",d=await fetch(b,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:c,assignee:j,...a?{teamId:a}:{}})}),e=await d.json();if(!d.ok)return void o(e.error||"Assign failed");g.refresh()}catch{o("Assign failed")}finally{m(!1)}}return(0,b.jsxs)("div",{className:"ck-glass p-4",children:[(0,b.jsxs)("div",{className:"flex flex-wrap items-center gap-3",children:[(0,b.jsx)("div",{className:"text-xs font-medium text-[color:var(--ck-text-tertiary)]",children:"Assignee"}),(0,b.jsxs)("select",{className:"rounded-md border border-white/10 bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)]",value:j,onChange:a=>k(a.target.value),children:[(0,b.jsx)("option",{value:"",disabled:!0,children:"Select…"}),p.map(a=>(0,b.jsx)("option",{value:a,children:a},a))]}),(0,b.jsx)("button",{type:"button",onClick:q,disabled:l||!j||j===(f??""),className:"rounded-md bg-emerald-600 px-3 py-2 text-sm font-semibold text-white hover:bg-emerald-500 disabled:cursor-not-allowed disabled:opacity-50",children:l?"Assigning…":"Assign"}),f?(0,b.jsxs)("div",{className:"text-xs text-[color:var(--ck-text-tertiary)]",children:["Currently: ",f]}):null]}),n?(0,b.jsx)("div",{className:"mt-2 text-xs text-red-300",children:n}):null]})}function k(a,b){return b||0==a.trim().length}function l(a){let l=(0,d.useRouter)(),m=(0,f.useToast)(),[n,o]=(0,e.useTransition)(),[p,q]=(0,e.useState)(null),[r,s]=(0,e.useState)(null),t=(0,e.useMemo)(()=>(function(a){let b=function(a){let b=/^## Comments\s*$/gim.exec(a);if(!b)return null;let c=b.index+b[0].length,d=a.slice(c).search(/^##\s+/m),e=-1===d?a.length:c+d;return{start:c,end:e}}(a);if(!b)return[];let c=a.slice(b.start,b.end).replace(/^\s+/,"").split("\n"),d=[],e=null,f=/^- \*\*(.+?)\*\*\s+—\s+(.*)$/;for(let a of c){let b=a.replace(/\r$/,""),c=b.match(f);if(c){e&&d.push({...e,body:e.body.trimEnd()});let a=c[1].trim(),b=c[2].trim(),f=b.indexOf(":"),g=(f>=0?b.slice(0,f):b).trim()||"unknown",h=f>=0?b.slice(f+1).trimStart():"",j=function(a){let b=a?.trim()||"unknown",c=b.match(/^(.*?)\s*\((.*?)\)\s*$/);if(c){let a=c[1].trim()||"unknown",d=c[2].trim();return{name:a,role:d&&d.toLowerCase()!=a.toLowerCase()?d:void 0,raw:b}}for(let a of[" — "," - "," | "]){if(!b.includes(a))continue;let[c,d]=b.split(a).map(a=>a.trim()).filter(Boolean);if(!c||!d)break;let e=i.has(c.toLowerCase()),f=i.has(d.toLowerCase());if(e&&!f)return{name:d,role:c,raw:b};return{name:c,role:d,raw:b}}return{name:b,raw:b}}(g);e={timestamp:a,authorName:j.name,authorRole:j.role,authorRaw:j.raw,body:h?h+"\n":""};continue}e&&(e.body+=b.replace(/^\s{0,2}/,"")+"\n")}return e&&d.push({...e,body:e.body.trimEnd()}),d})(a.markdown),[a.markdown]),[u,v]=(0,e.useState)(""),[w,x]=(0,e.useState)("");async function y(){q(null),await (0,h.fetchJson)(`/api/teams/${encodeURIComponent(a.teamId)}/tickets/move-to-goals`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:a.ticketId})})}async function z(){q(null),await (0,h.fetchJson)(`/api/teams/${encodeURIComponent(a.teamId)}/tickets/delete`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:a.ticketId})})}async function A(b){q(null),await (0,h.fetchJson)(`/api/teams/${encodeURIComponent(a.teamId)}/tickets/move`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:a.ticketId,to:b})})}async function B(){q(null);let b=w.trim();if(!b)throw Error("Comment cannot be empty.");await (0,h.fetchJson)(`/api/teams/${encodeURIComponent(a.teamId)}/tickets/comment`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:a.ticketId,author:u.trim()||void 0,comment:b})}),x(""),m.push({kind:"success",message:"Comment added."}),l.refresh()}return(0,b.jsxs)("div",{className:"space-y-4",children:[(0,b.jsxs)("div",{className:"flex items-center justify-between gap-3",children:[(0,b.jsx)(c.default,{href:a.backHref??"/tickets",className:"text-sm font-medium hover:underline",children:"← Back"}),(0,b.jsx)("span",{className:"text-xs text-[color:var(--ck-text-tertiary)]",children:a.file})]}),p?(0,b.jsx)("div",{className:"ck-glass border border-[color:var(--ck-border-strong)] p-3 text-sm text-[color:var(--ck-text-primary)]",children:p}):null,(0,b.jsxs)("div",{className:"ck-glass p-6",children:[(0,b.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[(0,b.jsx)("div",{className:"text-sm font-semibold text-[color:var(--ck-text-primary)]",children:"Ticket"}),(0,b.jsxs)("div",{className:"flex flex-wrap items-center justify-end gap-2",children:[(0,b.jsxs)("label",{className:"flex items-center gap-2 text-xs text-[color:var(--ck-text-secondary)]",children:[(0,b.jsx)("span",{children:"Status"}),(0,b.jsxs)("select",{className:"rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-2 py-1 text-xs text-[color:var(--ck-text-primary)]",disabled:n,value:a.stage,onChange:b=>{let c=b.target.value;if(c!==a.stage){if("done"===c&&!window.confirm("Mark this ticket done? This will move the file to work/done and add an audit comment."))return;o(()=>{A(c).then(()=>{m.push({kind:"success",message:`Status updated to ${c}.`}),l.refresh()}).catch(a=>q((0,g.errorMessage)(a)))})}},children:[(0,b.jsx)("option",{value:"backlog",children:"backlog"}),(0,b.jsx)("option",{value:"in-progress",children:"in-progress"}),(0,b.jsx)("option",{value:"testing",children:"testing"}),(0,b.jsx)("option",{value:"done",children:"done"})]})]}),(0,b.jsx)("button",{className:"rounded border border-[color:var(--ck-border-subtle)] px-3 py-1.5 text-xs font-medium text-[color:var(--ck-text-secondary)] hover:border-[color:var(--ck-border-strong)]",onClick:()=>s({kind:"goals"}),disabled:n,children:"Move to Goals"}),(0,b.jsx)("button",{className:"rounded border border-[color:var(--ck-accent-red)] bg-[color:var(--ck-accent-red-soft)] px-3 py-1.5 text-xs font-medium text-[color:var(--ck-accent-red)] hover:bg-[color:var(--ck-accent-red-soft-strong)]",onClick:()=>s({kind:"delete"}),disabled:n,children:"Delete Ticket"})]})]}),void 0!==a.currentOwner?(0,b.jsx)("div",{className:"mt-4",children:(0,b.jsx)(j,{teamId:a.teamId,ticket:a.ticketId,currentOwner:a.currentOwner??null})}):null,(0,b.jsxs)("div",{className:"mt-6 border-t border-[color:var(--ck-border-subtle)] pt-6",children:[(0,b.jsxs)("div",{className:"flex items-center justify-between gap-3",children:[(0,b.jsx)("div",{className:"text-sm font-semibold text-[color:var(--ck-text-primary)]",children:"Comments"}),(0,b.jsx)("div",{className:"text-xs text-[color:var(--ck-text-tertiary)]",children:t.length})]}),t.length?(0,b.jsx)("div",{className:"mt-3 space-y-3",children:t.map((a,c)=>(0,b.jsxs)("div",{className:"rounded-[var(--ck-radius-sm)] border border-[color:var(--ck-border-subtle)] bg-black/10 p-3",children:[(0,b.jsxs)("div",{className:"flex flex-wrap items-baseline justify-between gap-2",children:[(0,b.jsxs)("div",{className:"flex flex-wrap items-baseline gap-2",children:[(0,b.jsx)("div",{className:"text-xs font-medium text-[color:var(--ck-text-primary)]",children:a.authorName}),a.authorRole?(0,b.jsx)("div",{className:"rounded border border-[color:var(--ck-border-subtle)] bg-black/10 px-1.5 py-0.5 text-[10px] font-medium text-[color:var(--ck-text-tertiary)]",children:a.authorRole}):null]}),(0,b.jsx)("div",{className:"text-xs text-[color:var(--ck-text-tertiary)]",children:a.timestamp})]}),(0,b.jsx)("div",{className:"mt-2 whitespace-pre-wrap break-words text-sm leading-6 text-[color:var(--ck-text-secondary)]",children:a.body})]},`${a.timestamp}-${c}`))}):(0,b.jsx)("div",{className:"mt-3 text-sm text-[color:var(--ck-text-secondary)]",children:"No comments yet."}),(0,b.jsxs)("div",{className:"mt-4 border-t border-[color:var(--ck-border-subtle)] pt-4",children:[(0,b.jsx)("div",{className:"text-sm font-semibold text-[color:var(--ck-text-primary)]",children:"Add comment"}),(0,b.jsxs)("div",{className:"mt-3 grid gap-3",children:[(0,b.jsxs)("div",{className:"grid gap-1",children:[(0,b.jsx)("label",{className:"text-xs font-medium text-[color:var(--ck-text-secondary)]",htmlFor:"ck-comment-author",children:"Author (optional)"}),(0,b.jsx)("input",{id:"ck-comment-author",value:u,onChange:a=>v(a.target.value),placeholder:"unknown",className:"w-full rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)] outline-none focus:border-[color:var(--ck-border-strong)]",disabled:n})]}),(0,b.jsxs)("div",{className:"grid gap-1",children:[(0,b.jsx)("label",{className:"text-xs font-medium text-[color:var(--ck-text-secondary)]",htmlFor:"ck-comment-body",children:"Comment"}),(0,b.jsx)("textarea",{id:"ck-comment-body",value:w,onChange:a=>x(a.target.value),rows:4,className:"w-full rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)] outline-none focus:border-[color:var(--ck-border-strong)]",disabled:n})]}),(0,b.jsx)("div",{className:"flex items-center justify-end",children:(0,b.jsx)("button",{type:"button","aria-busy":n,className:"rounded-[var(--ck-radius-sm)] bg-[color:var(--ck-accent)] px-3 py-2 text-xs font-semibold text-black transition hover:brightness-110 active:brightness-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--ck-border-strong)] focus-visible:ring-offset-2 focus-visible:ring-offset-black/30 disabled:cursor-not-allowed disabled:border disabled:border-[color:var(--ck-border-subtle)] disabled:bg-[color:var(--ck-bg-glass)] disabled:text-[color:var(--ck-text-tertiary)]",onClick:()=>{o(()=>{B().catch(a=>q((0,g.errorMessage)(a)))})},disabled:k(w,n),children:n?"Posting…":"Post comment"})})]})]})]})]}),(0,b.jsx)("div",{className:"ck-glass p-6",children:(0,b.jsx)("pre",{className:"whitespace-pre-wrap break-words text-sm leading-6 text-[color:var(--ck-text-primary)]",children:a.markdown})}),r?(0,b.jsx)("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4",children:(0,b.jsxs)("div",{className:"ck-glass w-full max-w-lg rounded-[var(--ck-radius-md)] border border-[color:var(--ck-border-strong)] p-4",children:[(0,b.jsx)("div",{className:"text-sm font-semibold text-[color:var(--ck-text-primary)]",children:"goals"===r.kind?"Move to Goals":"Delete ticket"}),(0,b.jsx)("div",{className:"mt-2 text-sm text-[color:var(--ck-text-secondary)]",children:"goals"===r.kind?(0,b.jsxs)(b.Fragment,{children:["This will move the ticket out of the work lanes into ",(0,b.jsx)("code",{children:"work/goals/"})," so it won’t be picked up by automation."]}):(0,b.jsx)(b.Fragment,{children:"This will permanently remove the ticket markdown file. Assignment stubs (if any) will be archived."})}),(0,b.jsxs)("div",{className:"mt-4 flex items-center justify-end gap-2",children:[(0,b.jsx)("button",{className:"rounded border border-[color:var(--ck-border-subtle)] px-3 py-1.5 text-xs text-[color:var(--ck-text-secondary)]",onClick:()=>s(null),disabled:n,children:"Cancel"}),(0,b.jsx)("button",{className:"delete"===r.kind?"rounded bg-[color:var(--ck-accent-red)] px-3 py-1.5 text-xs font-medium text-white":"rounded bg-[color:var(--ck-accent)] px-3 py-1.5 text-xs font-medium text-black",onClick:()=>{let b=r.kind;s(null),o(()=>{("goals"===b?y():z()).then(()=>{m.push({kind:"success",message:"delete"===b?"Ticket deleted.":"Moved to Goals."}),"delete"===b?l.push(a.backHref??"/tickets"):l.refresh()}).catch(a=>q((0,g.errorMessage)(a)))})},disabled:n,children:"Confirm"})]})]})}):null]})}a.s(["TicketDetailClient",()=>l,"isPostCommentDisabled",()=>k],16627)}];
2
2
 
3
3
  //# sourceMappingURL=src_59477309._.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/lib/errors.ts","../../../../src/app/tickets/TicketDetailClient.tsx","../../../../src/lib/ticket-comments.ts","../../../../src/app/tickets/%5Bticket%5D/TicketAssignControl.tsx"],"sourcesContent":["/**\n * Extracts a string message from an unknown error value.\n * Used consistently across API routes and client components.\n */\nexport function errorMessage(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n","\"use client\";\n\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport { useMemo, useState, useTransition } from \"react\";\n\nimport { useToast } from \"@/components/ToastProvider\";\nimport { errorMessage } from \"@/lib/errors\";\nimport { fetchJson } from \"@/lib/fetch-json\";\nimport type { TicketStage } from \"@/lib/tickets\";\nimport { parseTicketComments } from \"@/lib/ticket-comments\";\nimport { TicketAssignControl } from \"@/app/tickets/[ticket]/TicketAssignControl\";\n\nexport function isPostCommentDisabled(commentBody: string, isPending: boolean) {\n return isPending || commentBody.trim().length == 0;\n}\n\nexport function TicketDetailClient(props: {\n teamId: string;\n ticketId: string;\n file: string;\n markdown: string;\n stage: TicketStage;\n backHref?: string;\n currentOwner?: string | null;\n}) {\n const router = useRouter();\n const toast = useToast();\n const [isPending, startTransition] = useTransition();\n const [error, setError] = useState<string | null>(null);\n const [confirm, setConfirm] = useState<null | { kind: \"goals\" | \"delete\" }>(null);\n\n const comments = useMemo(() => parseTicketComments(props.markdown), [props.markdown]);\n\n const [commentAuthor, setCommentAuthor] = useState<string>(\"\");\n const [commentBody, setCommentBody] = useState<string>(\"\");\n\n async function moveToGoals() {\n setError(null);\n await fetchJson(`/api/teams/${encodeURIComponent(props.teamId)}/tickets/move-to-goals`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ ticket: props.ticketId }),\n });\n }\n\n async function deleteTicket() {\n setError(null);\n await fetchJson(`/api/teams/${encodeURIComponent(props.teamId)}/tickets/delete`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ ticket: props.ticketId }),\n });\n }\n\n async function moveTicket(to: TicketStage) {\n setError(null);\n await fetchJson(`/api/teams/${encodeURIComponent(props.teamId)}/tickets/move`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ ticket: props.ticketId, to }),\n });\n }\n\n async function submitComment() {\n setError(null);\n const body = commentBody.trim();\n if (!body) throw new Error(\"Comment cannot be empty.\");\n\n await fetchJson(`/api/teams/${encodeURIComponent(props.teamId)}/tickets/comment`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n ticket: props.ticketId,\n author: commentAuthor.trim() || undefined,\n comment: body,\n }),\n });\n\n setCommentBody(\"\");\n toast.push({ kind: \"success\", message: \"Comment added.\" });\n router.refresh();\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <Link href={props.backHref ?? \"/tickets\"} className=\"text-sm font-medium hover:underline\">\n ← Back\n </Link>\n <span className=\"text-xs text-[color:var(--ck-text-tertiary)]\">{props.file}</span>\n </div>\n\n {error ? (\n <div className=\"ck-glass border border-[color:var(--ck-border-strong)] p-3 text-sm text-[color:var(--ck-text-primary)]\">\n {error}\n </div>\n ) : null}\n\n <div className=\"ck-glass p-6\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"text-sm font-semibold text-[color:var(--ck-text-primary)]\">Ticket</div>\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <label className=\"flex items-center gap-2 text-xs text-[color:var(--ck-text-secondary)]\">\n <span>Status</span>\n <select\n className=\"rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-2 py-1 text-xs text-[color:var(--ck-text-primary)]\"\n disabled={isPending}\n value={props.stage}\n onChange={(e) => {\n const next = e.target.value as TicketStage;\n if (next === props.stage) return;\n\n if (next === \"done\") {\n const ok = window.confirm(\n \"Mark this ticket done? This will move the file to work/done and add an audit comment.\",\n );\n if (!ok) return;\n }\n\n startTransition(() => {\n moveTicket(next)\n .then(() => {\n toast.push({ kind: \"success\", message: `Status updated to ${next}.` });\n router.refresh();\n })\n .catch((err: unknown) => setError(errorMessage(err)));\n });\n }}\n >\n <option value=\"backlog\">backlog</option>\n <option value=\"in-progress\">in-progress</option>\n <option value=\"testing\">testing</option>\n <option value=\"done\">done</option>\n </select>\n </label>\n\n <button\n className=\"rounded border border-[color:var(--ck-border-subtle)] px-3 py-1.5 text-xs font-medium text-[color:var(--ck-text-secondary)] hover:border-[color:var(--ck-border-strong)]\"\n onClick={() => setConfirm({ kind: \"goals\" })}\n disabled={isPending}\n >\n Move to Goals\n </button>\n <button\n className=\"rounded border border-[color:var(--ck-accent-red)] bg-[color:var(--ck-accent-red-soft)] px-3 py-1.5 text-xs font-medium text-[color:var(--ck-accent-red)] hover:bg-[color:var(--ck-accent-red-soft-strong)]\"\n onClick={() => setConfirm({ kind: \"delete\" })}\n disabled={isPending}\n >\n Delete Ticket\n </button>\n </div>\n </div>\n\n {props.currentOwner !== undefined ? (\n <div className=\"mt-4\">\n <TicketAssignControl teamId={props.teamId} ticket={props.ticketId} currentOwner={props.currentOwner ?? null} />\n </div>\n ) : null}\n\n <div className=\"mt-6 border-t border-[color:var(--ck-border-subtle)] pt-6\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"text-sm font-semibold text-[color:var(--ck-text-primary)]\">Comments</div>\n <div className=\"text-xs text-[color:var(--ck-text-tertiary)]\">{comments.length}</div>\n </div>\n\n {comments.length ? (\n <div className=\"mt-3 space-y-3\">\n {comments.map((c, idx) => (\n <div\n key={`${c.timestamp}-${idx}`}\n className=\"rounded-[var(--ck-radius-sm)] border border-[color:var(--ck-border-subtle)] bg-black/10 p-3\"\n >\n <div className=\"flex flex-wrap items-baseline justify-between gap-2\">\n <div className=\"flex flex-wrap items-baseline gap-2\">\n <div className=\"text-xs font-medium text-[color:var(--ck-text-primary)]\">{c.authorName}</div>\n {c.authorRole ? (\n <div className=\"rounded border border-[color:var(--ck-border-subtle)] bg-black/10 px-1.5 py-0.5 text-[10px] font-medium text-[color:var(--ck-text-tertiary)]\">\n {c.authorRole}\n </div>\n ) : null}\n </div>\n <div className=\"text-xs text-[color:var(--ck-text-tertiary)]\">{c.timestamp}</div>\n </div>\n <div className=\"mt-2 whitespace-pre-wrap break-words text-sm leading-6 text-[color:var(--ck-text-secondary)]\">\n {c.body}\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className=\"mt-3 text-sm text-[color:var(--ck-text-secondary)]\">No comments yet.</div>\n )}\n\n <div className=\"mt-4 border-t border-[color:var(--ck-border-subtle)] pt-4\">\n <div className=\"text-sm font-semibold text-[color:var(--ck-text-primary)]\">Add comment</div>\n\n <div className=\"mt-3 grid gap-3\">\n <div className=\"grid gap-1\">\n <label className=\"text-xs font-medium text-[color:var(--ck-text-secondary)]\" htmlFor=\"ck-comment-author\">\n Author (optional)\n </label>\n <input\n id=\"ck-comment-author\"\n value={commentAuthor}\n onChange={(e) => setCommentAuthor(e.target.value)}\n placeholder=\"unknown\"\n className=\"w-full rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)] outline-none focus:border-[color:var(--ck-border-strong)]\"\n disabled={isPending}\n />\n </div>\n\n <div className=\"grid gap-1\">\n <label className=\"text-xs font-medium text-[color:var(--ck-text-secondary)]\" htmlFor=\"ck-comment-body\">\n Comment\n </label>\n <textarea\n id=\"ck-comment-body\"\n value={commentBody}\n onChange={(e) => setCommentBody(e.target.value)}\n rows={4}\n className=\"w-full rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)] outline-none focus:border-[color:var(--ck-border-strong)]\"\n disabled={isPending}\n />\n </div>\n\n <div className=\"flex items-center justify-end\">\n <button\n type=\"button\"\n aria-busy={isPending}\n className={\n \"rounded-[var(--ck-radius-sm)] bg-[color:var(--ck-accent)] px-3 py-2 text-xs font-semibold text-black transition \" +\n \"hover:brightness-110 active:brightness-95 \" +\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--ck-border-strong)] focus-visible:ring-offset-2 focus-visible:ring-offset-black/30 \" +\n \"disabled:cursor-not-allowed disabled:border disabled:border-[color:var(--ck-border-subtle)] disabled:bg-[color:var(--ck-bg-glass)] disabled:text-[color:var(--ck-text-tertiary)]\"\n }\n onClick={() => {\n startTransition(() => {\n submitComment().catch((e: unknown) => setError(errorMessage(e)));\n });\n }}\n disabled={isPostCommentDisabled(commentBody, isPending)}\n >\n {isPending ? \"Posting…\" : \"Post comment\"}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"ck-glass p-6\">\n <pre className=\"whitespace-pre-wrap break-words text-sm leading-6 text-[color:var(--ck-text-primary)]\">\n {props.markdown}\n </pre>\n </div>\n\n {confirm ? (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4\">\n <div className=\"ck-glass w-full max-w-lg rounded-[var(--ck-radius-md)] border border-[color:var(--ck-border-strong)] p-4\">\n <div className=\"text-sm font-semibold text-[color:var(--ck-text-primary)]\">\n {confirm.kind === \"goals\" ? \"Move to Goals\" : \"Delete ticket\"}\n </div>\n\n <div className=\"mt-2 text-sm text-[color:var(--ck-text-secondary)]\">\n {confirm.kind === \"goals\" ? (\n <>\n This will move the ticket out of the work lanes into <code>work/goals/</code> so it won’t be picked up by\n automation.\n </>\n ) : (\n <>This will permanently remove the ticket markdown file. Assignment stubs (if any) will be archived.</>\n )}\n </div>\n\n <div className=\"mt-4 flex items-center justify-end gap-2\">\n <button\n className=\"rounded border border-[color:var(--ck-border-subtle)] px-3 py-1.5 text-xs text-[color:var(--ck-text-secondary)]\"\n onClick={() => setConfirm(null)}\n disabled={isPending}\n >\n Cancel\n </button>\n <button\n className={\n confirm.kind === \"delete\"\n ? \"rounded bg-[color:var(--ck-accent-red)] px-3 py-1.5 text-xs font-medium text-white\"\n : \"rounded bg-[color:var(--ck-accent)] px-3 py-1.5 text-xs font-medium text-black\"\n }\n onClick={() => {\n const kind = confirm.kind;\n setConfirm(null);\n startTransition(() => {\n const op = kind === \"goals\" ? moveToGoals() : deleteTicket();\n op\n .then(() => {\n toast.push({\n kind: \"success\",\n message: kind === \"delete\" ? \"Ticket deleted.\" : \"Moved to Goals.\",\n });\n if (kind === \"delete\") {\n router.push(props.backHref ?? \"/tickets\");\n } else {\n router.refresh();\n }\n })\n .catch((e: unknown) => setError(errorMessage(e)));\n });\n }}\n disabled={isPending}\n >\n Confirm\n </button>\n </div>\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n","export type TicketComment = {\n timestamp: string;\n authorName: string;\n authorRole?: string;\n authorRaw: string;\n body: string;\n};\n\nfunction findCommentsSection(md: string): { start: number; end: number } | null {\n const headerRe = /^## Comments\\s*$/gim;\n const m = headerRe.exec(md);\n if (!m) return null;\n\n const start = m.index + m[0].length;\n\n // Find next heading of same or higher level.\n const rest = md.slice(start);\n const nextHeading = rest.search(/^##\\s+/m);\n const end = nextHeading === -1 ? md.length : start + nextHeading;\n return { start, end };\n}\n\nconst ROLE_HINTS = new Set([\"lead\", \"dev\", \"qa\", \"tester\", \"agent\", \"system\"]);\n\nfunction parseAuthor(rawAuthor: string): { name: string; role?: string; raw: string } {\n const raw = rawAuthor?.trim() || \"\";\n const normalized = raw || \"unknown\";\n\n // Pattern: \"RJ (lead)\"\n const paren = normalized.match(/^(.*?)\\s*\\((.*?)\\)\\s*$/);\n if (paren) {\n const name = paren[1].trim() || \"unknown\";\n const role = paren[2].trim();\n return { name, role: role && role.toLowerCase() != name.toLowerCase() ? role : undefined, raw: normalized };\n }\n\n // Pattern: \"lead — RJ\" / \"RJ — lead\" / \"lead | RJ\" / \"RJ | lead\"\n for (const sep of [\" — \", \" - \", \" | \"] as const) {\n if (!normalized.includes(sep)) continue;\n const [a, b] = normalized.split(sep).map((s) => s.trim()).filter(Boolean);\n if (!a || !b) break;\n\n const aIsRole = ROLE_HINTS.has(a.toLowerCase());\n const bIsRole = ROLE_HINTS.has(b.toLowerCase());\n\n if (aIsRole && !bIsRole) return { name: b, role: a, raw: normalized };\n if (bIsRole && !aIsRole) return { name: a, role: b, raw: normalized };\n\n // Otherwise, treat the left as the primary author label and right as secondary detail.\n return { name: a, role: b, raw: normalized };\n }\n\n return { name: normalized, raw: normalized };\n}\n\nexport function parseTicketComments(md: string): TicketComment[] {\n const section = findCommentsSection(md);\n if (!section) return [];\n\n const body = md.slice(section.start, section.end).replace(/^\\s+/, \"\");\n const lines = body.split(\"\\n\");\n\n const out: TicketComment[] = [];\n let current: TicketComment | null = null;\n\n const header = /^- \\*\\*(.+?)\\*\\*\\s+—\\s+(.*)$/;\n\n for (const rawLine of lines) {\n const line = rawLine.replace(/\\r$/, \"\");\n const m = line.match(header);\n if (m) {\n if (current) out.push({ ...current, body: current.body.trimEnd() });\n const timestamp = m[1].trim();\n const rest = m[2].trim();\n // Support either \"author: first line\" or just \"author\".\n const colonIdx = rest.indexOf(\":\");\n const authorRaw = (colonIdx >= 0 ? rest.slice(0, colonIdx) : rest).trim() || \"unknown\";\n const firstBody = colonIdx >= 0 ? rest.slice(colonIdx + 1).trimStart() : \"\";\n const author = parseAuthor(authorRaw);\n current = {\n timestamp,\n authorName: author.name,\n authorRole: author.role,\n authorRaw: author.raw,\n body: firstBody ? firstBody + \"\\n\" : \"\",\n };\n continue;\n }\n\n if (!current) continue;\n\n // Allow list paragraph indentation, but keep it simple.\n current.body += line.replace(/^\\s{0,2}/, \"\") + \"\\n\";\n }\n\n if (current) out.push({ ...current, body: current.body.trimEnd() });\n return out;\n}\n\nexport function formatTicketCommentMarkdown(args: { timestamp: string; author: string; body: string }): string {\n const body = args.body.trim().replace(/\\r\\n/g, \"\\n\");\n const indented = body\n .split(\"\\n\")\n .map((l) => ` ${l}`)\n .join(\"\\n\");\n\n const author = args.author?.trim() || \"unknown\";\n return `\\n- **${args.timestamp}** — ${author}:\\n${indented}\\n`;\n}\n","\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useRouter } from \"next/navigation\";\n\nexport function TicketAssignControl({\n teamId,\n ticket,\n currentOwner,\n}: {\n teamId?: string | null;\n ticket: string;\n currentOwner: string | null;\n}) {\n const router = useRouter();\n const [assignees, setAssignees] = useState<string[]>([]);\n const [selected, setSelected] = useState<string>(currentOwner ?? \"\");\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n (async () => {\n try {\n const url = teamId\n ? `/api/teams/${encodeURIComponent(teamId)}/tickets/assignees`\n : \"/api/tickets/assignees\";\n const res = await fetch(url, { cache: \"no-store\" });\n const json = (await res.json()) as { assignees?: string[] };\n if (cancelled) return;\n setAssignees(Array.isArray(json.assignees) ? json.assignees : []);\n } catch {\n if (cancelled) return;\n setAssignees([]);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, []);\n\n const options = useMemo(() => {\n const base = new Set(assignees);\n if (currentOwner) base.add(currentOwner);\n return Array.from(base).sort();\n }, [assignees, currentOwner]);\n\n async function onAssign() {\n setLoading(true);\n setError(null);\n try {\n const url = teamId\n ? `/api/teams/${encodeURIComponent(teamId)}/tickets/assign`\n : \"/api/tickets/assign\";\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ ticket, assignee: selected }),\n });\n\n const json = (await res.json()) as { error?: string };\n if (!res.ok) {\n setError(json.error || \"Assign failed\");\n return;\n }\n\n router.refresh();\n } catch {\n setError(\"Assign failed\");\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div className=\"ck-glass p-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"text-xs font-medium text-[color:var(--ck-text-tertiary)]\">Assignee</div>\n <select\n className=\"rounded-md border border-white/10 bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)]\"\n value={selected}\n onChange={(e) => setSelected(e.target.value)}\n >\n <option value=\"\" disabled>\n Select…\n </option>\n {options.map((a) => (\n <option key={a} value={a}>\n {a}\n </option>\n ))}\n </select>\n\n <button\n type=\"button\"\n onClick={onAssign}\n disabled={loading || !selected || selected === (currentOwner ?? \"\")}\n className=\"rounded-md bg-emerald-600 px-3 py-2 text-sm font-semibold text-white hover:bg-emerald-500 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {loading ? \"Assigning…\" : \"Assign\"}\n </button>\n\n {currentOwner ? (\n <div className=\"text-xs text-[color:var(--ck-text-tertiary)]\">Currently: {currentOwner}</div>\n ) : null}\n </div>\n\n {error ? <div className=\"mt-2 text-xs text-red-300\">{error}</div> : null}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/lib/errors.ts","../../../../src/app/tickets/TicketDetailClient.tsx","../../../../src/lib/ticket-comments.ts","../../../../src/app/tickets/%5Bticket%5D/TicketAssignControl.tsx"],"sourcesContent":["/**\n * Extracts a string message from an unknown error value.\n * Used consistently across API routes and client components.\n */\nexport function errorMessage(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n","\"use client\";\n\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport { useMemo, useState, useTransition } from \"react\";\n\nimport { useToast } from \"@/components/ToastProvider\";\nimport { errorMessage } from \"@/lib/errors\";\nimport { fetchJson } from \"@/lib/fetch-json\";\nimport type { TicketStage } from \"@/lib/tickets\";\nimport { parseTicketComments } from \"@/lib/ticket-comments\";\nimport { TicketAssignControl } from \"@/app/tickets/[ticket]/TicketAssignControl\";\n\nexport function isPostCommentDisabled(commentBody: string, isPending: boolean) {\n return isPending || commentBody.trim().length == 0;\n}\n\nexport function TicketDetailClient(props: {\n teamId: string;\n ticketId: string;\n file: string;\n markdown: string;\n stage: TicketStage;\n backHref?: string;\n currentOwner?: string | null;\n}) {\n const router = useRouter();\n const toast = useToast();\n const [isPending, startTransition] = useTransition();\n const [error, setError] = useState<string | null>(null);\n const [confirm, setConfirm] = useState<null | { kind: \"goals\" | \"delete\" }>(null);\n\n const comments = useMemo(() => parseTicketComments(props.markdown), [props.markdown]);\n\n const [commentAuthor, setCommentAuthor] = useState<string>(\"\");\n const [commentBody, setCommentBody] = useState<string>(\"\");\n\n async function moveToGoals() {\n setError(null);\n await fetchJson(`/api/teams/${encodeURIComponent(props.teamId)}/tickets/move-to-goals`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ ticket: props.ticketId }),\n });\n }\n\n async function deleteTicket() {\n setError(null);\n await fetchJson(`/api/teams/${encodeURIComponent(props.teamId)}/tickets/delete`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ ticket: props.ticketId }),\n });\n }\n\n async function moveTicket(to: TicketStage) {\n setError(null);\n await fetchJson(`/api/teams/${encodeURIComponent(props.teamId)}/tickets/move`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ ticket: props.ticketId, to }),\n });\n }\n\n async function submitComment() {\n setError(null);\n const body = commentBody.trim();\n if (!body) throw new Error(\"Comment cannot be empty.\");\n\n await fetchJson(`/api/teams/${encodeURIComponent(props.teamId)}/tickets/comment`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n ticket: props.ticketId,\n author: commentAuthor.trim() || undefined,\n comment: body,\n }),\n });\n\n setCommentBody(\"\");\n toast.push({ kind: \"success\", message: \"Comment added.\" });\n router.refresh();\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <Link href={props.backHref ?? \"/tickets\"} className=\"text-sm font-medium hover:underline\">\n ← Back\n </Link>\n <span className=\"text-xs text-[color:var(--ck-text-tertiary)]\">{props.file}</span>\n </div>\n\n {error ? (\n <div className=\"ck-glass border border-[color:var(--ck-border-strong)] p-3 text-sm text-[color:var(--ck-text-primary)]\">\n {error}\n </div>\n ) : null}\n\n <div className=\"ck-glass p-6\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"text-sm font-semibold text-[color:var(--ck-text-primary)]\">Ticket</div>\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <label className=\"flex items-center gap-2 text-xs text-[color:var(--ck-text-secondary)]\">\n <span>Status</span>\n <select\n className=\"rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-2 py-1 text-xs text-[color:var(--ck-text-primary)]\"\n disabled={isPending}\n value={props.stage}\n onChange={(e) => {\n const next = e.target.value as TicketStage;\n if (next === props.stage) return;\n\n if (next === \"done\") {\n const ok = window.confirm(\n \"Mark this ticket done? This will move the file to work/done and add an audit comment.\",\n );\n if (!ok) return;\n }\n\n startTransition(() => {\n moveTicket(next)\n .then(() => {\n toast.push({ kind: \"success\", message: `Status updated to ${next}.` });\n router.refresh();\n })\n .catch((err: unknown) => setError(errorMessage(err)));\n });\n }}\n >\n <option value=\"backlog\">backlog</option>\n <option value=\"in-progress\">in-progress</option>\n <option value=\"testing\">testing</option>\n <option value=\"done\">done</option>\n </select>\n </label>\n\n <button\n className=\"rounded border border-[color:var(--ck-border-subtle)] px-3 py-1.5 text-xs font-medium text-[color:var(--ck-text-secondary)] hover:border-[color:var(--ck-border-strong)]\"\n onClick={() => setConfirm({ kind: \"goals\" })}\n disabled={isPending}\n >\n Move to Goals\n </button>\n <button\n className=\"rounded border border-[color:var(--ck-accent-red)] bg-[color:var(--ck-accent-red-soft)] px-3 py-1.5 text-xs font-medium text-[color:var(--ck-accent-red)] hover:bg-[color:var(--ck-accent-red-soft-strong)]\"\n onClick={() => setConfirm({ kind: \"delete\" })}\n disabled={isPending}\n >\n Delete Ticket\n </button>\n </div>\n </div>\n\n {props.currentOwner !== undefined ? (\n <div className=\"mt-4\">\n <TicketAssignControl teamId={props.teamId} ticket={props.ticketId} currentOwner={props.currentOwner ?? null} />\n </div>\n ) : null}\n\n <div className=\"mt-6 border-t border-[color:var(--ck-border-subtle)] pt-6\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"text-sm font-semibold text-[color:var(--ck-text-primary)]\">Comments</div>\n <div className=\"text-xs text-[color:var(--ck-text-tertiary)]\">{comments.length}</div>\n </div>\n\n {comments.length ? (\n <div className=\"mt-3 space-y-3\">\n {comments.map((c, idx) => (\n <div\n key={`${c.timestamp}-${idx}`}\n className=\"rounded-[var(--ck-radius-sm)] border border-[color:var(--ck-border-subtle)] bg-black/10 p-3\"\n >\n <div className=\"flex flex-wrap items-baseline justify-between gap-2\">\n <div className=\"flex flex-wrap items-baseline gap-2\">\n <div className=\"text-xs font-medium text-[color:var(--ck-text-primary)]\">{c.authorName}</div>\n {c.authorRole ? (\n <div className=\"rounded border border-[color:var(--ck-border-subtle)] bg-black/10 px-1.5 py-0.5 text-[10px] font-medium text-[color:var(--ck-text-tertiary)]\">\n {c.authorRole}\n </div>\n ) : null}\n </div>\n <div className=\"text-xs text-[color:var(--ck-text-tertiary)]\">{c.timestamp}</div>\n </div>\n <div className=\"mt-2 whitespace-pre-wrap break-words text-sm leading-6 text-[color:var(--ck-text-secondary)]\">\n {c.body}\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className=\"mt-3 text-sm text-[color:var(--ck-text-secondary)]\">No comments yet.</div>\n )}\n\n <div className=\"mt-4 border-t border-[color:var(--ck-border-subtle)] pt-4\">\n <div className=\"text-sm font-semibold text-[color:var(--ck-text-primary)]\">Add comment</div>\n\n <div className=\"mt-3 grid gap-3\">\n <div className=\"grid gap-1\">\n <label className=\"text-xs font-medium text-[color:var(--ck-text-secondary)]\" htmlFor=\"ck-comment-author\">\n Author (optional)\n </label>\n <input\n id=\"ck-comment-author\"\n value={commentAuthor}\n onChange={(e) => setCommentAuthor(e.target.value)}\n placeholder=\"unknown\"\n className=\"w-full rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)] outline-none focus:border-[color:var(--ck-border-strong)]\"\n disabled={isPending}\n />\n </div>\n\n <div className=\"grid gap-1\">\n <label className=\"text-xs font-medium text-[color:var(--ck-text-secondary)]\" htmlFor=\"ck-comment-body\">\n Comment\n </label>\n <textarea\n id=\"ck-comment-body\"\n value={commentBody}\n onChange={(e) => setCommentBody(e.target.value)}\n rows={4}\n className=\"w-full rounded border border-[color:var(--ck-border-subtle)] bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)] outline-none focus:border-[color:var(--ck-border-strong)]\"\n disabled={isPending}\n />\n </div>\n\n <div className=\"flex items-center justify-end\">\n <button\n type=\"button\"\n aria-busy={isPending}\n className={\n \"rounded-[var(--ck-radius-sm)] bg-[color:var(--ck-accent)] px-3 py-2 text-xs font-semibold text-black transition \" +\n \"hover:brightness-110 active:brightness-95 \" +\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--ck-border-strong)] focus-visible:ring-offset-2 focus-visible:ring-offset-black/30 \" +\n \"disabled:cursor-not-allowed disabled:border disabled:border-[color:var(--ck-border-subtle)] disabled:bg-[color:var(--ck-bg-glass)] disabled:text-[color:var(--ck-text-tertiary)]\"\n }\n onClick={() => {\n startTransition(() => {\n submitComment().catch((e: unknown) => setError(errorMessage(e)));\n });\n }}\n disabled={isPostCommentDisabled(commentBody, isPending)}\n >\n {isPending ? \"Posting…\" : \"Post comment\"}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"ck-glass p-6\">\n <pre className=\"whitespace-pre-wrap break-words text-sm leading-6 text-[color:var(--ck-text-primary)]\">\n {props.markdown}\n </pre>\n </div>\n\n {confirm ? (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4\">\n <div className=\"ck-glass w-full max-w-lg rounded-[var(--ck-radius-md)] border border-[color:var(--ck-border-strong)] p-4\">\n <div className=\"text-sm font-semibold text-[color:var(--ck-text-primary)]\">\n {confirm.kind === \"goals\" ? \"Move to Goals\" : \"Delete ticket\"}\n </div>\n\n <div className=\"mt-2 text-sm text-[color:var(--ck-text-secondary)]\">\n {confirm.kind === \"goals\" ? (\n <>\n This will move the ticket out of the work lanes into <code>work/goals/</code> so it won’t be picked up by\n automation.\n </>\n ) : (\n <>This will permanently remove the ticket markdown file. Assignment stubs (if any) will be archived.</>\n )}\n </div>\n\n <div className=\"mt-4 flex items-center justify-end gap-2\">\n <button\n className=\"rounded border border-[color:var(--ck-border-subtle)] px-3 py-1.5 text-xs text-[color:var(--ck-text-secondary)]\"\n onClick={() => setConfirm(null)}\n disabled={isPending}\n >\n Cancel\n </button>\n <button\n className={\n confirm.kind === \"delete\"\n ? \"rounded bg-[color:var(--ck-accent-red)] px-3 py-1.5 text-xs font-medium text-white\"\n : \"rounded bg-[color:var(--ck-accent)] px-3 py-1.5 text-xs font-medium text-black\"\n }\n onClick={() => {\n const kind = confirm.kind;\n setConfirm(null);\n startTransition(() => {\n const op = kind === \"goals\" ? moveToGoals() : deleteTicket();\n op\n .then(() => {\n toast.push({\n kind: \"success\",\n message: kind === \"delete\" ? \"Ticket deleted.\" : \"Moved to Goals.\",\n });\n if (kind === \"delete\") {\n router.push(props.backHref ?? \"/tickets\");\n } else {\n router.refresh();\n }\n })\n .catch((e: unknown) => setError(errorMessage(e)));\n });\n }}\n disabled={isPending}\n >\n Confirm\n </button>\n </div>\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n","export type TicketComment = {\n timestamp: string;\n authorName: string;\n authorRole?: string;\n authorRaw: string;\n body: string;\n};\n\nfunction findCommentsSection(md: string): { start: number; end: number } | null {\n const headerRe = /^## Comments\\s*$/gim;\n const m = headerRe.exec(md);\n if (!m) return null;\n\n const start = m.index + m[0].length;\n\n // Find next heading of same or higher level.\n const rest = md.slice(start);\n const nextHeading = rest.search(/^##\\s+/m);\n const end = nextHeading === -1 ? md.length : start + nextHeading;\n return { start, end };\n}\n\nconst ROLE_HINTS = new Set([\"lead\", \"dev\", \"qa\", \"tester\", \"agent\", \"system\"]);\n\nfunction parseAuthor(rawAuthor: string): { name: string; role?: string; raw: string } {\n const raw = rawAuthor?.trim() || \"\";\n const normalized = raw || \"unknown\";\n\n // Pattern: \"RJ (lead)\"\n const paren = normalized.match(/^(.*?)\\s*\\((.*?)\\)\\s*$/);\n if (paren) {\n const name = paren[1].trim() || \"unknown\";\n const role = paren[2].trim();\n return { name, role: role && role.toLowerCase() != name.toLowerCase() ? role : undefined, raw: normalized };\n }\n\n // Pattern: \"lead — RJ\" / \"RJ — lead\" / \"lead | RJ\" / \"RJ | lead\"\n for (const sep of [\" — \", \" - \", \" | \"] as const) {\n if (!normalized.includes(sep)) continue;\n const [a, b] = normalized.split(sep).map((s) => s.trim()).filter(Boolean);\n if (!a || !b) break;\n\n const aIsRole = ROLE_HINTS.has(a.toLowerCase());\n const bIsRole = ROLE_HINTS.has(b.toLowerCase());\n\n if (aIsRole && !bIsRole) return { name: b, role: a, raw: normalized };\n if (bIsRole && !aIsRole) return { name: a, role: b, raw: normalized };\n\n // Otherwise, treat the left as the primary author label and right as secondary detail.\n return { name: a, role: b, raw: normalized };\n }\n\n return { name: normalized, raw: normalized };\n}\n\nexport function parseTicketComments(md: string): TicketComment[] {\n const section = findCommentsSection(md);\n if (!section) return [];\n\n const body = md.slice(section.start, section.end).replace(/^\\s+/, \"\");\n const lines = body.split(\"\\n\");\n\n const out: TicketComment[] = [];\n let current: TicketComment | null = null;\n\n const header = /^- \\*\\*(.+?)\\*\\*\\s+—\\s+(.*)$/;\n\n for (const rawLine of lines) {\n const line = rawLine.replace(/\\r$/, \"\");\n const m = line.match(header);\n if (m) {\n if (current) out.push({ ...current, body: current.body.trimEnd() });\n const timestamp = m[1].trim();\n const rest = m[2].trim();\n // Support either \"author: first line\" or just \"author\".\n const colonIdx = rest.indexOf(\":\");\n const authorRaw = (colonIdx >= 0 ? rest.slice(0, colonIdx) : rest).trim() || \"unknown\";\n const firstBody = colonIdx >= 0 ? rest.slice(colonIdx + 1).trimStart() : \"\";\n const author = parseAuthor(authorRaw);\n current = {\n timestamp,\n authorName: author.name,\n authorRole: author.role,\n authorRaw: author.raw,\n body: firstBody ? firstBody + \"\\n\" : \"\",\n };\n continue;\n }\n\n if (!current) continue;\n\n // Allow list paragraph indentation, but keep it simple.\n current.body += line.replace(/^\\s{0,2}/, \"\") + \"\\n\";\n }\n\n if (current) out.push({ ...current, body: current.body.trimEnd() });\n return out;\n}\n\nexport function formatTicketCommentMarkdown(args: { timestamp: string; author: string; body: string }): string {\n const body = args.body.trim().replace(/\\r\\n/g, \"\\n\");\n const indented = body\n .split(\"\\n\")\n .map((l) => ` ${l}`)\n .join(\"\\n\");\n\n const author = args.author?.trim() || \"unknown\";\n return `\\n- **${args.timestamp}** — ${author}:\\n${indented}\\n`;\n}\n","\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useRouter } from \"next/navigation\";\n\nexport function TicketAssignControl({\n teamId,\n ticket,\n currentOwner,\n}: {\n teamId?: string | null;\n ticket: string;\n currentOwner: string | null;\n}) {\n const router = useRouter();\n const [assignees, setAssignees] = useState<string[]>([]);\n const [selected, setSelected] = useState<string>(currentOwner ?? \"\");\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n (async () => {\n try {\n const url = teamId\n ? `/api/teams/${encodeURIComponent(teamId)}/tickets/assignees`\n : \"/api/tickets/assignees\";\n const res = await fetch(url, { cache: \"no-store\" });\n const json = (await res.json()) as { assignees?: string[] };\n if (cancelled) return;\n setAssignees(Array.isArray(json.assignees) ? json.assignees : []);\n } catch {\n if (cancelled) return;\n setAssignees([]);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, []);\n\n const options = useMemo(() => {\n const base = new Set(assignees);\n if (currentOwner) base.add(currentOwner);\n return Array.from(base).sort();\n }, [assignees, currentOwner]);\n\n async function onAssign() {\n setLoading(true);\n setError(null);\n try {\n const url = teamId\n ? `/api/teams/${encodeURIComponent(teamId)}/tickets/assign`\n : \"/api/tickets/assign\";\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ ticket, assignee: selected, ...(teamId ? { teamId } : {}) }),\n });\n\n const json = (await res.json()) as { error?: string };\n if (!res.ok) {\n setError(json.error || \"Assign failed\");\n return;\n }\n\n router.refresh();\n } catch {\n setError(\"Assign failed\");\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div className=\"ck-glass p-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"text-xs font-medium text-[color:var(--ck-text-tertiary)]\">Assignee</div>\n <select\n className=\"rounded-md border border-white/10 bg-black/20 px-3 py-2 text-sm text-[color:var(--ck-text-primary)]\"\n value={selected}\n onChange={(e) => setSelected(e.target.value)}\n >\n <option value=\"\" disabled>\n Select…\n </option>\n {options.map((a) => (\n <option key={a} value={a}>\n {a}\n </option>\n ))}\n </select>\n\n <button\n type=\"button\"\n onClick={onAssign}\n disabled={loading || !selected || selected === (currentOwner ?? \"\")}\n className=\"rounded-md bg-emerald-600 px-3 py-2 text-sm font-semibold text-white hover:bg-emerald-500 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {loading ? \"Assigning…\" : \"Assign\"}\n </button>\n\n {currentOwner ? (\n <div className=\"text-xs text-[color:var(--ck-text-tertiary)]\">Currently: {currentOwner}</div>\n ) : null}\n </div>\n\n {error ? <div className=\"mt-2 text-xs text-red-300\">{error}</div> : null}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}