@umudik/task-bridge 0.0.2 → 0.0.3
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.
- package/apps/backend/dist/db/workflow-template-db.js +89 -442
- package/apps/backend/dist/domain/workflow-template-id.js +1 -1
- package/apps/backend/dist/services/workflow-template-service.js +1 -1
- package/apps/backend/public/assets/{index-Bl1ciVpY.js → index-NY9anVyw.js} +1 -1
- package/apps/backend/public/index.html +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { DEFAULT_WORKFLOW_TEMPLATE_ID } from "../domain/workflow-template-id.js"
|
|
|
3
3
|
import { serializeTaskTemplates } from "../domain/workflow-stage.js";
|
|
4
4
|
import { getProjectsDb } from "./projects-db.js";
|
|
5
5
|
const DEPRECATED_TEMPLATE_IDS = [
|
|
6
|
+
"ai-sdlc",
|
|
6
7
|
"go",
|
|
7
8
|
"nodejs",
|
|
8
9
|
"sdlc-classic",
|
|
@@ -26,25 +27,9 @@ function task(id, title, description = "", assigneeRole = "", children = [], dep
|
|
|
26
27
|
children,
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
|
-
function chain(id, title, description, assigneeRole, steps) {
|
|
30
|
-
let previousId = null;
|
|
31
|
-
const children = steps.map((step) => {
|
|
32
|
-
let dependsOn;
|
|
33
|
-
if (previousId !== null) {
|
|
34
|
-
dependsOn = [previousId];
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
dependsOn = [];
|
|
38
|
-
}
|
|
39
|
-
const node = Object.assign({}, step, { dependsOn });
|
|
40
|
-
previousId = step.id;
|
|
41
|
-
return node;
|
|
42
|
-
});
|
|
43
|
-
return task(id, title, description, assigneeRole, children);
|
|
44
|
-
}
|
|
45
30
|
const DEFAULT_TEMPLATE_SEEDS = [
|
|
46
31
|
{
|
|
47
|
-
id:
|
|
32
|
+
id: "empty",
|
|
48
33
|
title: "Empty workflow",
|
|
49
34
|
description: "Minimal pipeline with one step. Customize stages on the Pipeline tab.",
|
|
50
35
|
stages: [
|
|
@@ -61,146 +46,50 @@ const DEFAULT_TEMPLATE_SEEDS = [
|
|
|
61
46
|
],
|
|
62
47
|
},
|
|
63
48
|
{
|
|
64
|
-
id:
|
|
65
|
-
title: "
|
|
66
|
-
description: `
|
|
49
|
+
id: DEFAULT_WORKFLOW_TEMPLATE_ID,
|
|
50
|
+
title: "Lean SDLC",
|
|
51
|
+
description: `Lean, classic software delivery pipeline.
|
|
67
52
|
|
|
68
|
-
|
|
53
|
+
Research -> Define -> Design -> Implement -> Review -> Verify -> Done.
|
|
69
54
|
|
|
70
|
-
|
|
55
|
+
No branches, no PRs, no AI gates: build the change, then review the diff directly. Customize stages on the Pipeline tab.`,
|
|
71
56
|
stages: [
|
|
72
57
|
{
|
|
73
|
-
id: "
|
|
74
|
-
title: "
|
|
58
|
+
id: "research",
|
|
59
|
+
title: "Research",
|
|
75
60
|
description: `## Objective
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
## Entry
|
|
79
|
-
New epic or greenfield feature.
|
|
61
|
+
Learn from prior art before committing to a solution.
|
|
80
62
|
|
|
81
63
|
## Exit
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
Tech lead confirms constitution is enforceable — not aspirational markdown.`,
|
|
86
|
-
purpose: "Project governance",
|
|
87
|
-
rules: ["AGENTS.md exists", "Testing policy defined", "Security baseline stated"],
|
|
64
|
+
You know how others solved this, with concrete references, examples, and known pitfalls.`,
|
|
65
|
+
purpose: "Prior art",
|
|
66
|
+
rules: ["Similar solutions reviewed", "References collected", "Pitfalls noted"],
|
|
88
67
|
position: 0,
|
|
89
68
|
autoAssign: false,
|
|
90
69
|
taskTemplates: [
|
|
91
|
-
task("
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
]),
|
|
70
|
+
task("rs-similar", "Find how others solved this", "**Output:** short notes on existing approaches.\n\n- Search the web, forums, and repos for similar work\n- Capture 2-3 approaches and their trade-offs"),
|
|
71
|
+
task("rs-examples", "Collect code examples", "**Output:** a list of reference snippets and libraries.\n\n- Gather relevant examples and reusable libraries\n- Note license and fit"),
|
|
72
|
+
task("rs-docs", "Gather official docs and references", "**Output:** links to authoritative docs.\n\n- Official docs, specs, and API references for the tools involved"),
|
|
73
|
+
task("rs-opinions", "Collect community opinions and pitfalls", "**Output:** list of gotchas.\n\n- Issues, threads, and comments from people who built this\n- What broke for them and what they would do differently"),
|
|
96
74
|
],
|
|
97
75
|
},
|
|
98
76
|
{
|
|
99
|
-
id: "
|
|
100
|
-
title: "
|
|
77
|
+
id: "define",
|
|
78
|
+
title: "Define",
|
|
101
79
|
description: `## Objective
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
## Entry
|
|
105
|
-
Constitution in place.
|
|
80
|
+
Turn the request into a clear, testable spec.
|
|
106
81
|
|
|
107
82
|
## Exit
|
|
108
|
-
Problem
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
Evidence that building this is worth the engineering cost.`,
|
|
112
|
-
purpose: "Problem-solution fit",
|
|
113
|
-
rules: ["Problem statement written", "Stakeholders listed", "Out of scope explicit"],
|
|
83
|
+
Problem, acceptance criteria, and scope are written down.`,
|
|
84
|
+
purpose: "Spec",
|
|
85
|
+
rules: ["Problem statement written", "Acceptance criteria defined", "Scope explicit"],
|
|
114
86
|
position: 1,
|
|
115
87
|
autoAssign: false,
|
|
116
88
|
taskTemplates: [
|
|
117
|
-
task("
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
task("dc-assumptions", "Capture assumptions", "**Output:** ASM-* files\n\nBeliefs not yet validated. Flag highest-risk assumptions for research.", "product"),
|
|
122
|
-
task("dc-research", "Research alternatives", "**Output:** research.md (Specorator Stage 2)\n\n- ≥2 alternatives explored\n- Build vs buy vs integrate\n- Risks named with severity", "architect"),
|
|
123
|
-
task("dc-scope-out", "Define out of scope", "**Output:** explicit non-goals in idea.md\n\nPrevents agent scope creep during implementation.", "product"),
|
|
124
|
-
],
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
id: "specification",
|
|
128
|
-
title: "Specification",
|
|
129
|
-
description: `## Objective
|
|
130
|
-
Write the authoritative spec — code is derived from this, not from chat context (Thoughtworks SDD, Jama spec-anchored).
|
|
131
|
-
|
|
132
|
-
## Entry
|
|
133
|
-
Discovery complete.
|
|
134
|
-
|
|
135
|
-
## Exit
|
|
136
|
-
User stories, REQ-* in EARS notation, acceptance criteria, BDD scenarios, traceability matrix.
|
|
137
|
-
|
|
138
|
-
## Gate
|
|
139
|
-
Every requirement is testable; no vague "should work well" language.`,
|
|
140
|
-
purpose: "Intent capture",
|
|
141
|
-
rules: ["EARS or Given/When/Then AC", "REQ IDs stable", "Traceability GOAL→US→REQ"],
|
|
142
|
-
position: 2,
|
|
143
|
-
autoAssign: true,
|
|
144
|
-
taskTemplates: [
|
|
145
|
-
task("sp-stories", "Write user stories", "**Output:** US-* files\n\nFormat: As a [role], I want [action], so that [benefit]. Each story links to a GOAL-*.", "product"),
|
|
146
|
-
task("sp-functional", "Functional requirements (EARS)", "**Output:** REQ-F-* files\n\nUse EARS patterns: Ubiquitous, Event-driven, State-driven, Unwanted behaviour. Stable IDs for traceability.", "product"),
|
|
147
|
-
task("sp-nonfunctional", "Non-functional requirements", "**Output:** REQ-NF-* files\n\nPerformance, availability, security, observability, accessibility targets with measurable thresholds.", "architect"),
|
|
148
|
-
task("sp-acceptance", "Acceptance criteria", "**Output:** AC per user story\n\nGiven/When/Then or checklist items a QA can execute without guessing.", "product"),
|
|
149
|
-
task("sp-bdd", "BDD / Gherkin scenarios", "**Output:** .feature files or equivalent\n\nHappy path + error paths. Backend: API contract scenarios. Frontend: user-visible behaviour.", "qa"),
|
|
150
|
-
task("sp-traceability", "Traceability matrix", "**Output:** RTM draft linking GOAL → US → REQ → future tests\n\nRequired for regulated or audit-sensitive work.", "product"),
|
|
151
|
-
task("sp-edge-cases", "Edge cases & error catalogue", "**Output:** enumerated edge cases\n\nEmpty states, timeouts, permissions denied, rate limits, partial failures.", "qa"),
|
|
152
|
-
task("sp-review-checklist", "Spec review checklist", "**Output:** completed Spec Kit Review & Acceptance Checklist\n\nMark pass/fail per item before human gate.", "tech-lead"),
|
|
153
|
-
],
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
id: "clarify",
|
|
157
|
-
title: "Clarify",
|
|
158
|
-
description: `## Objective
|
|
159
|
-
Surface ambiguities before design — mandatory Spec Kit step before planning (/speckit.clarify).
|
|
160
|
-
|
|
161
|
-
## Entry
|
|
162
|
-
Draft specification exists.
|
|
163
|
-
|
|
164
|
-
## Exit
|
|
165
|
-
Clarifications section added; open questions resolved or explicitly deferred with owner.
|
|
166
|
-
|
|
167
|
-
## Gate
|
|
168
|
-
No TBD items blocking design. Agent cannot proceed with guessed requirements.`,
|
|
169
|
-
purpose: "Ambiguity removal",
|
|
170
|
-
rules: ["Clarifications documented", "No blocking TBDs", "Deferred items have owner"],
|
|
171
|
-
position: 3,
|
|
172
|
-
autoAssign: true,
|
|
173
|
-
taskTemplates: [
|
|
174
|
-
task("cl-scan", "Scan spec for ambiguity", "**Action:** structured pass over all spec artifacts.\n\nFlag vague adjectives, missing error behaviour, undefined actors, implicit dependencies.", "agent"),
|
|
175
|
-
task("cl-questions", "Generate clarification questions", "**Output:** numbered questions for human\n\nPrioritize by risk: security > data > UX > nice-to-have.", "agent"),
|
|
176
|
-
task("cl-answers", "Record human answers", "**Output:** Clarifications section in spec\n\nEach Q→A dated and attributed. Update affected REQ-* IDs.", "product"),
|
|
177
|
-
task("cl-consistency", "Consistency pass", "**Action:** ensure clarifications don't contradict GOAL-* or CON-* constraints.", "tech-lead"),
|
|
178
|
-
],
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
id: "spec-approval",
|
|
182
|
-
title: "Spec Approval",
|
|
183
|
-
description: `## Objective
|
|
184
|
-
**Human gate 1** — formal approval before any architecture or code (SDDD mandatory validation checkpoint).
|
|
185
|
-
|
|
186
|
-
## Entry
|
|
187
|
-
Specification + Clarify complete.
|
|
188
|
-
|
|
189
|
-
## Exit
|
|
190
|
-
Spec status: Draft → **Approved**. Baseline tagged or versioned.
|
|
191
|
-
|
|
192
|
-
## Gate
|
|
193
|
-
Product + tech lead sign-off. **No design work until this gate passes.**`,
|
|
194
|
-
purpose: "Human gate — spec",
|
|
195
|
-
rules: ["Human sign-off recorded", "Spec frozen", "No code before approval"],
|
|
196
|
-
position: 4,
|
|
197
|
-
autoAssign: true,
|
|
198
|
-
taskTemplates: [
|
|
199
|
-
chain("sa-product-review", "Product review", "**Action:** product owner reads all US-* and REQ-* artifacts.\n\nConfirm spec matches original intent. Reject if scope drift detected.", "product", [
|
|
200
|
-
task("sa-tech-review", "Technical feasibility review", "**Action:** tech lead confirms spec is implementable within constraints.\n\nFlag impossible NFRs or missing infra dependencies.", "tech-lead"),
|
|
201
|
-
task("sa-ambiguity-final", "Final ambiguity sweep", "**Action:** remove remaining vague language agents could misinterpret.\n\nReplace 'fast', 'secure', 'user-friendly' with measurable criteria.", "tech-lead"),
|
|
202
|
-
task("sa-approve", "Approve specification", "**Output:** Approved status on all spec artifacts + approval record (date, approver).\n\nThis unlocks Design stage.", "tech-lead"),
|
|
203
|
-
task("sa-freeze", "Freeze spec baseline", "**Output:** git tag or spec version number\n\nImplementation must trace to this baseline. Changes require spec amendment + re-approval.", "tech-lead"),
|
|
89
|
+
task("df-problem", "Write problem statement", "**Output:** one paragraph on who has the problem and why it matters.", "", [
|
|
90
|
+
task("df-criteria", "Define acceptance criteria", "**Output:** checklist of conditions that mean done."),
|
|
91
|
+
task("df-scope", "Set scope and out-of-scope", "**Output:** what is in and what is explicitly out."),
|
|
92
|
+
task("df-priority", "Prioritize and size", "**Output:** rough size and priority versus other work."),
|
|
204
93
|
]),
|
|
205
94
|
],
|
|
206
95
|
},
|
|
@@ -208,347 +97,105 @@ Product + tech lead sign-off. **No design work until this gate passes.**`,
|
|
|
208
97
|
id: "design",
|
|
209
98
|
title: "Design",
|
|
210
99
|
description: `## Objective
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
## Entry
|
|
214
|
-
Spec Approval gate passed.
|
|
215
|
-
|
|
216
|
-
## Exit
|
|
217
|
-
architecture.md, data-model.md, api-design.md, ADRs for irreversible decisions.
|
|
218
|
-
|
|
219
|
-
## Gate
|
|
220
|
-
Architect + tech lead review. Constitution compliance verified.`,
|
|
221
|
-
purpose: "Technical blueprint",
|
|
222
|
-
rules: ["Architecture doc complete", "ADRs for key decisions", "Constitution compliant"],
|
|
223
|
-
position: 5,
|
|
224
|
-
autoAssign: true,
|
|
225
|
-
taskTemplates: [
|
|
226
|
-
task("ds-architecture", "System architecture", "**Output:** architecture.md\n\nComponents, boundaries, data flow, sync/async patterns, failure modes.", "architect"),
|
|
227
|
-
task("ds-data-model", "Data model", "**Output:** data-model.md\n\nEntities, schemas, migrations, indexes, retention, PII handling.", "architect"),
|
|
228
|
-
task("ds-api", "API design", "**Output:** api-design.md or OpenAPI draft\n\nEndpoints, auth, errors, idempotency, versioning.", "architect"),
|
|
229
|
-
task("ds-security", "Security & threat model", "**Output:** security section or threat-model.md\n\nSTRIDE-lite: spoofing, tampering, elevation, data exposure.", "architect"),
|
|
230
|
-
task("ds-adr", "Write ADRs", "**Output:** DEC-* files\n\nContext, options, decision, consequences. Required for irreversible choices.", "architect"),
|
|
231
|
-
task("ds-components", "Component boundaries", "**Output:** component list for agent decomposition\n\nEach component: inputs, outputs, owner, test boundary.", "architect"),
|
|
232
|
-
task("ds-nfr-map", "Map NFRs to design", "**Output:** table linking REQ-NF-* to architecture elements\n\nProves non-functionals are designed, not hoped for.", "architect"),
|
|
233
|
-
],
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
id: "plan-approval",
|
|
237
|
-
title: "Plan Approval",
|
|
238
|
-
description: `## Objective
|
|
239
|
-
**Human gate 2** — architects approve technical plan before task breakdown (Spec Kit: validate plan before tasks).
|
|
240
|
-
|
|
241
|
-
## Entry
|
|
242
|
-
Design artifacts complete.
|
|
243
|
-
|
|
244
|
-
## Exit
|
|
245
|
-
Plan status: Approved. Constitution + spec alignment confirmed.
|
|
246
|
-
|
|
247
|
-
## Gate
|
|
248
|
-
**Never jump spec → code.** Plan must be reviewed before decomposition.`,
|
|
249
|
-
purpose: "Human gate — plan",
|
|
250
|
-
rules: ["Architect sign-off", "Plan matches spec", "No tasks before approval"],
|
|
251
|
-
position: 6,
|
|
252
|
-
autoAssign: true,
|
|
253
|
-
taskTemplates: [
|
|
254
|
-
chain("pa-architect-review", "Architect review", "**Action:** review architecture.md against approved REQ-* set.\n\nVerify no spec requirements are orphaned.", "architect", [
|
|
255
|
-
task("pa-constitution-check", "Constitution compliance", "**Action:** verify plan obeys AGENTS.md rules.\n\nFramework choices, testing approach, security patterns.", "tech-lead"),
|
|
256
|
-
task("pa-risk-review", "Risk & trade-off review", "**Action:** review ADRs and flagged risks.\n\nAccept or mitigate before tasks are written.", "tech-lead"),
|
|
257
|
-
task("pa-approve", "Approve technical plan", "**Output:** Approved status on design artifacts.\n\nUnlocks Tasks stage.", "tech-lead"),
|
|
258
|
-
]),
|
|
259
|
-
],
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
id: "tasks",
|
|
263
|
-
title: "Tasks",
|
|
264
|
-
description: `## Objective
|
|
265
|
-
Decompose plan into atomic agent-executable packets (Spec Kit /speckit.tasks, Specorator Stage 6).
|
|
266
|
-
|
|
267
|
-
## Entry
|
|
268
|
-
Plan Approval gate passed.
|
|
100
|
+
Decide how to build it before writing code.
|
|
269
101
|
|
|
270
102
|
## Exit
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
rules: ["Tasks atomic", "Dependencies mapped", "Each task has AC"],
|
|
277
|
-
position: 7,
|
|
278
|
-
autoAssign: true,
|
|
279
|
-
taskTemplates: [
|
|
280
|
-
task("tk-phases", "Define implementation phases", "**Output:** phased plan in tasks.md\n\nEach phase ends with something deployable/testable locally — not a monolith at the end.", "architect"),
|
|
281
|
-
task("tk-backlog", "Generate task backlog", "**Output:** tasks.md with stable task IDs\n\nExample: 'Create POST /users validating email format (REQ-F-012)' not 'build auth'.", "architect"),
|
|
282
|
-
task("tk-boundaries", "Bound task packets", "**Action:** split until each task is completable in one agent session.\n\nInclude file list hint per task where possible.", "architect"),
|
|
283
|
-
task("tk-deps", "Map dependencies", "**Output:** dependsOn graph\n\nMark [P] parallel-safe vs sequential. Critical path identified.", "architect"),
|
|
284
|
-
task("tk-ac", "Acceptance criteria per task", "**Output:** testable AC under each task\n\nAgent marks done only when AC objectively met.", "qa"),
|
|
285
|
-
task("tk-test-plan", "Test plan per task", "**Output:** test-plan.md entries\n\nUnit, integration, contract tests expected per task.", "qa"),
|
|
286
|
-
task("tk-order", "Prioritize execution order", "**Output:** ordered task list\n\nRisk spikes and foundation tasks first.", "tech-lead"),
|
|
287
|
-
],
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
id: "tasks-approval",
|
|
291
|
-
title: "Tasks Approval",
|
|
292
|
-
description: `## Objective
|
|
293
|
-
**Human gate 3** — approve task breakdown before any agent writes code (SDDD task validation checkpoint).
|
|
294
|
-
|
|
295
|
-
## Entry
|
|
296
|
-
tasks.md complete.
|
|
297
|
-
|
|
298
|
-
## Exit
|
|
299
|
-
Tasks approved. Implementation may begin.
|
|
300
|
-
|
|
301
|
-
## Gate
|
|
302
|
-
Human confirms scope per task is logical, complete, and not oversized.`,
|
|
303
|
-
purpose: "Human gate — tasks",
|
|
304
|
-
rules: ["Task breakdown approved", "No implementation before approval"],
|
|
305
|
-
position: 8,
|
|
306
|
-
autoAssign: true,
|
|
103
|
+
A technical approach and a task breakdown exist.`,
|
|
104
|
+
purpose: "Plan",
|
|
105
|
+
rules: ["Approach chosen", "Data/API impact known", "Work broken down"],
|
|
106
|
+
position: 2,
|
|
107
|
+
autoAssign: false,
|
|
307
108
|
taskTemplates: [
|
|
308
|
-
|
|
309
|
-
task("
|
|
310
|
-
|
|
311
|
-
|
|
109
|
+
task("ds-approach", "Choose technical approach", "**Output:** short design note describing the chosen approach.", "", [
|
|
110
|
+
task("ds-data", "Define data model and API changes", "**Output:** schema, types, or endpoint changes.", "", [
|
|
111
|
+
task("ds-risks", "List risks and dependencies", "**Output:** risks, unknowns, and what this depends on.", "", [
|
|
112
|
+
task("ds-breakdown", "Break work into tasks", "**Output:** ordered list of implementation steps."),
|
|
113
|
+
]),
|
|
114
|
+
]),
|
|
312
115
|
]),
|
|
313
116
|
],
|
|
314
117
|
},
|
|
315
118
|
{
|
|
316
|
-
id: "
|
|
317
|
-
title: "
|
|
318
|
-
description: `## Objective
|
|
319
|
-
Prepare repo knowledge layer so agents don't hallucinate structure (pangon ai-sdlc-scaffold, context-window efficiency).
|
|
320
|
-
|
|
321
|
-
## Entry
|
|
322
|
-
Tasks Approval gate passed.
|
|
323
|
-
|
|
324
|
-
## Exit
|
|
325
|
-
Indexes, status.md, spec/design indexes, .env.example updated.
|
|
326
|
-
|
|
327
|
-
## Gate
|
|
328
|
-
Agent can find GOAL/US/REQ/DEC/tasks without loading entire repo.`,
|
|
329
|
-
purpose: "Context engineering",
|
|
330
|
-
rules: ["Status doc current", "Indexes updated", ".env.example current"],
|
|
331
|
-
position: 9,
|
|
332
|
-
autoAssign: true,
|
|
333
|
-
taskTemplates: [
|
|
334
|
-
task("cx-status", "Create or update status.md", "**Output:** status.md\n\nCurrent phase, active task, blockers, last handoff, next agent action.", "agent"),
|
|
335
|
-
task("cx-spec-index", "Spec artifact index", "**Output:** phase index linking GOAL/US/REQ paths\n\nMinimize tokens per agent invocation.", "agent"),
|
|
336
|
-
task("cx-decisions-index", "Decisions index", "**Output:** DEC-* index by component and phase.", "agent"),
|
|
337
|
-
task("cx-repo-map", "Repo map", "**Output:** entry points, module boundaries, test locations.", "agent"),
|
|
338
|
-
task("cx-branch", "Create feature branch", "**Output:** feature branch from dev/main\n\nWork stays local. **Do not open PR yet** — draft branch only.", "engineer"),
|
|
339
|
-
task("cx-env-example", "Update .env.example", "**Output:** documented placeholders for any new config\n\nNever commit real secrets.", "engineer"),
|
|
340
|
-
],
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
id: "implementation",
|
|
344
|
-
title: "Implementation",
|
|
119
|
+
id: "implement",
|
|
120
|
+
title: "Implement",
|
|
345
121
|
description: `## Objective
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
## Entry
|
|
349
|
-
Agent context ready; feature branch exists.
|
|
122
|
+
Build the change.
|
|
350
123
|
|
|
351
124
|
## Exit
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
## Gate
|
|
355
|
-
One task at a time. Design gaps stop work — update spec/ADR, don't guess.
|
|
356
|
-
|
|
357
|
-
**Note:** Draft PR optional during this stage; must stay draft until Human Pre-PR Approval.`,
|
|
125
|
+
Change is complete and passes local checks.`,
|
|
358
126
|
purpose: "Build",
|
|
359
|
-
rules: ["
|
|
360
|
-
position:
|
|
361
|
-
autoAssign:
|
|
127
|
+
rules: ["Change implemented", "Tests added/updated", "Local checks pass"],
|
|
128
|
+
position: 3,
|
|
129
|
+
autoAssign: false,
|
|
362
130
|
taskTemplates: [
|
|
363
|
-
|
|
364
|
-
task("im-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
task("im-commit", "Commit task increment", "**Action:** atomic commit per task with task ID in message.\n\nPush to feature branch (still no public PR).", "agent"),
|
|
131
|
+
task("im-core", "Implement the change", "**Output:** working code for the feature or fix.", "", [
|
|
132
|
+
task("im-tests", "Add or update tests", "**Output:** tests covering the new behavior.", "", [
|
|
133
|
+
task("im-docs", "Update docs and config", "**Output:** updated docs, config, or examples.", "", [
|
|
134
|
+
task("im-selfcheck", "Run local checks", "**Output:** lint, typecheck, and build all pass locally."),
|
|
135
|
+
]),
|
|
136
|
+
]),
|
|
370
137
|
]),
|
|
371
|
-
task("im-docs", "Update documentation", "**Output:** README, API docs, inline docs where behaviour is non-obvious.", "engineer"),
|
|
372
138
|
],
|
|
373
139
|
},
|
|
374
140
|
{
|
|
375
|
-
id: "
|
|
376
|
-
title: "
|
|
141
|
+
id: "review",
|
|
142
|
+
title: "Review",
|
|
377
143
|
description: `## Objective
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
## Entry
|
|
381
|
-
Implementation complete on feature branch.
|
|
382
|
-
|
|
383
|
-
## Exit
|
|
384
|
-
Lint, typecheck, unit, integration, contract tests all exit 0.
|
|
385
|
-
|
|
386
|
-
## Gate
|
|
387
|
-
Retry loop until green. Agent cannot claim success without command output.`,
|
|
388
|
-
purpose: "Automated QA",
|
|
389
|
-
rules: ["Lint exit 0", "Typecheck exit 0", "All tests exit 0"],
|
|
390
|
-
position: 11,
|
|
391
|
-
autoAssign: true,
|
|
392
|
-
taskTemplates: [
|
|
393
|
-
task("vf-lint", "Run linter", "**Command:** project lint (eslint, detekt, etc.)\n\nFix all errors; warnings per team policy.", "qa"),
|
|
394
|
-
task("vf-types", "Run type checker", "**Command:** tsc / mypy / kotlin compile\n\nZero type errors on changed modules.", "qa"),
|
|
395
|
-
task("vf-unit", "Run unit tests", "**Command:** unit test suite\n\nNo skipped tests on critical paths without documented reason.", "qa"),
|
|
396
|
-
task("vf-integration", "Run integration tests", "**Command:** integration suite\n\nDB, API, service boundaries exercised.", "qa"),
|
|
397
|
-
task("vf-contract", "Contract / BDD verify", "**Command:** Gherkin runner or SpecBridge\n\nBehaviour matches approved scenarios.", "qa"),
|
|
398
|
-
task("vf-retry", "Fix until green", "**Action:** on failure → fix → re-run.\n\nNo proceeding while red. Max retries logged in status.md.", "engineer"),
|
|
399
|
-
task("vf-test-report", "Write test report", "**Output:** test-report.md (Specorator Stage 8)\n\nSuites run, pass/fail counts, known gaps.", "qa"),
|
|
400
|
-
],
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
id: "review-security",
|
|
404
|
-
title: "Review & Security",
|
|
405
|
-
description: `## Objective
|
|
406
|
-
Security audit + human review of agent output before pre-PR approval (Specorator QA track, agentic security review).
|
|
407
|
-
|
|
408
|
-
## Entry
|
|
409
|
-
Verification green.
|
|
144
|
+
Review the change directly. No PR, no branch ceremony.
|
|
410
145
|
|
|
411
146
|
## Exit
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
rules: ["Secrets scan clean", "Peer review done", "Debt logged"],
|
|
418
|
-
position: 12,
|
|
419
|
-
autoAssign: true,
|
|
420
|
-
taskTemplates: [
|
|
421
|
-
task("rs-secrets", "Secrets scan", "**Action:** scan diff for keys, tokens, credentials, .env leaks.\n\nUse gitleaks or equivalent.", "qa"),
|
|
422
|
-
task("rs-auth", "Auth & injection review", "**Action:** review authz, session handling, SQL/command injection, XSS surfaces.", "architect"),
|
|
423
|
-
task("rs-deps", "Dependency audit", "**Action:** SCA scan; CVEs fixed, accepted with ADR, or mitigated.", "engineer"),
|
|
424
|
-
task("rs-diff-review", "Human diff review", "**Action:** human reads full branch diff — not rubber-stamp.\n\nFocus on agent-generated code quality.", "tech-lead"),
|
|
425
|
-
task("rs-anti-skip", "Anti-rationalization pass", "**Action:** reject excuses: 'tests flaky', 'types later', 'works on my machine'.\n\nAll gates must genuinely pass.", "tech-lead"),
|
|
426
|
-
task("rs-debt", "Log technical debt", "**Output:** technical-debt.md entries\n\nShortcuts explicit with owner and follow-up task.", "engineer"),
|
|
427
|
-
],
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
id: "analyze",
|
|
431
|
-
title: "Analyze",
|
|
432
|
-
description: `## Objective
|
|
433
|
-
Cross-artifact consistency check (Spec Kit /speckit.analyze, Specorator /spec:analyze).
|
|
434
|
-
|
|
435
|
-
## Entry
|
|
436
|
-
Review & Security complete.
|
|
437
|
-
|
|
438
|
-
## Exit
|
|
439
|
-
traceability.md updated; spec ↔ plan ↔ tasks ↔ code alignment verified.
|
|
440
|
-
|
|
441
|
-
## Gate
|
|
442
|
-
No drift between approved spec and implementation. Gaps documented with resolution plan.`,
|
|
443
|
-
purpose: "Consistency analysis",
|
|
444
|
-
rules: ["RTM complete", "No spec drift", "Gaps documented"],
|
|
445
|
-
position: 13,
|
|
446
|
-
autoAssign: true,
|
|
447
|
-
taskTemplates: [
|
|
448
|
-
task("an-spec-plan", "Spec ↔ plan alignment", "**Action:** verify design covers all approved REQ-*.\n\nFlag orphan requirements or orphan design elements.", "qa"),
|
|
449
|
-
task("an-plan-code", "Plan ↔ code alignment", "**Action:** verify each task's AC is met in code.\n\nWalk tasks.md checklist item by item.", "qa"),
|
|
450
|
-
task("an-rtm", "Update traceability matrix", "**Output:** traceability.md (Specorator Stage 9)\n\nGOAL → US → REQ → task → test → file mapping.", "qa"),
|
|
451
|
-
task("an-drift", "Spec drift report", "**Output:** list any code behaviour not in spec.\n\nEither update spec (with re-approval) or revert code.", "tech-lead"),
|
|
452
|
-
],
|
|
453
|
-
},
|
|
454
|
-
{
|
|
455
|
-
id: "human-pre-pr-approval",
|
|
456
|
-
title: "Human Pre-PR Approval",
|
|
457
|
-
description: `## Objective
|
|
458
|
-
**Human gate 4 — mandatory before any PR is opened or marked ready.**
|
|
459
|
-
|
|
460
|
-
This is the fix for PR hierarchy: humans approve that branch work is complete and safe to **expose** as a pull request.
|
|
461
|
-
|
|
462
|
-
## Entry
|
|
463
|
-
Analyze stage complete; all automated gates green.
|
|
464
|
-
|
|
465
|
-
## Exit
|
|
466
|
-
Written approval record: "authorized to open PR".
|
|
467
|
-
|
|
468
|
-
## Gate
|
|
469
|
-
**No gh pr create, no gh pr ready, no public PR until this gate passes.** Agents must not self-open PRs.`,
|
|
470
|
-
purpose: "Human gate — pre-PR",
|
|
471
|
-
rules: ["Human written approval", "No PR before this gate", "All upstream gates passed"],
|
|
472
|
-
position: 14,
|
|
473
|
-
autoAssign: true,
|
|
147
|
+
The diff has been read and acceptance criteria are met.`,
|
|
148
|
+
purpose: "Review diff",
|
|
149
|
+
rules: ["Diff reviewed", "Criteria met", "Feedback applied"],
|
|
150
|
+
position: 4,
|
|
151
|
+
autoAssign: false,
|
|
474
152
|
taskTemplates: [
|
|
475
|
-
|
|
476
|
-
task("
|
|
477
|
-
|
|
478
|
-
|
|
153
|
+
task("rv-diff", "Review the change set", "**Output:** notes from reading the full diff.", "", [
|
|
154
|
+
task("rv-criteria", "Verify acceptance criteria", "**Output:** each criterion checked against the change.", "", [
|
|
155
|
+
task("rv-feedback", "Apply review feedback", "**Output:** fixes for issues found in review."),
|
|
156
|
+
]),
|
|
479
157
|
]),
|
|
480
158
|
],
|
|
481
159
|
},
|
|
482
160
|
{
|
|
483
|
-
id: "
|
|
484
|
-
title: "
|
|
161
|
+
id: "verify",
|
|
162
|
+
title: "Verify",
|
|
485
163
|
description: `## Objective
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
## Entry
|
|
489
|
-
Human Pre-PR Approval gate passed with written authorization.
|
|
164
|
+
Confirm it works and nothing else broke.
|
|
490
165
|
|
|
491
166
|
## Exit
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
rules: ["Pre-PR approval on record", "PR description complete", "CI triggered"],
|
|
498
|
-
position: 15,
|
|
499
|
-
autoAssign: true,
|
|
500
|
-
taskTemplates: [
|
|
501
|
-
task("pr-desc", "Write PR description", "**Output:** PR body with:\n\n- Summary & motivation\n- Links to GOAL/US/REQ IDs\n- Test plan (commands + manual steps)\n- Screenshots if UI\n- Known limitations / debt", "engineer"),
|
|
502
|
-
task("pr-create", "Open pull request", "**Action:** gh pr create to dev/main (or team target).\n\nOnly execute after hp-approve task is done.", "engineer"),
|
|
503
|
-
task("pr-ready", "Mark PR ready for review", "**Action:** gh pr ready if was draft.\n\nMoves from WIP to reviewable state for team.", "engineer"),
|
|
504
|
-
task("pr-ci", "CI pipeline green", "**Action:** wait for CI; fix failures on branch.\n\nRe-push until all checks pass.", "engineer"),
|
|
505
|
-
],
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
id: "pr-review",
|
|
509
|
-
title: "PR Review",
|
|
510
|
-
description: `## Objective
|
|
511
|
-
Team review cycle on the open PR — address feedback until merge-ready (Specorator Stage 9 Review).
|
|
512
|
-
|
|
513
|
-
## Entry
|
|
514
|
-
PR open and CI green.
|
|
515
|
-
|
|
516
|
-
## Exit
|
|
517
|
-
All review threads resolved; merge approval from required reviewers.
|
|
518
|
-
|
|
519
|
-
## Gate
|
|
520
|
-
Human merge approval. Agent does not self-merge.`,
|
|
521
|
-
purpose: "PR review cycle",
|
|
522
|
-
rules: ["Review threads resolved", "CI green", "Merge approval recorded"],
|
|
523
|
-
position: 16,
|
|
524
|
-
autoAssign: true,
|
|
167
|
+
Tests pass and behavior is verified.`,
|
|
168
|
+
purpose: "QA",
|
|
169
|
+
rules: ["Test suite green", "Behavior verified", "No regressions"],
|
|
170
|
+
position: 5,
|
|
171
|
+
autoAssign: false,
|
|
525
172
|
taskTemplates: [
|
|
526
|
-
task("
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
173
|
+
task("vf-suite", "Run the test suite", "**Output:** full test run is green.", "", [
|
|
174
|
+
task("vf-manual", "Manual and exploratory test", "**Output:** notes from trying the change by hand.", "", [
|
|
175
|
+
task("vf-regression", "Regression check", "**Output:** confirm related features still work."),
|
|
176
|
+
]),
|
|
177
|
+
]),
|
|
531
178
|
],
|
|
532
179
|
},
|
|
533
180
|
{
|
|
534
181
|
id: "done",
|
|
535
182
|
title: "Done",
|
|
536
183
|
description: `## Objective
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
## Entry
|
|
540
|
-
PR merged.
|
|
184
|
+
Wrap up and record what shipped.
|
|
541
185
|
|
|
542
186
|
## Exit
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
## Note
|
|
546
|
-
Deploy to staging/production is **not** part of this template — use your release pipeline separately.`,
|
|
187
|
+
Change is integrated and noted.`,
|
|
547
188
|
purpose: "Closed",
|
|
548
|
-
rules: [],
|
|
549
|
-
position:
|
|
189
|
+
rules: ["Change integrated", "Notes updated"],
|
|
190
|
+
position: 6,
|
|
550
191
|
autoAssign: false,
|
|
551
|
-
taskTemplates:
|
|
192
|
+
taskTemplates: [
|
|
193
|
+
task("dn-apply", "Integrate the change", "**Output:** change merged or applied to the main line of work.", "", [
|
|
194
|
+
task("dn-notes", "Update changelog and notes", "**Output:** short note of what changed and why.", "", [
|
|
195
|
+
task("dn-close", "Close out and record learnings", "**Output:** capture anything worth remembering next time."),
|
|
196
|
+
]),
|
|
197
|
+
]),
|
|
198
|
+
],
|
|
552
199
|
},
|
|
553
200
|
],
|
|
554
201
|
},
|
|
@@ -584,7 +231,7 @@ function insertTemplateStages(template) {
|
|
|
584
231
|
}
|
|
585
232
|
}
|
|
586
233
|
function upsertBuiltinTemplate(template) {
|
|
587
|
-
if (template.id ===
|
|
234
|
+
if (template.id === DEFAULT_WORKFLOW_TEMPLATE_ID) {
|
|
588
235
|
const existing = listWorkflowTemplateRows({ id: template.id });
|
|
589
236
|
if (existing.length > 0) {
|
|
590
237
|
deleteWorkflowTemplateStages(template.id);
|
|
@@ -165,7 +165,7 @@ export function importWorkflowTemplate(input) {
|
|
|
165
165
|
});
|
|
166
166
|
return replaceWorkflowTemplate(id, input.stages);
|
|
167
167
|
}
|
|
168
|
-
const PROTECTED_WORKFLOW_TEMPLATE_IDS = new Set(["
|
|
168
|
+
const PROTECTED_WORKFLOW_TEMPLATE_IDS = new Set(["empty", DEFAULT_WORKFLOW_TEMPLATE_ID]);
|
|
169
169
|
export function deleteWorkflowTemplate(templateId) {
|
|
170
170
|
ensureDefaultWorkflowTemplates();
|
|
171
171
|
const id = templateId;
|
|
@@ -343,7 +343,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${t.do
|
|
|
343
343
|
*
|
|
344
344
|
* This source code is licensed under the ISC license.
|
|
345
345
|
* See the LICENSE file in the root directory of this source tree.
|
|
346
|
-
*/const iv=te("ZoomOut",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]),hs=WS,hN=VS,mN=HS,av=p.forwardRef((e,t)=>p.createElement(Gg,Object.assign({},ze(e,["className"]),{ref:t,className:Z("fixed inset-0 z-50 bg-black/70 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",e.className)})));av.displayName=Gg.displayName;const wr=p.forwardRef((e,t)=>p.createElement(mN,null,p.createElement(av,null),p.createElement(Yg,Object.assign({},ze(e,["className","children"]),{ref:t,className:Z("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-card p-6 shadow-xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-xl",e.className)}),e.children,p.createElement(GS,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"},p.createElement(Gu,{className:"h-4 w-4"}),p.createElement("span",{className:"sr-only"},"Close")))));wr.displayName=Yg.displayName;function br(e){return p.createElement("div",Object.assign({},ze(e,["className"]),{className:Z("flex flex-col space-y-1.5 text-center sm:text-left",e.className)}))}br.displayName="DialogHeader";function wo(e){return p.createElement("div",Object.assign({},ze(e,["className"]),{className:Z("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",e.className)}))}wo.displayName="DialogFooter";const kr=p.forwardRef((e,t)=>p.createElement(Kg,Object.assign({},ze(e,["className"]),{ref:t,className:Z("text-lg font-semibold leading-none tracking-tight",e.className)})));kr.displayName=Kg.displayName;const cv=p.forwardRef((e,t)=>p.createElement(Qg,Object.assign({},ze(e,["className"]),{ref:t,className:Z("text-sm text-muted-foreground",e.className)})));cv.displayName=Qg.displayName;const uv=p.createContext(null);function gN({children:e}){const[t,n]=p.useState(!1),[r,s]=p.useState({title:null,message:"",confirmLabel:null,cancelLabel:null}),o=p.useRef(null),l=p.useCallback((f,h=null)=>new Promise(y=>{o.current=y;const x={title:null,confirmLabel:null,cancelLabel:null,message:f};h!==null&&(x.title=h.title,x.confirmLabel=h.confirmLabel,x.cancelLabel=h.cancelLabel),s(x),n(!0)}),[]);function a(f){n(!1);const h=o.current;h!==null&&h(f),o.current=null}let c="Delete?";r.title!==null&&(c=r.title);let u="Cancel";r.cancelLabel!==null&&(u=r.cancelLabel);let d="Delete";return r.confirmLabel!==null&&(d=r.confirmLabel),i.jsxs(uv.Provider,{value:{confirmDestructive:l},children:[e,i.jsx(hs,{open:t,onOpenChange:f=>{f||a(!1)},children:i.jsxs(wr,{className:"max-w-md border-white/[0.1] bg-[#111] sm:rounded-xl [&>button]:hidden",children:[i.jsxs(br,{children:[i.jsx(kr,{children:c}),i.jsx(cv,{children:r.message})]}),i.jsxs(wo,{className:"gap-2 sm:gap-2",children:[i.jsx(H,{type:"button",variant:"outline",onClick:()=>a(!1),children:u}),i.jsx(H,{type:"button",variant:"destructive",onClick:()=>a(!0),children:d})]})]})})]})}function Yu(){const e=p.useContext(uv);if(!e)throw new Error("useConfirm must be used within ConfirmDialogProvider");return e}function Ei(e={}){let t=null;"className"in e&&(e.className===null?t=null:typeof e.className=="string"&&(t=e.className));let n=!1;"compact"in e&&e.compact===!0&&(n=!0);let r="/projects";"linkTo"in e&&(e.linkTo===null?r=null:typeof e.linkTo=="string"&&(r=e.linkTo));const s=qt(),o=r,l=i.jsxs(i.Fragment,{children:[i.jsxs("div",{className:Z("relative flex shrink-0 items-center justify-center rounded-lg bg-primary/15 ring-1 ring-primary/25",n?"h-8 w-8":"h-10 w-10"),children:[i.jsx(nv,{className:Z("text-primary",n?"h-4 w-4":"h-5 w-5")}),i.jsx(cN,{className:Z("absolute rounded-full bg-card p-0.5 text-primary",n?"-bottom-0.5 -right-0.5 h-3 w-3":"-bottom-1 -right-1 h-4 w-4")})]}),i.jsxs("div",{className:"min-w-0",children:[i.jsx("p",{className:Z("font-semibold tracking-tight",n?"text-sm":"text-base"),children:"Task Bridge"}),n?i.jsx("p",{className:"text-[10px] text-muted-foreground",children:"v0.1"}):null]})]});if(!o)return i.jsx("div",{className:Z("flex items-center gap-2.5",t),children:l});let a=null;return s.pathname.startsWith("/projects/")&&(a={from:s.pathname}),i.jsx(tr,{to:o,state:a,className:Z("flex items-center gap-2.5 rounded-lg transition-colors hover:bg-white/[0.04]",n?"-mx-1 px-1 py-0.5":"px-1 py-0.5",t),children:l})}const vN=ig("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground",secondary:"border-transparent bg-secondary text-secondary-foreground",outline:"text-foreground",success:"border-transparent bg-success/15 text-success",warn:"border-transparent bg-warn/15 text-warn",muted:"border-transparent bg-muted text-muted-foreground"}},defaultVariants:{variant:"default"}});function dv(e){return p.createElement("div",Object.assign({},ze(e,["className","variant"]),{className:Z(vN({variant:e.variant}),e.className)}))}const Ku="task-bridge.session.v2";function Jt(){try{const e=localStorage.getItem(Ku);if(!e)return null;const t=JSON.parse(e),n=t.token,r=t.userId;return n===null||n.trim()===""||r===null||r.trim()===""?null:Object.assign({},t,{mustChangePassword:t.mustChangePassword===!0})}catch{return null}}function bo(e){localStorage.setItem(Ku,JSON.stringify(e))}function Qu(){localStorage.removeItem(Ku)}function Ic(e,t){const n=Jt();n&&bo(Object.assign({},n,{projectId:e,projectName:t}))}function xN(){const e=Jt();e&&bo(Object.assign({},e,{projectId:null,projectName:null}))}const ml="empty";class Wn extends Error{constructor(n,r){super(n);ge(this,"status");this.status=r}}function yN(e){return{Accept:"application/json",Authorization:`Bearer ${e.token}`}}async function wN(e){try{const t=await e.json(),n=t.details;if(n!==null&&n.code==="PASSWORD_CHANGE_REQUIRED")return"PASSWORD_CHANGE_REQUIRED";if(t.error)return t.error}catch{return e.statusText||"Request failed"}return e.statusText||"Request failed"}async function ue(e,t,n=null){let r={};if(n!==null){const c=n.headers;typeof c<"u"&&c!==null&&(r=c)}let s={};n!==null&&(s=n);const o=Object.assign({},yN(e),r),l=await fetch(t,Object.assign({},s,{headers:o}));if(l.status===401)throw Qu(),window.location.href="/app/login",new Wn("Session expired",401);if(!l.ok){const c=await wN(l);if(c==="PASSWORD_CHANGE_REQUIRED"){const u=Jt();throw u&&bo(Object.assign({},u,{mustChangePassword:!0})),window.location.href="/app/change-password",new Wn("Password change required",403)}throw new Wn(c,l.status)}if(l.status===204)return null;const a=await l.text();return a?JSON.parse(a):null}async function fv(){const e=await fetch("/api/auth/status");if(!e.ok)throw new Wn("Failed to check status",e.status);return e.json()}async function bN(e){const t=await fetch("/api/auth/setup",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){const n=await t.json().catch(()=>({}));let r="Setup failed";throw n.error!==null&&(r=n.error),new Wn(r,t.status)}return t.json()}async function kN(e){const t=await fetch("/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){const n=await t.json().catch(()=>({}));let r="Login failed";throw n.error!==null&&(r=n.error),new Wn(r,t.status)}return t.json()}async function SN(e,t){const n=await fetch("/api/auth/change-password",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.token}`},body:JSON.stringify(t)});if(!n.ok){const r=await n.json().catch(()=>({}));let s="Password change failed";throw r.error!==null&&(s=r.error),new Wn(s,n.status)}return n.json()}function NN(e){let t="";return typeof window<"u"&&(t=window.location.origin),`taskbridge://auth?server=${encodeURIComponent(t)}&token=${encodeURIComponent(e.token)}`}async function jN(e){const t=await ue(e,"/api/admin/users");return t.users!==null?t.users:[]}async function CN(e,t){return(await ue(e,"/api/admin/users",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).user}async function EN(e,t,n){return(await ue(e,`/api/admin/users/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).user}async function TN(e,t){await ue(e,`/api/admin/users/${t}`,{method:"DELETE"})}async function pv(e){const t=await ue(e,"/api/projects");return t.projects!==null?t.projects:[]}async function RN(e,t){return ue(e,"/api/projects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t.name,id:t.id.trim(),repoPath:t.repoPath,description:t.description.trim(),workflowTemplateId:t.workflowTemplateId.trim()||ml})})}async function IN(e,t,n){return ue(e,`/api/projects/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}async function Xu(e){const t=await ue(e,"/api/workflow-templates");return t.items!==null?t.items:[]}async function PN(e,t){return ue(e,`/api/workflow-templates/${t}`)}async function _N(e,t){return ue(e,"/api/workflow-templates",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function LN(e,t,n){return ue(e,`/api/workflow-templates/${t}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({stages:n})})}async function MN(e,t){await ue(e,`/api/workflow-templates/${t}`,{method:"DELETE"})}const ya=new Set(["ai-sdlc"]);async function cp(e,t){const n=await fetch(`/api/workflow-templates/${t}/export`,{headers:{Authorization:`Bearer ${e.token}`}});if(!n.ok)throw new Wn("Export failed",n.status);const r=await n.blob(),s=n.headers.get("Content-Disposition");let o="";s!==null&&(o=s);const l=/filename="([^"]+)"/.exec(o);let a=`${t}.json`;if(l!==null&&l.length>1){const d=l[1];typeof d=="string"&&(a=d)}const c=URL.createObjectURL(r),u=document.createElement("a");u.href=c,u.download=a,u.click(),URL.revokeObjectURL(c)}async function ON(e,t){return ue(e,"/api/workflow-templates/import",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function AN(e,t){let n="";return t.description!==null&&(n=t.description),ue(e,"/api/epics",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectId:t.projectId,title:t.title,description:n})})}async function DN(e,t){let n="";return t.description!==null&&(n=t.description),ue(e,"/api/tasks",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parentId:t.parentId,title:t.title,description:n,stageId:t.stageId})})}async function Zu(e,t={projectId:null,commentsOnly:null,epicsOnly:null,cursor:null,limit:null}){const n=new URLSearchParams;t.projectId&&n.set("projectId",t.projectId),t.commentsOnly&&n.set("commentsOnly","true"),t.epicsOnly&&n.set("epicsOnly","true"),t.cursor&&n.set("cursor",t.cursor),t.limit&&n.set("limit",String(t.limit));let r="";return n.toString()&&(r=`?${n.toString()}`),ue(e,`/api/inbox${r}`)}async function up(e,t={projectId:null,commentsOnly:null,epicsOnly:null,limit:null}){const n=[];let r=null;do{let s=100;t.limit!==null&&(s=t.limit);const o=await Zu(e,Object.assign({},t,{cursor:r,limit:s}));for(const l of o.items)n.push(l);r=o.nextCursor}while(r);return n}async function dp(e,t){return ue(e,`/api/tasks/${t}`)}async function zN(e,t,n){return ue(e,`/api/tasks/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({comment:{text:n,by:"web"}})})}async function $N(e,t,n){return ue(e,`/api/tasks/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({description:n})})}async function hv(e,t){return ue(e,`/api/projects/${t}/workflow`)}async function FN(e,t,n){return ue(e,`/api/projects/${t}/workflow`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}async function BN(e,t,n){return ue(e,`/api/projects/${t}/members`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n.name,role:n.role})})}async function UN(e,t,n){await ue(e,`/api/projects/${t}/members/${n}`,{method:"DELETE"})}async function mv(e){const t=await ue(e,"/api/libraries");return t.items!==null?t.items:[]}async function gv(e,t){return ue(e,`/api/libraries/${t}`)}async function WN(e,t){return ue(e,"/api/libraries",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function VN(e,t,n){return ue(e,`/api/libraries/${t}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}async function HN(e,t){await ue(e,`/api/libraries/${t}`,{method:"DELETE"})}async function GN(e,t,n){return ue(e,`/api/libraries/${t}/documents`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}async function YN(e,t){return ue(e,`/api/library-documents/${t}`)}async function KN(e,t,n,r){return ue(e,`/api/libraries/${t}/documents/${n}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)})}async function QN(e,t,n){await ue(e,`/api/libraries/${t}/documents/${n}`,{method:"DELETE"})}async function XN(e,t,n){const r=await ue(e,`/api/library-documents/${t}/links`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({taskId:n})});return r.items!==null?r.items:[]}async function ZN(e,t,n){await ue(e,`/api/library-documents/${t}/links/${n}`,{method:"DELETE"})}async function fp(e,t,n,r){return ue(e,`/api/tasks/${t}/work-status`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({workStatus:n,claimedBy:r})})}const Pc="task-bridge.read-tasks",_c="task-bridge.notified-comments";function qN(e){if(Number.isInteger(e))return e;if(e===null)return null;const t=String(e).trim();if(t.length===0)return null;const n=Number(t);return Number.isInteger(n)?n:null}function Ti(e){try{const t=localStorage.getItem(e);if(!t)return[];const n=JSON.parse(t);if(!Array.isArray(n))return[];const r=[];for(const s of n){const o=qN(s);o!==null&&r.push(o)}return r}catch{return[]}}function vv(e,t){localStorage.setItem(e,JSON.stringify(Array.from(new Set(t))))}function Lc(e){return Ti(Pc).includes(e)}function xv(e){Number.isInteger(e)&&(vv(Pc,Ti(Pc).concat([e])),window.dispatchEvent(new CustomEvent("task-bridge:read")))}function JN(e){return Ti(_c).includes(e)}function pp(e){Number.isInteger(e)&&vv(_c,Ti(_c).concat([e]))}function ej(e){return e.filter(t=>t.status==="ready"&&!Lc(t.taskId)).length}function tj(e,t){const[n,r]=p.useState([]),[s,o]=p.useState(!0),l=p.useRef(!1),a=p.useCallback(async()=>{if(!(!e||!t)){o(!0);try{const d=await up(e,{projectId:t,commentsOnly:!0,epicsOnly:null,limit:100});for(const h of d){if(!l.current){pp(h.taskId);continue}JN(h.taskId)||(pp(h.taskId),Y.message("A comment was added to your task",{description:h.title||`Task #${h.taskId}`}))}l.current=!0;const f=await up(e,{projectId:t,commentsOnly:null,epicsOnly:null,limit:100});r(f)}finally{o(!1)}}},[e,t]);p.useEffect(()=>{a();const d=window.setInterval(()=>void a(),3e4),f=()=>void a();return window.addEventListener("task-bridge:read",f),()=>{window.clearInterval(d),window.removeEventListener("task-bridge:read",f)}},[a]);const c=n.filter(d=>d.status==="ready"),u=n.filter(d=>d.status==="sent");return{items:n,commentItems:c,openItems:u,loading:s,refresh:a}}function zt(){const[e]=p.useState(()=>Jt());return e}const hp={admin:"Admin","read-write":"Read & Write",read:"Read only"};function nj(){const e=qt(),t=yn(),n=zt(),r=Vl("/projects/:projectId/*",e.pathname);let s=null;if(r!==null){const y=r.params.projectId;typeof y=="string"&&y.length>0&&(s=y)}let o=s;o===null&&n!==null&&n.projectId!==null&&(o=n.projectId);const l=s;let a=l;n!==null&&n.projectName!==null?a=n.projectName:l===null&&o!==null&&(a=o);const{commentItems:c}=tj(n,l),u=ej(c);function d(){Qu(),t("/login",{replace:!0})}const f=n!==null&&n.userRole==="admin";let h="";if(n!==null){const y=n.userRole;if(y in hp){const x=hp[y];typeof x=="string"&&(h=x)}h===""&&(h=y)}return i.jsxs("aside",{className:"app-sidebar",children:[i.jsx("div",{className:"flex h-14 shrink-0 items-center border-b border-white/[0.07] px-4",children:i.jsx(Ei,{compact:!0})}),i.jsxs("nav",{className:"flex-1 space-y-4 overflow-y-auto px-2 py-3",children:[i.jsxs("div",{className:"space-y-0.5",children:[i.jsx(nn,{to:"/projects",label:"Projects",icon:ev,end:!0}),i.jsx(nn,{to:"/library",label:"Library",icon:Kl}),i.jsx(nn,{to:"/workflow-templates",label:"Workflow templates",icon:ip})]}),l?i.jsxs("div",{className:"space-y-0.5",children:[i.jsx("p",{className:"truncate px-3 pb-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground",children:a}),i.jsx(nn,{to:`/projects/${l}/tasks`,label:"Epics",icon:Rc}),i.jsx(nn,{to:`/projects/${l}/inbox`,label:"Inbox",icon:nN,badge:u}),i.jsx(nn,{to:`/projects/${l}/mobile`,label:"Mobile",icon:ov}),i.jsx(nn,{to:`/projects/${l}/workflow`,label:"Pipeline",icon:ip})]}):o?i.jsxs("div",{className:"space-y-0.5",children:[i.jsx("p",{className:"px-3 pb-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground",children:"Recent project"}),i.jsx(nn,{to:`/projects/${o}/tasks`,label:a!==null?a:"Open project",icon:Rc})]}):null,f?i.jsxs("div",{className:"space-y-0.5",children:[i.jsx("p",{className:"px-3 pb-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground",children:"Admin"}),i.jsx(nn,{to:"/admin/users",label:"Team members",icon:pN})]}):null]}),i.jsxs("div",{className:"shrink-0 border-t border-white/[0.07] p-2 space-y-1",children:[n?i.jsxs("div",{className:"flex items-center gap-2.5 rounded-lg px-3 py-2",children:[i.jsx("div",{className:"flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary/20 text-xs font-semibold text-primary uppercase",children:n.userName.charAt(0)}),i.jsxs("div",{className:"flex-1 min-w-0",children:[i.jsx("p",{className:"truncate text-sm font-medium text-foreground leading-none",children:n.userName}),i.jsx("p",{className:"truncate text-[11px] text-muted-foreground mt-0.5",children:h})]}),i.jsx(dv,{variant:"outline",className:"text-[10px] px-1.5 py-0 shrink-0 hidden sm:flex",children:h})]}):null,i.jsxs(H,{variant:"ghost",size:"sm",className:"h-9 w-full justify-start rounded-lg text-muted-foreground hover:bg-white/[0.05] hover:text-foreground",onClick:d,children:[i.jsx(sN,{className:"h-4 w-4"}),"Sign out"]})]})]})}function nn(e){let t=0;"badge"in e&&typeof e.badge=="number"&&(t=e.badge);let n=!1;"end"in e&&e.end===!0&&(n=!0);const{to:r,label:s,icon:o}=e;return i.jsxs(jw,{to:r,end:n,className:({isActive:l})=>Z("flex h-9 items-center gap-2.5 rounded-lg px-3 text-sm transition-colors",l?"bg-white/[0.09] font-medium text-white":"text-muted-foreground hover:bg-white/[0.04] hover:text-foreground"),children:[i.jsx(o,{className:"h-4 w-4 shrink-0 opacity-90"}),i.jsx("span",{className:"flex-1 truncate",children:s}),i.jsx("span",{className:Z("flex h-5 min-w-[1.25rem] items-center justify-center rounded-full px-1.5 text-[11px] font-medium",t>0?"bg-primary text-primary-foreground":"invisible"),"aria-hidden":t<=0,children:t>0?t:0})]})}function rj(){return i.jsxs("div",{className:"app-shell",children:[i.jsx(nj,{}),i.jsx("main",{className:"app-main flex min-h-0 flex-1 flex-col overflow-y-auto",children:i.jsx(ng,{})})]})}function sj(){const{projectId:e}=fs(),t=yn(),n=zt(),r=p.useRef(null);return p.useEffect(()=>{if(!n||!e||r.current===e)return;let s=!0;return pv(n).then(o=>{if(!s)return;const l=o.find(a=>a.id===e);if(!l){t("/projects",{replace:!0});return}Ic(l.id,l.name),r.current=e}),()=>{s=!1}},[n,e,t]),n?e?i.jsx(ng,{}):i.jsx(qe,{to:"/projects",replace:!0}):i.jsx(qe,{to:"/login",replace:!0})}function yv(e){let t=!1;"loading"in e&&(t=e.loading===!0);const{loaded:n,hasMore:r,onLoadMore:s}=e;return n===0&&!r?null:i.jsxs("div",{className:"flex flex-col items-center gap-2 pt-2",children:[i.jsxs("p",{className:"text-xs text-muted-foreground",children:[n," loaded"]}),r?i.jsxs(H,{variant:"outline",size:"sm",disabled:t,onClick:s,children:[t?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):null,"Load more"]}):n>0?i.jsx("p",{className:"text-xs text-muted-foreground",children:"End of list"}):null]})}function bn(e){let t=null;"breadcrumb"in e&&(e.breadcrumb===null?t=null:Array.isArray(e.breadcrumb)&&(t=e.breadcrumb));let n=null;"subtitle"in e&&(e.subtitle===null?n=null:typeof e.subtitle=="string"&&(n=e.subtitle));let r=null;"actions"in e&&e.actions!==null&&(r=e.actions);let s=null;"className"in e&&(e.className===null?s=null:typeof e.className=="string"&&(s=e.className));const{title:o}=e;return i.jsxs("div",{className:Z("page-toolbar flex-wrap",s),children:[i.jsxs("div",{className:"min-w-0",children:[t&&t.length>0?i.jsx(oj,{items:t}):null,i.jsx("h1",{className:"truncate text-lg font-semibold tracking-tight text-white",children:o}),n?i.jsx("p",{className:"text-xs text-muted-foreground",children:n}):null]}),r?i.jsx("div",{className:"flex flex-wrap items-center gap-2",children:r}):null]})}function oj({items:e}){return i.jsx("nav",{className:"mb-1 flex items-center gap-1 text-xs text-muted-foreground",children:e.map((t,n)=>{const r=n===e.length-1;return i.jsxs(p.Fragment,{children:[t.to&&!r?i.jsx(tr,{to:t.to,className:"max-w-[12rem] truncate transition-colors hover:text-foreground",children:t.label}):i.jsx("span",{className:Z("max-w-[12rem] truncate",r&&"text-foreground"),children:t.label}),r?null:i.jsx(yo,{className:"h-3 w-3 shrink-0 opacity-60"})]},`${t.label}-${n}`)})})}const Kt=p.forwardRef((e,t)=>p.createElement("div",Object.assign({},ze(e,["className"]),{ref:t,className:Z("panel-card text-card-foreground",e.className)})));Kt.displayName="Card";const Vn=p.forwardRef((e,t)=>p.createElement("div",Object.assign({},ze(e,["className"]),{ref:t,className:Z("flex flex-col space-y-1.5 p-5",e.className)})));Vn.displayName="CardHeader";const Hn=p.forwardRef((e,t)=>p.createElement("h3",Object.assign({},ze(e,["className"]),{ref:t,className:Z("text-lg font-semibold leading-none tracking-tight",e.className)})));Hn.displayName="CardTitle";const ko=p.forwardRef((e,t)=>p.createElement("p",Object.assign({},ze(e,["className"]),{ref:t,className:Z("text-sm text-muted-foreground",e.className)})));ko.displayName="CardDescription";const Qt=p.forwardRef((e,t)=>p.createElement("div",Object.assign({},ze(e,["className"]),{ref:t,className:Z("p-5 pt-0",e.className)})));Qt.displayName="CardContent";function dn(e){return p.createElement("div",Object.assign({},ze(e,["className"]),{className:Z("animate-pulse rounded-md bg-muted",e.className)}))}const lj=20;function ij(){const e=fs();let t=null;typeof e.projectId=="string"&&e.projectId.length>0&&(t=e.projectId);const n=zt(),[r,s]=p.useState([]),[o,l]=p.useState(null),[a,c]=p.useState(!1),[u,d]=p.useState(!0),[f,h]=p.useState(!1),y=p.useCallback(async(v,g)=>{if(!(!n||!t)){g?h(!0):d(!0);try{const k=await Zu(n,{projectId:t,commentsOnly:!0,epicsOnly:null,cursor:v,limit:lj});s(j=>g?j.concat(k.items):k.items),l(k.nextCursor),c(k.hasMore)}catch(k){Y.error(k instanceof Error?k.message:"Failed to load inbox")}finally{d(!1),h(!1)}}},[n,t]),x=p.useCallback(()=>{y(null,!1)},[y]);p.useEffect(()=>{y(null,!1)},[y]),p.useEffect(()=>{const v=()=>{s(g=>g.slice())};return window.addEventListener("task-bridge:read",v),()=>window.removeEventListener("task-bridge:read",v)},[]);const w=r.filter(v=>!Lc(v.taskId)).length;let b="Project";n!==null&&n.projectName!==null?b=n.projectName:t!==null&&(b=t);let m="/projects";return t!==null&&(m=`/projects/${t}/tasks`),i.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[i.jsx(bn,{breadcrumb:[{label:"Projects",to:"/projects"},{label:b,to:m},{label:"Inbox",to:null}],title:"Inbox",subtitle:w>0?`${w} unread`:"All caught up",actions:i.jsxs(H,{variant:"outline",size:"sm",onClick:x,disabled:u||f,children:[u?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):i.jsx(Ql,{className:"h-4 w-4"}),"Refresh"]})}),i.jsx("div",{className:"flex-1 overflow-y-auto p-5",children:i.jsxs(Kt,{children:[i.jsxs(Vn,{children:[i.jsxs(Hn,{className:"flex items-center gap-2 text-lg",children:[i.jsx(lN,{className:"h-5 w-5 text-primary"}),"Notifications"]}),i.jsx(ko,{children:w>0?`${w} unread`:"You are all caught up."})]}),i.jsxs(Qt,{className:"space-y-3",children:[u&&r.length===0?i.jsxs(i.Fragment,{children:[i.jsx(dn,{className:"h-16 w-full"}),i.jsx(dn,{className:"h-16 w-full"})]}):r.length===0?i.jsx("p",{className:"text-sm text-muted-foreground",children:"No comments yet."}):r.map(v=>{const g=!Lc(v.taskId);return i.jsxs(tr,{to:`/projects/${t}/tasks/${v.taskId}`,onClick:()=>{g&&xv(v.taskId)},className:Z("flex items-center justify-between rounded-xl border px-4 py-3 transition-colors",g?"border-primary/20 bg-primary/5 hover:border-primary/40 hover:bg-primary/10":"border-border/60 bg-background/40 opacity-80 hover:bg-accent/20"),children:[i.jsxs("div",{className:"min-w-0 pr-3",children:[i.jsx("p",{className:"text-sm font-medium",children:g?"A comment was added to your task":"Comment on your task"}),i.jsx("p",{className:"truncate text-sm text-muted-foreground",children:v.title||`Task #${v.taskId}`}),v.preview?i.jsx("p",{className:"line-clamp-1 text-xs text-muted-foreground",children:v.preview}):null,i.jsx("p",{className:"text-xs text-muted-foreground",children:Bu(v.activityAt!==null?v.activityAt:v.updatedAt!==null?v.updatedAt:v.createdAt)})]}),i.jsx(yo,{className:"h-4 w-4 shrink-0 text-muted-foreground"})]},v.taskId)}),i.jsx(yv,{loaded:r.length,hasMore:a,loading:f,onLoadMore:()=>{y(o,!0)}})]})]})})]})}const Ne=p.forwardRef((e,t)=>p.createElement("input",Object.assign({},ze(e,["className","type"]),{type:e.type,className:Z("flex h-10 w-full rounded-xl border border-white/[0.1] bg-[#111111] px-3 py-2 text-sm text-foreground ring-offset-black file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-black disabled:cursor-not-allowed disabled:opacity-50",e.className),ref:t})));Ne.displayName="Input";const xe=p.forwardRef((e,t)=>p.createElement("label",Object.assign({},ze(e,["className"]),{ref:t,className:Z("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",e.className)})));xe.displayName="Label";function aj({session:e}){const t=yn(),[n,r]=p.useState(""),[s,o]=p.useState(""),[l,a]=p.useState(""),[c,u]=p.useState(""),[d,f]=p.useState(!1);async function h(x){if(x.preventDefault(),u(""),s.length<6){u("New password must be at least 6 characters");return}if(s!==l){u("New passwords do not match");return}f(!0);try{await SN(e,{currentPassword:n,newPassword:s}),bo(Object.assign({},e,{mustChangePassword:!1})),t("/projects",{replace:!0})}catch(w){u(w instanceof Error?w.message:"Password change failed")}finally{f(!1)}}function y(){Qu(),t("/login",{replace:!0})}return i.jsx("div",{className:"flex h-full items-center justify-center bg-background px-4",children:i.jsxs(Kt,{className:"w-full max-w-md border-white/[0.08] bg-card shadow-2xl",children:[i.jsxs(Vn,{className:"space-y-6 text-center",children:[i.jsx("div",{className:"flex justify-center",children:i.jsx(Ei,{})}),i.jsxs("div",{children:[i.jsx(Hn,{className:"text-2xl",children:"Change your password"}),i.jsx(ko,{className:"mt-2",children:"You must set a new password before using Task Bridge."})]})]}),i.jsxs(Qt,{className:"space-y-5",children:[i.jsxs("form",{onSubmit:x=>void h(x),className:"space-y-4",children:[i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"current-password",children:"Current password"}),i.jsxs("div",{className:"relative",children:[i.jsx(tv,{className:"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"}),i.jsx(Ne,{id:"current-password",type:"password",className:"pl-10",value:n,onChange:x=>r(x.target.value),required:!0,disabled:d})]})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"new-password",children:"New password"}),i.jsx(Ne,{id:"new-password",type:"password",value:s,onChange:x=>o(x.target.value),required:!0,disabled:d,minLength:6})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"confirm-password",children:"Confirm new password"}),i.jsx(Ne,{id:"confirm-password",type:"password",value:l,onChange:x=>a(x.target.value),required:!0,disabled:d,minLength:6})]}),c&&i.jsx("p",{className:"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive",children:c}),i.jsx(H,{type:"submit",className:"h-11 w-full",disabled:d,children:d?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):"Update password"})]}),i.jsx(H,{type:"button",variant:"ghost",className:"w-full",onClick:y,children:"Sign out"})]})]})})}function cj(){const e=Jt();return e?e.mustChangePassword?i.jsx(aj,{session:e}):i.jsx(qe,{to:"/projects",replace:!0}):i.jsx(qe,{to:"/login",replace:!0})}function uj(){const e=yn(),[t,n]=p.useState(""),[r,s]=p.useState(""),[o,l]=p.useState(""),[a,c]=p.useState(!1),[u,d]=p.useState(!0);p.useEffect(()=>{const h=Jt();if(h){if(h.mustChangePassword){e("/change-password",{replace:!0});return}e(h.projectId?`/projects/${h.projectId}/tasks`:"/projects",{replace:!0});return}fv().then(({hasUsers:y})=>{y||e("/setup",{replace:!0})}).catch(()=>{}).finally(()=>d(!1))},[e]);async function f(h){h.preventDefault(),l(""),c(!0);try{const y=await kN({email:t,password:r});if(bo({token:y.token,userId:y.user.id,userName:y.user.name,userEmail:y.user.email,userRole:y.user.role,isSystemAdmin:y.user.isSystemAdmin,mustChangePassword:y.user.mustChangePassword,projectId:null,projectName:null}),y.user.mustChangePassword){e("/change-password",{replace:!0});return}e("/projects",{replace:!0})}catch(y){l(y instanceof Error?y.message:"Login failed")}finally{c(!1)}}return u?i.jsx("div",{className:"flex h-full items-center justify-center",children:i.jsx(ve,{className:"h-6 w-6 animate-spin text-muted-foreground"})}):i.jsx("div",{className:"flex h-full items-center justify-center bg-background px-4",children:i.jsxs(Kt,{className:"w-full max-w-md border-white/[0.08] bg-card shadow-2xl",children:[i.jsxs(Vn,{className:"space-y-6 text-center",children:[i.jsx("div",{className:"flex justify-center",children:i.jsx(Ei,{})}),i.jsxs("div",{children:[i.jsx(Hn,{className:"text-2xl",children:"Sign in"}),i.jsx(ko,{className:"mt-2",children:"Enter your email and password to continue"})]})]}),i.jsx(Qt,{className:"space-y-5",children:i.jsxs("form",{onSubmit:h=>void f(h),className:"space-y-4",children:[i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"email",children:"Email"}),i.jsxs("div",{className:"relative",children:[i.jsx(oN,{className:"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"}),i.jsx(Ne,{id:"email",type:"email",className:"pl-10",value:t,onChange:h=>n(h.target.value),placeholder:"you@example.com",required:!0,disabled:a})]})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"password",children:"Password"}),i.jsxs("div",{className:"relative",children:[i.jsx(tv,{className:"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"}),i.jsx(Ne,{id:"password",type:"password",className:"pl-10",value:r,onChange:h=>s(h.target.value),placeholder:"••••••••",required:!0,disabled:a,onKeyDown:h=>{if(h.key==="Enter"){h.preventDefault();const y=h.currentTarget.form;y&&y.requestSubmit()}}})]})]}),o&&i.jsx("p",{className:"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive",children:o}),i.jsxs(H,{type:"submit",className:"h-11 w-full",disabled:a,children:[a?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):i.jsx(rN,{className:"h-4 w-4"}),"Sign in"]})]})})]})})}function dj(){const e=yn(),[t,n]=p.useState(""),[r,s]=p.useState(""),[o,l]=p.useState(""),[a,c]=p.useState(""),[u,d]=p.useState(""),[f,h]=p.useState(!1);p.useEffect(()=>{fv().then(({hasUsers:x})=>{x&&e("/login",{replace:!0})}).catch(()=>{})},[e]);async function y(x){if(x.preventDefault(),d(""),o!==a){d("Passwords do not match");return}if(o.length<6){d("Password must be at least 6 characters");return}h(!0);try{await bN({name:t,email:r,password:o}),e("/login",{replace:!0})}catch(w){d(w instanceof Error?w.message:"Setup failed")}finally{h(!1)}}return i.jsx("div",{className:"flex h-full items-center justify-center bg-background px-4",children:i.jsxs(Kt,{className:"w-full max-w-md border-white/[0.08] bg-card shadow-2xl",children:[i.jsxs(Vn,{className:"space-y-6 text-center",children:[i.jsx("div",{className:"flex justify-center",children:i.jsx(Ei,{})}),i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-center gap-2 mb-1",children:[i.jsx(ap,{className:"h-5 w-5 text-primary"}),i.jsx(Hn,{className:"text-2xl",children:"Admin Setup"})]}),i.jsx(ko,{children:"Create the administrator account to get started"})]})]}),i.jsx(Qt,{children:i.jsxs("form",{onSubmit:x=>void y(x),className:"space-y-4",children:[i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"name",children:"Full Name"}),i.jsx(Ne,{id:"name",type:"text",value:t,onChange:x=>n(x.target.value),placeholder:"John Doe",required:!0,disabled:f})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"email",children:"Email"}),i.jsx(Ne,{id:"email",type:"email",value:r,onChange:x=>s(x.target.value),placeholder:"admin@example.com",required:!0,disabled:f})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"password",children:"Password"}),i.jsx(Ne,{id:"password",type:"password",value:o,onChange:x=>l(x.target.value),placeholder:"Min. 6 characters",required:!0,disabled:f})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"confirm",children:"Confirm Password"}),i.jsx(Ne,{id:"confirm",type:"password",value:a,onChange:x=>c(x.target.value),placeholder:"Repeat password",required:!0,disabled:f})]}),u&&i.jsx("p",{className:"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive",children:u}),i.jsxs(H,{type:"submit",className:"h-11 w-full",disabled:f,children:[f?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):i.jsx(ap,{className:"h-4 w-4"}),"Create Admin Account"]})]})})]})})}const Zl=p.forwardRef((e,t)=>p.createElement("select",Object.assign({},ze(e,["className","style"]),{className:Z("flex h-10 w-full rounded-xl border border-white/[0.1] bg-[#111111] px-3 py-2 text-sm text-foreground ring-offset-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-black disabled:cursor-not-allowed disabled:opacity-50",e.className),style:e.style,ref:t})));Zl.displayName="Select";const mp={admin:"Admin","read-write":"Read & Write",read:"Read only"},gp={admin:"default","read-write":"secondary",read:"outline"};function fj(){const e=zt(),[t,n]=p.useState([]),[r,s]=p.useState(!0),[o,l]=p.useState(!1),[a,c]=p.useState(""),[u,d]=p.useState(""),[f,h]=p.useState(""),[y,x]=p.useState("read-write"),[w,b]=p.useState(!1),[m,v]=p.useState(""),g=p.useCallback(async()=>{if(e)try{s(!0),n(await jN(e))}catch{Y.error("Failed to load users")}finally{s(!1)}},[e]);p.useEffect(()=>{g()},[g]);async function k(S){if(S.preventDefault(),!!e){v(""),b(!0);try{const C=await CN(e,{name:a,email:u,password:f,role:y});n(U=>U.concat([C])),l(!1),c(""),d(""),h(""),x("read-write"),Y.success("User created")}catch(C){v(C instanceof Error?C.message:"Failed to create user")}finally{b(!1)}}}async function j(S,C){if(e&&confirm(`Remove ${C}? This cannot be undone.`))try{await TN(e,S),n(U=>U.filter(T=>T.id!==S)),Y.success("User removed")}catch(U){Y.error(U instanceof Error?U.message:"Failed to remove user")}}async function E(S,C){if(e)try{const U=await EN(e,S,{name:null,role:C});n(T=>T.map(V=>V.id===S?U:V)),Y.success("Role updated")}catch(U){Y.error(U instanceof Error?U.message:"Failed to update role")}}return e?i.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[i.jsx(bn,{title:"Team members",subtitle:"Manage who can sign in and what they can access",actions:i.jsxs(hs,{open:o,onOpenChange:l,children:[i.jsx(hN,{asChild:!0,children:i.jsxs(H,{size:"sm",children:[i.jsx(Ct,{className:"h-4 w-4"}),"Add member"]})}),i.jsxs(wr,{className:"border-white/[0.08] bg-[#111111]",children:[i.jsx(br,{children:i.jsx(kr,{children:"Add team member"})}),i.jsxs("form",{onSubmit:S=>void k(S),className:"space-y-4 pt-2",children:[i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{children:"Full name"}),i.jsx(Ne,{value:a,onChange:S=>c(S.target.value),placeholder:"Jane Smith",required:!0})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{children:"Email"}),i.jsx(Ne,{type:"email",value:u,onChange:S=>d(S.target.value),placeholder:"jane@example.com",required:!0})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{children:"Password"}),i.jsx(Ne,{type:"password",value:f,onChange:S=>h(S.target.value),placeholder:"Min. 6 characters",required:!0,minLength:6})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{children:"Role"}),i.jsxs(Zl,{value:y,onChange:S=>x(S.target.value),children:[i.jsx("option",{value:"read",children:"Read only"}),i.jsx("option",{value:"read-write",children:"Read & Write"}),i.jsx("option",{value:"admin",children:"Admin"})]})]}),m?i.jsx("p",{className:"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive",children:m}):null,i.jsxs("div",{className:"flex justify-end gap-2",children:[i.jsx(H,{type:"button",variant:"ghost",onClick:()=>l(!1),children:"Cancel"}),i.jsxs(H,{type:"submit",disabled:w,children:[w?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):null,w?"Creating…":"Create"]})]})]})]})]})}),i.jsx("div",{className:"flex-1 overflow-y-auto px-8 py-6",children:r?i.jsx("div",{className:"flex items-center justify-center py-16",children:i.jsx(ve,{className:"h-6 w-6 animate-spin text-muted-foreground"})}):t.length===0?i.jsx("div",{className:"panel-card p-10 text-center text-sm text-muted-foreground",children:"No team members yet. Add one to get started."}):i.jsx("div",{className:"panel-card divide-y divide-white/[0.06] overflow-hidden",children:t.map(S=>i.jsxs("div",{className:"flex items-center justify-between gap-4 px-5 py-4",children:[i.jsxs("div",{className:"min-w-0 space-y-0.5",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary/15 text-xs font-semibold uppercase text-primary",children:S.name.charAt(0)}),i.jsx("span",{className:"truncate font-medium text-white",children:S.name}),S.isSystemAdmin?i.jsx("span",{title:"System admin — cannot be deleted",children:i.jsx(dN,{className:"h-3.5 w-3.5 shrink-0 text-muted-foreground"})}):null]}),i.jsx("p",{className:"truncate pl-10 text-sm text-muted-foreground",children:S.email})]}),i.jsxs("div",{className:"ml-4 flex shrink-0 items-center gap-3",children:[S.isSystemAdmin?i.jsx(dv,{variant:S.role in gp?gp[S.role]:"outline",children:S.role in mp?mp[S.role]:S.role}):i.jsxs(Zl,{value:S.role,onChange:C=>void E(S.id,C.target.value),className:"h-9 w-auto min-w-[9.5rem] py-1 text-xs",children:[i.jsx("option",{value:"read",children:"Read only"}),i.jsx("option",{value:"read-write",children:"Read & Write"}),i.jsx("option",{value:"admin",children:"Admin"})]}),S.isSystemAdmin?null:i.jsx(H,{size:"icon",variant:"ghost",className:"h-8 w-8 text-muted-foreground hover:text-destructive",onClick:()=>void j(S.id,S.name),title:"Remove user",children:i.jsx(gn,{className:"h-4 w-4"})})]})]},S.id))})})]}):null}var pj=Object.defineProperty,ql=Object.getOwnPropertySymbols,wv=Object.prototype.hasOwnProperty,bv=Object.prototype.propertyIsEnumerable,vp=(e,t,n)=>t in e?pj(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Mc=(e,t)=>{for(var n in t||(t={}))wv.call(t,n)&&vp(e,n,t[n]);if(ql)for(var n of ql(t))bv.call(t,n)&&vp(e,n,t[n]);return e},Oc=(e,t)=>{var n={};for(var r in e)wv.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&ql)for(var r of ql(e))t.indexOf(r)<0&&bv.call(e,r)&&(n[r]=e[r]);return n};/**
|
|
346
|
+
*/const iv=te("ZoomOut",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]),hs=WS,hN=VS,mN=HS,av=p.forwardRef((e,t)=>p.createElement(Gg,Object.assign({},ze(e,["className"]),{ref:t,className:Z("fixed inset-0 z-50 bg-black/70 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",e.className)})));av.displayName=Gg.displayName;const wr=p.forwardRef((e,t)=>p.createElement(mN,null,p.createElement(av,null),p.createElement(Yg,Object.assign({},ze(e,["className","children"]),{ref:t,className:Z("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-card p-6 shadow-xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-xl",e.className)}),e.children,p.createElement(GS,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"},p.createElement(Gu,{className:"h-4 w-4"}),p.createElement("span",{className:"sr-only"},"Close")))));wr.displayName=Yg.displayName;function br(e){return p.createElement("div",Object.assign({},ze(e,["className"]),{className:Z("flex flex-col space-y-1.5 text-center sm:text-left",e.className)}))}br.displayName="DialogHeader";function wo(e){return p.createElement("div",Object.assign({},ze(e,["className"]),{className:Z("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",e.className)}))}wo.displayName="DialogFooter";const kr=p.forwardRef((e,t)=>p.createElement(Kg,Object.assign({},ze(e,["className"]),{ref:t,className:Z("text-lg font-semibold leading-none tracking-tight",e.className)})));kr.displayName=Kg.displayName;const cv=p.forwardRef((e,t)=>p.createElement(Qg,Object.assign({},ze(e,["className"]),{ref:t,className:Z("text-sm text-muted-foreground",e.className)})));cv.displayName=Qg.displayName;const uv=p.createContext(null);function gN({children:e}){const[t,n]=p.useState(!1),[r,s]=p.useState({title:null,message:"",confirmLabel:null,cancelLabel:null}),o=p.useRef(null),l=p.useCallback((f,h=null)=>new Promise(y=>{o.current=y;const x={title:null,confirmLabel:null,cancelLabel:null,message:f};h!==null&&(x.title=h.title,x.confirmLabel=h.confirmLabel,x.cancelLabel=h.cancelLabel),s(x),n(!0)}),[]);function a(f){n(!1);const h=o.current;h!==null&&h(f),o.current=null}let c="Delete?";r.title!==null&&(c=r.title);let u="Cancel";r.cancelLabel!==null&&(u=r.cancelLabel);let d="Delete";return r.confirmLabel!==null&&(d=r.confirmLabel),i.jsxs(uv.Provider,{value:{confirmDestructive:l},children:[e,i.jsx(hs,{open:t,onOpenChange:f=>{f||a(!1)},children:i.jsxs(wr,{className:"max-w-md border-white/[0.1] bg-[#111] sm:rounded-xl [&>button]:hidden",children:[i.jsxs(br,{children:[i.jsx(kr,{children:c}),i.jsx(cv,{children:r.message})]}),i.jsxs(wo,{className:"gap-2 sm:gap-2",children:[i.jsx(H,{type:"button",variant:"outline",onClick:()=>a(!1),children:u}),i.jsx(H,{type:"button",variant:"destructive",onClick:()=>a(!0),children:d})]})]})})]})}function Yu(){const e=p.useContext(uv);if(!e)throw new Error("useConfirm must be used within ConfirmDialogProvider");return e}function Ei(e={}){let t=null;"className"in e&&(e.className===null?t=null:typeof e.className=="string"&&(t=e.className));let n=!1;"compact"in e&&e.compact===!0&&(n=!0);let r="/projects";"linkTo"in e&&(e.linkTo===null?r=null:typeof e.linkTo=="string"&&(r=e.linkTo));const s=qt(),o=r,l=i.jsxs(i.Fragment,{children:[i.jsxs("div",{className:Z("relative flex shrink-0 items-center justify-center rounded-lg bg-primary/15 ring-1 ring-primary/25",n?"h-8 w-8":"h-10 w-10"),children:[i.jsx(nv,{className:Z("text-primary",n?"h-4 w-4":"h-5 w-5")}),i.jsx(cN,{className:Z("absolute rounded-full bg-card p-0.5 text-primary",n?"-bottom-0.5 -right-0.5 h-3 w-3":"-bottom-1 -right-1 h-4 w-4")})]}),i.jsxs("div",{className:"min-w-0",children:[i.jsx("p",{className:Z("font-semibold tracking-tight",n?"text-sm":"text-base"),children:"Task Bridge"}),n?i.jsx("p",{className:"text-[10px] text-muted-foreground",children:"v0.1"}):null]})]});if(!o)return i.jsx("div",{className:Z("flex items-center gap-2.5",t),children:l});let a=null;return s.pathname.startsWith("/projects/")&&(a={from:s.pathname}),i.jsx(tr,{to:o,state:a,className:Z("flex items-center gap-2.5 rounded-lg transition-colors hover:bg-white/[0.04]",n?"-mx-1 px-1 py-0.5":"px-1 py-0.5",t),children:l})}const vN=ig("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground",secondary:"border-transparent bg-secondary text-secondary-foreground",outline:"text-foreground",success:"border-transparent bg-success/15 text-success",warn:"border-transparent bg-warn/15 text-warn",muted:"border-transparent bg-muted text-muted-foreground"}},defaultVariants:{variant:"default"}});function dv(e){return p.createElement("div",Object.assign({},ze(e,["className","variant"]),{className:Z(vN({variant:e.variant}),e.className)}))}const Ku="task-bridge.session.v2";function Jt(){try{const e=localStorage.getItem(Ku);if(!e)return null;const t=JSON.parse(e),n=t.token,r=t.userId;return n===null||n.trim()===""||r===null||r.trim()===""?null:Object.assign({},t,{mustChangePassword:t.mustChangePassword===!0})}catch{return null}}function bo(e){localStorage.setItem(Ku,JSON.stringify(e))}function Qu(){localStorage.removeItem(Ku)}function Ic(e,t){const n=Jt();n&&bo(Object.assign({},n,{projectId:e,projectName:t}))}function xN(){const e=Jt();e&&bo(Object.assign({},e,{projectId:null,projectName:null}))}const ml="empty";class Wn extends Error{constructor(n,r){super(n);ge(this,"status");this.status=r}}function yN(e){return{Accept:"application/json",Authorization:`Bearer ${e.token}`}}async function wN(e){try{const t=await e.json(),n=t.details;if(n!==null&&n.code==="PASSWORD_CHANGE_REQUIRED")return"PASSWORD_CHANGE_REQUIRED";if(t.error)return t.error}catch{return e.statusText||"Request failed"}return e.statusText||"Request failed"}async function ue(e,t,n=null){let r={};if(n!==null){const c=n.headers;typeof c<"u"&&c!==null&&(r=c)}let s={};n!==null&&(s=n);const o=Object.assign({},yN(e),r),l=await fetch(t,Object.assign({},s,{headers:o}));if(l.status===401)throw Qu(),window.location.href="/app/login",new Wn("Session expired",401);if(!l.ok){const c=await wN(l);if(c==="PASSWORD_CHANGE_REQUIRED"){const u=Jt();throw u&&bo(Object.assign({},u,{mustChangePassword:!0})),window.location.href="/app/change-password",new Wn("Password change required",403)}throw new Wn(c,l.status)}if(l.status===204)return null;const a=await l.text();return a?JSON.parse(a):null}async function fv(){const e=await fetch("/api/auth/status");if(!e.ok)throw new Wn("Failed to check status",e.status);return e.json()}async function bN(e){const t=await fetch("/api/auth/setup",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){const n=await t.json().catch(()=>({}));let r="Setup failed";throw n.error!==null&&(r=n.error),new Wn(r,t.status)}return t.json()}async function kN(e){const t=await fetch("/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){const n=await t.json().catch(()=>({}));let r="Login failed";throw n.error!==null&&(r=n.error),new Wn(r,t.status)}return t.json()}async function SN(e,t){const n=await fetch("/api/auth/change-password",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.token}`},body:JSON.stringify(t)});if(!n.ok){const r=await n.json().catch(()=>({}));let s="Password change failed";throw r.error!==null&&(s=r.error),new Wn(s,n.status)}return n.json()}function NN(e){let t="";return typeof window<"u"&&(t=window.location.origin),`taskbridge://auth?server=${encodeURIComponent(t)}&token=${encodeURIComponent(e.token)}`}async function jN(e){const t=await ue(e,"/api/admin/users");return t.users!==null?t.users:[]}async function CN(e,t){return(await ue(e,"/api/admin/users",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).user}async function EN(e,t,n){return(await ue(e,`/api/admin/users/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).user}async function TN(e,t){await ue(e,`/api/admin/users/${t}`,{method:"DELETE"})}async function pv(e){const t=await ue(e,"/api/projects");return t.projects!==null?t.projects:[]}async function RN(e,t){return ue(e,"/api/projects",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t.name,id:t.id.trim(),repoPath:t.repoPath,description:t.description.trim(),workflowTemplateId:t.workflowTemplateId.trim()||ml})})}async function IN(e,t,n){return ue(e,`/api/projects/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}async function Xu(e){const t=await ue(e,"/api/workflow-templates");return t.items!==null?t.items:[]}async function PN(e,t){return ue(e,`/api/workflow-templates/${t}`)}async function _N(e,t){return ue(e,"/api/workflow-templates",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function LN(e,t,n){return ue(e,`/api/workflow-templates/${t}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({stages:n})})}async function MN(e,t){await ue(e,`/api/workflow-templates/${t}`,{method:"DELETE"})}const ya=new Set(["empty","lean-sdlc"]);async function cp(e,t){const n=await fetch(`/api/workflow-templates/${t}/export`,{headers:{Authorization:`Bearer ${e.token}`}});if(!n.ok)throw new Wn("Export failed",n.status);const r=await n.blob(),s=n.headers.get("Content-Disposition");let o="";s!==null&&(o=s);const l=/filename="([^"]+)"/.exec(o);let a=`${t}.json`;if(l!==null&&l.length>1){const d=l[1];typeof d=="string"&&(a=d)}const c=URL.createObjectURL(r),u=document.createElement("a");u.href=c,u.download=a,u.click(),URL.revokeObjectURL(c)}async function ON(e,t){return ue(e,"/api/workflow-templates/import",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function AN(e,t){let n="";return t.description!==null&&(n=t.description),ue(e,"/api/epics",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectId:t.projectId,title:t.title,description:n})})}async function DN(e,t){let n="";return t.description!==null&&(n=t.description),ue(e,"/api/tasks",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parentId:t.parentId,title:t.title,description:n,stageId:t.stageId})})}async function Zu(e,t={projectId:null,commentsOnly:null,epicsOnly:null,cursor:null,limit:null}){const n=new URLSearchParams;t.projectId&&n.set("projectId",t.projectId),t.commentsOnly&&n.set("commentsOnly","true"),t.epicsOnly&&n.set("epicsOnly","true"),t.cursor&&n.set("cursor",t.cursor),t.limit&&n.set("limit",String(t.limit));let r="";return n.toString()&&(r=`?${n.toString()}`),ue(e,`/api/inbox${r}`)}async function up(e,t={projectId:null,commentsOnly:null,epicsOnly:null,limit:null}){const n=[];let r=null;do{let s=100;t.limit!==null&&(s=t.limit);const o=await Zu(e,Object.assign({},t,{cursor:r,limit:s}));for(const l of o.items)n.push(l);r=o.nextCursor}while(r);return n}async function dp(e,t){return ue(e,`/api/tasks/${t}`)}async function zN(e,t,n){return ue(e,`/api/tasks/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({comment:{text:n,by:"web"}})})}async function $N(e,t,n){return ue(e,`/api/tasks/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({description:n})})}async function hv(e,t){return ue(e,`/api/projects/${t}/workflow`)}async function FN(e,t,n){return ue(e,`/api/projects/${t}/workflow`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}async function BN(e,t,n){return ue(e,`/api/projects/${t}/members`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n.name,role:n.role})})}async function UN(e,t,n){await ue(e,`/api/projects/${t}/members/${n}`,{method:"DELETE"})}async function mv(e){const t=await ue(e,"/api/libraries");return t.items!==null?t.items:[]}async function gv(e,t){return ue(e,`/api/libraries/${t}`)}async function WN(e,t){return ue(e,"/api/libraries",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function VN(e,t,n){return ue(e,`/api/libraries/${t}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}async function HN(e,t){await ue(e,`/api/libraries/${t}`,{method:"DELETE"})}async function GN(e,t,n){return ue(e,`/api/libraries/${t}/documents`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}async function YN(e,t){return ue(e,`/api/library-documents/${t}`)}async function KN(e,t,n,r){return ue(e,`/api/libraries/${t}/documents/${n}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)})}async function QN(e,t,n){await ue(e,`/api/libraries/${t}/documents/${n}`,{method:"DELETE"})}async function XN(e,t,n){const r=await ue(e,`/api/library-documents/${t}/links`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({taskId:n})});return r.items!==null?r.items:[]}async function ZN(e,t,n){await ue(e,`/api/library-documents/${t}/links/${n}`,{method:"DELETE"})}async function fp(e,t,n,r){return ue(e,`/api/tasks/${t}/work-status`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({workStatus:n,claimedBy:r})})}const Pc="task-bridge.read-tasks",_c="task-bridge.notified-comments";function qN(e){if(Number.isInteger(e))return e;if(e===null)return null;const t=String(e).trim();if(t.length===0)return null;const n=Number(t);return Number.isInteger(n)?n:null}function Ti(e){try{const t=localStorage.getItem(e);if(!t)return[];const n=JSON.parse(t);if(!Array.isArray(n))return[];const r=[];for(const s of n){const o=qN(s);o!==null&&r.push(o)}return r}catch{return[]}}function vv(e,t){localStorage.setItem(e,JSON.stringify(Array.from(new Set(t))))}function Lc(e){return Ti(Pc).includes(e)}function xv(e){Number.isInteger(e)&&(vv(Pc,Ti(Pc).concat([e])),window.dispatchEvent(new CustomEvent("task-bridge:read")))}function JN(e){return Ti(_c).includes(e)}function pp(e){Number.isInteger(e)&&vv(_c,Ti(_c).concat([e]))}function ej(e){return e.filter(t=>t.status==="ready"&&!Lc(t.taskId)).length}function tj(e,t){const[n,r]=p.useState([]),[s,o]=p.useState(!0),l=p.useRef(!1),a=p.useCallback(async()=>{if(!(!e||!t)){o(!0);try{const d=await up(e,{projectId:t,commentsOnly:!0,epicsOnly:null,limit:100});for(const h of d){if(!l.current){pp(h.taskId);continue}JN(h.taskId)||(pp(h.taskId),Y.message("A comment was added to your task",{description:h.title||`Task #${h.taskId}`}))}l.current=!0;const f=await up(e,{projectId:t,commentsOnly:null,epicsOnly:null,limit:100});r(f)}finally{o(!1)}}},[e,t]);p.useEffect(()=>{a();const d=window.setInterval(()=>void a(),3e4),f=()=>void a();return window.addEventListener("task-bridge:read",f),()=>{window.clearInterval(d),window.removeEventListener("task-bridge:read",f)}},[a]);const c=n.filter(d=>d.status==="ready"),u=n.filter(d=>d.status==="sent");return{items:n,commentItems:c,openItems:u,loading:s,refresh:a}}function zt(){const[e]=p.useState(()=>Jt());return e}const hp={admin:"Admin","read-write":"Read & Write",read:"Read only"};function nj(){const e=qt(),t=yn(),n=zt(),r=Vl("/projects/:projectId/*",e.pathname);let s=null;if(r!==null){const y=r.params.projectId;typeof y=="string"&&y.length>0&&(s=y)}let o=s;o===null&&n!==null&&n.projectId!==null&&(o=n.projectId);const l=s;let a=l;n!==null&&n.projectName!==null?a=n.projectName:l===null&&o!==null&&(a=o);const{commentItems:c}=tj(n,l),u=ej(c);function d(){Qu(),t("/login",{replace:!0})}const f=n!==null&&n.userRole==="admin";let h="";if(n!==null){const y=n.userRole;if(y in hp){const x=hp[y];typeof x=="string"&&(h=x)}h===""&&(h=y)}return i.jsxs("aside",{className:"app-sidebar",children:[i.jsx("div",{className:"flex h-14 shrink-0 items-center border-b border-white/[0.07] px-4",children:i.jsx(Ei,{compact:!0})}),i.jsxs("nav",{className:"flex-1 space-y-4 overflow-y-auto px-2 py-3",children:[i.jsxs("div",{className:"space-y-0.5",children:[i.jsx(nn,{to:"/projects",label:"Projects",icon:ev,end:!0}),i.jsx(nn,{to:"/library",label:"Library",icon:Kl}),i.jsx(nn,{to:"/workflow-templates",label:"Workflow templates",icon:ip})]}),l?i.jsxs("div",{className:"space-y-0.5",children:[i.jsx("p",{className:"truncate px-3 pb-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground",children:a}),i.jsx(nn,{to:`/projects/${l}/tasks`,label:"Epics",icon:Rc}),i.jsx(nn,{to:`/projects/${l}/inbox`,label:"Inbox",icon:nN,badge:u}),i.jsx(nn,{to:`/projects/${l}/mobile`,label:"Mobile",icon:ov}),i.jsx(nn,{to:`/projects/${l}/workflow`,label:"Pipeline",icon:ip})]}):o?i.jsxs("div",{className:"space-y-0.5",children:[i.jsx("p",{className:"px-3 pb-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground",children:"Recent project"}),i.jsx(nn,{to:`/projects/${o}/tasks`,label:a!==null?a:"Open project",icon:Rc})]}):null,f?i.jsxs("div",{className:"space-y-0.5",children:[i.jsx("p",{className:"px-3 pb-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground",children:"Admin"}),i.jsx(nn,{to:"/admin/users",label:"Team members",icon:pN})]}):null]}),i.jsxs("div",{className:"shrink-0 border-t border-white/[0.07] p-2 space-y-1",children:[n?i.jsxs("div",{className:"flex items-center gap-2.5 rounded-lg px-3 py-2",children:[i.jsx("div",{className:"flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary/20 text-xs font-semibold text-primary uppercase",children:n.userName.charAt(0)}),i.jsxs("div",{className:"flex-1 min-w-0",children:[i.jsx("p",{className:"truncate text-sm font-medium text-foreground leading-none",children:n.userName}),i.jsx("p",{className:"truncate text-[11px] text-muted-foreground mt-0.5",children:h})]}),i.jsx(dv,{variant:"outline",className:"text-[10px] px-1.5 py-0 shrink-0 hidden sm:flex",children:h})]}):null,i.jsxs(H,{variant:"ghost",size:"sm",className:"h-9 w-full justify-start rounded-lg text-muted-foreground hover:bg-white/[0.05] hover:text-foreground",onClick:d,children:[i.jsx(sN,{className:"h-4 w-4"}),"Sign out"]})]})]})}function nn(e){let t=0;"badge"in e&&typeof e.badge=="number"&&(t=e.badge);let n=!1;"end"in e&&e.end===!0&&(n=!0);const{to:r,label:s,icon:o}=e;return i.jsxs(jw,{to:r,end:n,className:({isActive:l})=>Z("flex h-9 items-center gap-2.5 rounded-lg px-3 text-sm transition-colors",l?"bg-white/[0.09] font-medium text-white":"text-muted-foreground hover:bg-white/[0.04] hover:text-foreground"),children:[i.jsx(o,{className:"h-4 w-4 shrink-0 opacity-90"}),i.jsx("span",{className:"flex-1 truncate",children:s}),i.jsx("span",{className:Z("flex h-5 min-w-[1.25rem] items-center justify-center rounded-full px-1.5 text-[11px] font-medium",t>0?"bg-primary text-primary-foreground":"invisible"),"aria-hidden":t<=0,children:t>0?t:0})]})}function rj(){return i.jsxs("div",{className:"app-shell",children:[i.jsx(nj,{}),i.jsx("main",{className:"app-main flex min-h-0 flex-1 flex-col overflow-y-auto",children:i.jsx(ng,{})})]})}function sj(){const{projectId:e}=fs(),t=yn(),n=zt(),r=p.useRef(null);return p.useEffect(()=>{if(!n||!e||r.current===e)return;let s=!0;return pv(n).then(o=>{if(!s)return;const l=o.find(a=>a.id===e);if(!l){t("/projects",{replace:!0});return}Ic(l.id,l.name),r.current=e}),()=>{s=!1}},[n,e,t]),n?e?i.jsx(ng,{}):i.jsx(qe,{to:"/projects",replace:!0}):i.jsx(qe,{to:"/login",replace:!0})}function yv(e){let t=!1;"loading"in e&&(t=e.loading===!0);const{loaded:n,hasMore:r,onLoadMore:s}=e;return n===0&&!r?null:i.jsxs("div",{className:"flex flex-col items-center gap-2 pt-2",children:[i.jsxs("p",{className:"text-xs text-muted-foreground",children:[n," loaded"]}),r?i.jsxs(H,{variant:"outline",size:"sm",disabled:t,onClick:s,children:[t?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):null,"Load more"]}):n>0?i.jsx("p",{className:"text-xs text-muted-foreground",children:"End of list"}):null]})}function bn(e){let t=null;"breadcrumb"in e&&(e.breadcrumb===null?t=null:Array.isArray(e.breadcrumb)&&(t=e.breadcrumb));let n=null;"subtitle"in e&&(e.subtitle===null?n=null:typeof e.subtitle=="string"&&(n=e.subtitle));let r=null;"actions"in e&&e.actions!==null&&(r=e.actions);let s=null;"className"in e&&(e.className===null?s=null:typeof e.className=="string"&&(s=e.className));const{title:o}=e;return i.jsxs("div",{className:Z("page-toolbar flex-wrap",s),children:[i.jsxs("div",{className:"min-w-0",children:[t&&t.length>0?i.jsx(oj,{items:t}):null,i.jsx("h1",{className:"truncate text-lg font-semibold tracking-tight text-white",children:o}),n?i.jsx("p",{className:"text-xs text-muted-foreground",children:n}):null]}),r?i.jsx("div",{className:"flex flex-wrap items-center gap-2",children:r}):null]})}function oj({items:e}){return i.jsx("nav",{className:"mb-1 flex items-center gap-1 text-xs text-muted-foreground",children:e.map((t,n)=>{const r=n===e.length-1;return i.jsxs(p.Fragment,{children:[t.to&&!r?i.jsx(tr,{to:t.to,className:"max-w-[12rem] truncate transition-colors hover:text-foreground",children:t.label}):i.jsx("span",{className:Z("max-w-[12rem] truncate",r&&"text-foreground"),children:t.label}),r?null:i.jsx(yo,{className:"h-3 w-3 shrink-0 opacity-60"})]},`${t.label}-${n}`)})})}const Kt=p.forwardRef((e,t)=>p.createElement("div",Object.assign({},ze(e,["className"]),{ref:t,className:Z("panel-card text-card-foreground",e.className)})));Kt.displayName="Card";const Vn=p.forwardRef((e,t)=>p.createElement("div",Object.assign({},ze(e,["className"]),{ref:t,className:Z("flex flex-col space-y-1.5 p-5",e.className)})));Vn.displayName="CardHeader";const Hn=p.forwardRef((e,t)=>p.createElement("h3",Object.assign({},ze(e,["className"]),{ref:t,className:Z("text-lg font-semibold leading-none tracking-tight",e.className)})));Hn.displayName="CardTitle";const ko=p.forwardRef((e,t)=>p.createElement("p",Object.assign({},ze(e,["className"]),{ref:t,className:Z("text-sm text-muted-foreground",e.className)})));ko.displayName="CardDescription";const Qt=p.forwardRef((e,t)=>p.createElement("div",Object.assign({},ze(e,["className"]),{ref:t,className:Z("p-5 pt-0",e.className)})));Qt.displayName="CardContent";function dn(e){return p.createElement("div",Object.assign({},ze(e,["className"]),{className:Z("animate-pulse rounded-md bg-muted",e.className)}))}const lj=20;function ij(){const e=fs();let t=null;typeof e.projectId=="string"&&e.projectId.length>0&&(t=e.projectId);const n=zt(),[r,s]=p.useState([]),[o,l]=p.useState(null),[a,c]=p.useState(!1),[u,d]=p.useState(!0),[f,h]=p.useState(!1),y=p.useCallback(async(v,g)=>{if(!(!n||!t)){g?h(!0):d(!0);try{const k=await Zu(n,{projectId:t,commentsOnly:!0,epicsOnly:null,cursor:v,limit:lj});s(j=>g?j.concat(k.items):k.items),l(k.nextCursor),c(k.hasMore)}catch(k){Y.error(k instanceof Error?k.message:"Failed to load inbox")}finally{d(!1),h(!1)}}},[n,t]),x=p.useCallback(()=>{y(null,!1)},[y]);p.useEffect(()=>{y(null,!1)},[y]),p.useEffect(()=>{const v=()=>{s(g=>g.slice())};return window.addEventListener("task-bridge:read",v),()=>window.removeEventListener("task-bridge:read",v)},[]);const w=r.filter(v=>!Lc(v.taskId)).length;let b="Project";n!==null&&n.projectName!==null?b=n.projectName:t!==null&&(b=t);let m="/projects";return t!==null&&(m=`/projects/${t}/tasks`),i.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[i.jsx(bn,{breadcrumb:[{label:"Projects",to:"/projects"},{label:b,to:m},{label:"Inbox",to:null}],title:"Inbox",subtitle:w>0?`${w} unread`:"All caught up",actions:i.jsxs(H,{variant:"outline",size:"sm",onClick:x,disabled:u||f,children:[u?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):i.jsx(Ql,{className:"h-4 w-4"}),"Refresh"]})}),i.jsx("div",{className:"flex-1 overflow-y-auto p-5",children:i.jsxs(Kt,{children:[i.jsxs(Vn,{children:[i.jsxs(Hn,{className:"flex items-center gap-2 text-lg",children:[i.jsx(lN,{className:"h-5 w-5 text-primary"}),"Notifications"]}),i.jsx(ko,{children:w>0?`${w} unread`:"You are all caught up."})]}),i.jsxs(Qt,{className:"space-y-3",children:[u&&r.length===0?i.jsxs(i.Fragment,{children:[i.jsx(dn,{className:"h-16 w-full"}),i.jsx(dn,{className:"h-16 w-full"})]}):r.length===0?i.jsx("p",{className:"text-sm text-muted-foreground",children:"No comments yet."}):r.map(v=>{const g=!Lc(v.taskId);return i.jsxs(tr,{to:`/projects/${t}/tasks/${v.taskId}`,onClick:()=>{g&&xv(v.taskId)},className:Z("flex items-center justify-between rounded-xl border px-4 py-3 transition-colors",g?"border-primary/20 bg-primary/5 hover:border-primary/40 hover:bg-primary/10":"border-border/60 bg-background/40 opacity-80 hover:bg-accent/20"),children:[i.jsxs("div",{className:"min-w-0 pr-3",children:[i.jsx("p",{className:"text-sm font-medium",children:g?"A comment was added to your task":"Comment on your task"}),i.jsx("p",{className:"truncate text-sm text-muted-foreground",children:v.title||`Task #${v.taskId}`}),v.preview?i.jsx("p",{className:"line-clamp-1 text-xs text-muted-foreground",children:v.preview}):null,i.jsx("p",{className:"text-xs text-muted-foreground",children:Bu(v.activityAt!==null?v.activityAt:v.updatedAt!==null?v.updatedAt:v.createdAt)})]}),i.jsx(yo,{className:"h-4 w-4 shrink-0 text-muted-foreground"})]},v.taskId)}),i.jsx(yv,{loaded:r.length,hasMore:a,loading:f,onLoadMore:()=>{y(o,!0)}})]})]})})]})}const Ne=p.forwardRef((e,t)=>p.createElement("input",Object.assign({},ze(e,["className","type"]),{type:e.type,className:Z("flex h-10 w-full rounded-xl border border-white/[0.1] bg-[#111111] px-3 py-2 text-sm text-foreground ring-offset-black file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-black disabled:cursor-not-allowed disabled:opacity-50",e.className),ref:t})));Ne.displayName="Input";const xe=p.forwardRef((e,t)=>p.createElement("label",Object.assign({},ze(e,["className"]),{ref:t,className:Z("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",e.className)})));xe.displayName="Label";function aj({session:e}){const t=yn(),[n,r]=p.useState(""),[s,o]=p.useState(""),[l,a]=p.useState(""),[c,u]=p.useState(""),[d,f]=p.useState(!1);async function h(x){if(x.preventDefault(),u(""),s.length<6){u("New password must be at least 6 characters");return}if(s!==l){u("New passwords do not match");return}f(!0);try{await SN(e,{currentPassword:n,newPassword:s}),bo(Object.assign({},e,{mustChangePassword:!1})),t("/projects",{replace:!0})}catch(w){u(w instanceof Error?w.message:"Password change failed")}finally{f(!1)}}function y(){Qu(),t("/login",{replace:!0})}return i.jsx("div",{className:"flex h-full items-center justify-center bg-background px-4",children:i.jsxs(Kt,{className:"w-full max-w-md border-white/[0.08] bg-card shadow-2xl",children:[i.jsxs(Vn,{className:"space-y-6 text-center",children:[i.jsx("div",{className:"flex justify-center",children:i.jsx(Ei,{})}),i.jsxs("div",{children:[i.jsx(Hn,{className:"text-2xl",children:"Change your password"}),i.jsx(ko,{className:"mt-2",children:"You must set a new password before using Task Bridge."})]})]}),i.jsxs(Qt,{className:"space-y-5",children:[i.jsxs("form",{onSubmit:x=>void h(x),className:"space-y-4",children:[i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"current-password",children:"Current password"}),i.jsxs("div",{className:"relative",children:[i.jsx(tv,{className:"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"}),i.jsx(Ne,{id:"current-password",type:"password",className:"pl-10",value:n,onChange:x=>r(x.target.value),required:!0,disabled:d})]})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"new-password",children:"New password"}),i.jsx(Ne,{id:"new-password",type:"password",value:s,onChange:x=>o(x.target.value),required:!0,disabled:d,minLength:6})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"confirm-password",children:"Confirm new password"}),i.jsx(Ne,{id:"confirm-password",type:"password",value:l,onChange:x=>a(x.target.value),required:!0,disabled:d,minLength:6})]}),c&&i.jsx("p",{className:"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive",children:c}),i.jsx(H,{type:"submit",className:"h-11 w-full",disabled:d,children:d?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):"Update password"})]}),i.jsx(H,{type:"button",variant:"ghost",className:"w-full",onClick:y,children:"Sign out"})]})]})})}function cj(){const e=Jt();return e?e.mustChangePassword?i.jsx(aj,{session:e}):i.jsx(qe,{to:"/projects",replace:!0}):i.jsx(qe,{to:"/login",replace:!0})}function uj(){const e=yn(),[t,n]=p.useState(""),[r,s]=p.useState(""),[o,l]=p.useState(""),[a,c]=p.useState(!1),[u,d]=p.useState(!0);p.useEffect(()=>{const h=Jt();if(h){if(h.mustChangePassword){e("/change-password",{replace:!0});return}e(h.projectId?`/projects/${h.projectId}/tasks`:"/projects",{replace:!0});return}fv().then(({hasUsers:y})=>{y||e("/setup",{replace:!0})}).catch(()=>{}).finally(()=>d(!1))},[e]);async function f(h){h.preventDefault(),l(""),c(!0);try{const y=await kN({email:t,password:r});if(bo({token:y.token,userId:y.user.id,userName:y.user.name,userEmail:y.user.email,userRole:y.user.role,isSystemAdmin:y.user.isSystemAdmin,mustChangePassword:y.user.mustChangePassword,projectId:null,projectName:null}),y.user.mustChangePassword){e("/change-password",{replace:!0});return}e("/projects",{replace:!0})}catch(y){l(y instanceof Error?y.message:"Login failed")}finally{c(!1)}}return u?i.jsx("div",{className:"flex h-full items-center justify-center",children:i.jsx(ve,{className:"h-6 w-6 animate-spin text-muted-foreground"})}):i.jsx("div",{className:"flex h-full items-center justify-center bg-background px-4",children:i.jsxs(Kt,{className:"w-full max-w-md border-white/[0.08] bg-card shadow-2xl",children:[i.jsxs(Vn,{className:"space-y-6 text-center",children:[i.jsx("div",{className:"flex justify-center",children:i.jsx(Ei,{})}),i.jsxs("div",{children:[i.jsx(Hn,{className:"text-2xl",children:"Sign in"}),i.jsx(ko,{className:"mt-2",children:"Enter your email and password to continue"})]})]}),i.jsx(Qt,{className:"space-y-5",children:i.jsxs("form",{onSubmit:h=>void f(h),className:"space-y-4",children:[i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"email",children:"Email"}),i.jsxs("div",{className:"relative",children:[i.jsx(oN,{className:"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"}),i.jsx(Ne,{id:"email",type:"email",className:"pl-10",value:t,onChange:h=>n(h.target.value),placeholder:"you@example.com",required:!0,disabled:a})]})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"password",children:"Password"}),i.jsxs("div",{className:"relative",children:[i.jsx(tv,{className:"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"}),i.jsx(Ne,{id:"password",type:"password",className:"pl-10",value:r,onChange:h=>s(h.target.value),placeholder:"••••••••",required:!0,disabled:a,onKeyDown:h=>{if(h.key==="Enter"){h.preventDefault();const y=h.currentTarget.form;y&&y.requestSubmit()}}})]})]}),o&&i.jsx("p",{className:"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive",children:o}),i.jsxs(H,{type:"submit",className:"h-11 w-full",disabled:a,children:[a?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):i.jsx(rN,{className:"h-4 w-4"}),"Sign in"]})]})})]})})}function dj(){const e=yn(),[t,n]=p.useState(""),[r,s]=p.useState(""),[o,l]=p.useState(""),[a,c]=p.useState(""),[u,d]=p.useState(""),[f,h]=p.useState(!1);p.useEffect(()=>{fv().then(({hasUsers:x})=>{x&&e("/login",{replace:!0})}).catch(()=>{})},[e]);async function y(x){if(x.preventDefault(),d(""),o!==a){d("Passwords do not match");return}if(o.length<6){d("Password must be at least 6 characters");return}h(!0);try{await bN({name:t,email:r,password:o}),e("/login",{replace:!0})}catch(w){d(w instanceof Error?w.message:"Setup failed")}finally{h(!1)}}return i.jsx("div",{className:"flex h-full items-center justify-center bg-background px-4",children:i.jsxs(Kt,{className:"w-full max-w-md border-white/[0.08] bg-card shadow-2xl",children:[i.jsxs(Vn,{className:"space-y-6 text-center",children:[i.jsx("div",{className:"flex justify-center",children:i.jsx(Ei,{})}),i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-center gap-2 mb-1",children:[i.jsx(ap,{className:"h-5 w-5 text-primary"}),i.jsx(Hn,{className:"text-2xl",children:"Admin Setup"})]}),i.jsx(ko,{children:"Create the administrator account to get started"})]})]}),i.jsx(Qt,{children:i.jsxs("form",{onSubmit:x=>void y(x),className:"space-y-4",children:[i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"name",children:"Full Name"}),i.jsx(Ne,{id:"name",type:"text",value:t,onChange:x=>n(x.target.value),placeholder:"John Doe",required:!0,disabled:f})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"email",children:"Email"}),i.jsx(Ne,{id:"email",type:"email",value:r,onChange:x=>s(x.target.value),placeholder:"admin@example.com",required:!0,disabled:f})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"password",children:"Password"}),i.jsx(Ne,{id:"password",type:"password",value:o,onChange:x=>l(x.target.value),placeholder:"Min. 6 characters",required:!0,disabled:f})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{htmlFor:"confirm",children:"Confirm Password"}),i.jsx(Ne,{id:"confirm",type:"password",value:a,onChange:x=>c(x.target.value),placeholder:"Repeat password",required:!0,disabled:f})]}),u&&i.jsx("p",{className:"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive",children:u}),i.jsxs(H,{type:"submit",className:"h-11 w-full",disabled:f,children:[f?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):i.jsx(ap,{className:"h-4 w-4"}),"Create Admin Account"]})]})})]})})}const Zl=p.forwardRef((e,t)=>p.createElement("select",Object.assign({},ze(e,["className","style"]),{className:Z("flex h-10 w-full rounded-xl border border-white/[0.1] bg-[#111111] px-3 py-2 text-sm text-foreground ring-offset-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-black disabled:cursor-not-allowed disabled:opacity-50",e.className),style:e.style,ref:t})));Zl.displayName="Select";const mp={admin:"Admin","read-write":"Read & Write",read:"Read only"},gp={admin:"default","read-write":"secondary",read:"outline"};function fj(){const e=zt(),[t,n]=p.useState([]),[r,s]=p.useState(!0),[o,l]=p.useState(!1),[a,c]=p.useState(""),[u,d]=p.useState(""),[f,h]=p.useState(""),[y,x]=p.useState("read-write"),[w,b]=p.useState(!1),[m,v]=p.useState(""),g=p.useCallback(async()=>{if(e)try{s(!0),n(await jN(e))}catch{Y.error("Failed to load users")}finally{s(!1)}},[e]);p.useEffect(()=>{g()},[g]);async function k(S){if(S.preventDefault(),!!e){v(""),b(!0);try{const C=await CN(e,{name:a,email:u,password:f,role:y});n(U=>U.concat([C])),l(!1),c(""),d(""),h(""),x("read-write"),Y.success("User created")}catch(C){v(C instanceof Error?C.message:"Failed to create user")}finally{b(!1)}}}async function j(S,C){if(e&&confirm(`Remove ${C}? This cannot be undone.`))try{await TN(e,S),n(U=>U.filter(T=>T.id!==S)),Y.success("User removed")}catch(U){Y.error(U instanceof Error?U.message:"Failed to remove user")}}async function E(S,C){if(e)try{const U=await EN(e,S,{name:null,role:C});n(T=>T.map(V=>V.id===S?U:V)),Y.success("Role updated")}catch(U){Y.error(U instanceof Error?U.message:"Failed to update role")}}return e?i.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[i.jsx(bn,{title:"Team members",subtitle:"Manage who can sign in and what they can access",actions:i.jsxs(hs,{open:o,onOpenChange:l,children:[i.jsx(hN,{asChild:!0,children:i.jsxs(H,{size:"sm",children:[i.jsx(Ct,{className:"h-4 w-4"}),"Add member"]})}),i.jsxs(wr,{className:"border-white/[0.08] bg-[#111111]",children:[i.jsx(br,{children:i.jsx(kr,{children:"Add team member"})}),i.jsxs("form",{onSubmit:S=>void k(S),className:"space-y-4 pt-2",children:[i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{children:"Full name"}),i.jsx(Ne,{value:a,onChange:S=>c(S.target.value),placeholder:"Jane Smith",required:!0})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{children:"Email"}),i.jsx(Ne,{type:"email",value:u,onChange:S=>d(S.target.value),placeholder:"jane@example.com",required:!0})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{children:"Password"}),i.jsx(Ne,{type:"password",value:f,onChange:S=>h(S.target.value),placeholder:"Min. 6 characters",required:!0,minLength:6})]}),i.jsxs("div",{className:"grid gap-2",children:[i.jsx(xe,{children:"Role"}),i.jsxs(Zl,{value:y,onChange:S=>x(S.target.value),children:[i.jsx("option",{value:"read",children:"Read only"}),i.jsx("option",{value:"read-write",children:"Read & Write"}),i.jsx("option",{value:"admin",children:"Admin"})]})]}),m?i.jsx("p",{className:"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive",children:m}):null,i.jsxs("div",{className:"flex justify-end gap-2",children:[i.jsx(H,{type:"button",variant:"ghost",onClick:()=>l(!1),children:"Cancel"}),i.jsxs(H,{type:"submit",disabled:w,children:[w?i.jsx(ve,{className:"h-4 w-4 animate-spin"}):null,w?"Creating…":"Create"]})]})]})]})]})}),i.jsx("div",{className:"flex-1 overflow-y-auto px-8 py-6",children:r?i.jsx("div",{className:"flex items-center justify-center py-16",children:i.jsx(ve,{className:"h-6 w-6 animate-spin text-muted-foreground"})}):t.length===0?i.jsx("div",{className:"panel-card p-10 text-center text-sm text-muted-foreground",children:"No team members yet. Add one to get started."}):i.jsx("div",{className:"panel-card divide-y divide-white/[0.06] overflow-hidden",children:t.map(S=>i.jsxs("div",{className:"flex items-center justify-between gap-4 px-5 py-4",children:[i.jsxs("div",{className:"min-w-0 space-y-0.5",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("div",{className:"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary/15 text-xs font-semibold uppercase text-primary",children:S.name.charAt(0)}),i.jsx("span",{className:"truncate font-medium text-white",children:S.name}),S.isSystemAdmin?i.jsx("span",{title:"System admin — cannot be deleted",children:i.jsx(dN,{className:"h-3.5 w-3.5 shrink-0 text-muted-foreground"})}):null]}),i.jsx("p",{className:"truncate pl-10 text-sm text-muted-foreground",children:S.email})]}),i.jsxs("div",{className:"ml-4 flex shrink-0 items-center gap-3",children:[S.isSystemAdmin?i.jsx(dv,{variant:S.role in gp?gp[S.role]:"outline",children:S.role in mp?mp[S.role]:S.role}):i.jsxs(Zl,{value:S.role,onChange:C=>void E(S.id,C.target.value),className:"h-9 w-auto min-w-[9.5rem] py-1 text-xs",children:[i.jsx("option",{value:"read",children:"Read only"}),i.jsx("option",{value:"read-write",children:"Read & Write"}),i.jsx("option",{value:"admin",children:"Admin"})]}),S.isSystemAdmin?null:i.jsx(H,{size:"icon",variant:"ghost",className:"h-8 w-8 text-muted-foreground hover:text-destructive",onClick:()=>void j(S.id,S.name),title:"Remove user",children:i.jsx(gn,{className:"h-4 w-4"})})]})]},S.id))})})]}):null}var pj=Object.defineProperty,ql=Object.getOwnPropertySymbols,wv=Object.prototype.hasOwnProperty,bv=Object.prototype.propertyIsEnumerable,vp=(e,t,n)=>t in e?pj(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Mc=(e,t)=>{for(var n in t||(t={}))wv.call(t,n)&&vp(e,n,t[n]);if(ql)for(var n of ql(t))bv.call(t,n)&&vp(e,n,t[n]);return e},Oc=(e,t)=>{var n={};for(var r in e)wv.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&ql)for(var r of ql(e))t.indexOf(r)<0&&bv.call(e,r)&&(n[r]=e[r]);return n};/**
|
|
347
347
|
* @license QR Code generator library (TypeScript)
|
|
348
348
|
* Copyright (c) Project Nayuki.
|
|
349
349
|
* SPDX-License-Identifier: MIT
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Task Bridge</title>
|
|
7
|
-
<script type="module" crossorigin src="/app/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/app/assets/index-NY9anVyw.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/app/assets/index-ByKECv-I.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|