@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.
@@ -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: DEFAULT_WORKFLOW_TEMPLATE_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: "ai-sdlc",
65
- title: "AI Spec-Driven SDLC",
66
- description: `Single canonical pipeline for AI-assisted delivery.
49
+ id: DEFAULT_WORKFLOW_TEMPLATE_ID,
50
+ title: "Lean SDLC",
51
+ description: `Lean, classic software delivery pipeline.
67
52
 
68
- **Sources:** GitHub Spec Kit, Thoughtworks SDD, Specorator, GSI-Protocol, pangon/ai-sdlc-scaffold.
53
+ Research -> Define -> Design -> Implement -> Review -> Verify -> Done.
69
54
 
70
- **Golden rule:** Never skip human gates. Never open a public PR before **Human Pre-PR Approval**. Draft branch work is fine during implementation; gh pr ready only after human sign-off.`,
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: "constitution",
74
- title: "Constitution",
58
+ id: "research",
59
+ title: "Research",
75
60
  description: `## Objective
76
- Codify non-negotiable project rules agents must always obey (Spec Kit /speckit.constitution, Specorator memory/constitution.md).
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
- AGENTS.md / CLAUDE.md committed with language, frameworks, testing, security, and dependency policies.
83
-
84
- ## Gate
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("cn-agents-md", "Write AGENTS.md / CLAUDE.md", "**Output:** root agent instructions.\n\n- Language & framework versions\n- Test commands that must exit 0\n- Lint/typecheck commands\n- Branch naming & commit style\n- Files agents must never edit\n- Human gate policy: no PR without approval", "tech-lead", [
92
- task("cn-testing", "Define testing constitution", "**Output:** testing section in constitution.\n\n- Unit vs integration boundaries\n- TDD expectation\n- Coverage expectations for new logic\n- Flaky test policy (fix, don't skip)", "qa"),
93
- task("cn-security", "Define security baseline", "**Output:** security rules in constitution.\n\n- No secrets in repo\n- Auth patterns to follow\n- Dependency update policy\n- Input validation expectations", "architect"),
94
- task("cn-deps", "Dependency & tooling rules", "**Output:** allowed deps, package manager, CI commands.\n\n- Prefer stdlib before new packages\n- Pin versions policy\n- How to run build locally", "architect"),
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: "discovery",
100
- title: "Discovery",
77
+ id: "define",
78
+ title: "Define",
101
79
  description: `## Objective
102
- Validate the problem before writing specs (Specorator Stage 1–2: Idea + Research).
103
-
104
- ## Entry
105
- Constitution in place.
80
+ Turn the request into a clear, testable spec.
106
81
 
107
82
  ## Exit
108
- Problem statement, stakeholders, GOAL-* artifacts, explicit out-of-scope.
109
-
110
- ## Gate
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("dc-stakeholders", "Identify stakeholders", "**Output:** stakeholders.md\n\n- Decision makers (who approves gates)\n- End users\n- Maintainers / on-call\n- Security or compliance reviewers if applicable", "product"),
118
- task("dc-problem", "Write problem statement", "**Output:** problem statement in idea.md\n\n- Current pain (quantify if possible)\n- Desired outcome\n- Why now\n- What happens if we don't build this"),
119
- task("dc-goals", "Define goals", "**Output:** GOAL-* files\n\nMeasurable outcomes, not feature lists. Each goal links to a business or user outcome.", "product"),
120
- task("dc-constraints", "List constraints", "**Output:** CON-* files\n\nBudget, timeline, compliance, tech stack limits, team capacity.", "product"),
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
- Technical blueprint from approved spec (Spec Kit /speckit.plan, GSI Architecture phase).
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
- tasks.md with bounded packets, dependencies, acceptance criteria per task, test plan.
272
-
273
- ## Gate
274
- Each task fits one agent context window; every task links to REQ-* IDs.`,
275
- purpose: "Work breakdown",
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
- chain("ta-scope-review", "Scope review", "**Action:** human reads tasks.md end-to-end.\n\nEach task independently reviewable in a future PR diff.", "tech-lead", [
309
- task("ta-coverage", "Requirement coverage check", "**Action:** every approved REQ-* maps to ≥1 task.\n\nNo orphan requirements.", "qa"),
310
- task("ta-sizing", "Task sizing check", "**Action:** reject tasks that are epics in disguise.\n\nSplit anything estimated >1 agent session.", "tech-lead"),
311
- task("ta-approve", "Approve task breakdown", "**Output:** Approved status on tasks.md.\n\nUnlocks Agent Context + Implementation.", "tech-lead"),
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: "agent-context",
317
- title: "Agent Context",
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
- Execute approved tasks one at a time with TDD + logging (Spec Kit /speckit.implement, Specorator Stage 7).
347
-
348
- ## Entry
349
- Agent context ready; feature branch exists.
122
+ Build the change.
350
123
 
351
124
  ## Exit
352
- All tasks done; implementation-log.md entries; code + tests committed to branch.
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: ["One task at a time", "TDD", "Log each task", "No public PR yet"],
360
- position: 10,
361
- autoAssign: true,
127
+ rules: ["Change implemented", "Tests added/updated", "Local checks pass"],
128
+ position: 3,
129
+ autoAssign: false,
362
130
  taskTemplates: [
363
- chain("im-loop", "Task execution loop", "Repeat for each approved task until tasks.md is complete.", "", [
364
- task("im-pick", "Pick next pending task", "**Action:** select next task from tasks.md by priority.\n\nRead linked REQ-* and design sections before any edit.", "agent"),
365
- task("im-context", "Load minimal context", "**Action:** open only files listed in task packet + constitution.\n\nDo not load entire repo into context.", "agent"),
366
- task("im-tdd", "Implement with TDD", "**Action:** write failing test implement refactor.\n\nMinimal diff; match existing conventions.", "agent"),
367
- task("im-gap", "Handle design gaps", "**Action:** if spec/design insufficient → STOP.\n\nUpdate spec or ADR; get human re-approval if scope changes.", "agent"),
368
- task("im-log", "Write implementation log", "**Output:** implementation-log/TASK-*.md\n\nFiles changed, decisions, debt, verification commands run.", "agent"),
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: "verification",
376
- title: "Verification",
141
+ id: "review",
142
+ title: "Review",
377
143
  description: `## Objective
378
- Programmatic quality gates subprocess exit codes, not agent self-report (GSD-2 / Spec Loop Engine pattern).
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
- Security checklist complete; peer review notes; no secrets in diff; debt logged.
413
-
414
- ## Gate
415
- This is review of **local branch work** — still no public PR.`,
416
- purpose: "Security & peer review",
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
- chain("hp-demo", "Demo or walkthrough", "**Action:** author demos feature to approver (live or recording).\n\nApprover understands what will appear in the PR.", "engineer", [
476
- task("hp-checklist", "Pre-PR checklist", "**Action:** confirm constitution, spec, plan, tasks, tests, security, analyze all passed.\n\nChecklist signed by approver.", "tech-lead"),
477
- task("hp-traceability", "Traceability sign-off", "**Action:** approver confirms RTM is accurate.\n\nRequired for audit-sensitive work.", "qa"),
478
- task("hp-approve", "Authorize PR creation", "**Output:** approval record in status.md or issue comment.\n\nExplicit: 'Approved to open PR to [target branch]'. **This unlocks Open PR stage.**", "tech-lead"),
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: "open-pr",
484
- title: "Open PR",
161
+ id: "verify",
162
+ title: "Verify",
485
163
  description: `## Objective
486
- Create and publish the pull request — **only after Human Pre-PR Approval** (Specorator: mark ready after testing + human review, not before).
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
- PR opened (or draft ready), description complete, CI triggered.
493
-
494
- ## Gate
495
- PR description links spec, tasks, test plan, and REQ-* IDs.`,
496
- purpose: "Publish PR",
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("rv-respond", "Address review comments", "**Action:** respond to each thread; push fixes or explain deferrals with linked debt item.", "engineer"),
527
- task("rv-rereview", "Request re-review", "**Action:** re-request reviewers after substantive changes.", "engineer"),
528
- task("rv-ci-recheck", "Re-run CI after changes", "**Action:** confirm CI green after each review fix round.", "engineer"),
529
- task("rv-merge-approve", "Merge approval", "**Output:** required approvers sign off.\n\nRecord in PR before merge.", "tech-lead"),
530
- task("rv-merge", "Merge to target branch", "**Action:** merge PR to dev/main per team convention.\n\nDelete feature branch if policy requires.", "engineer"),
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
- Epic closed after merge to dev. Production deployment is a separate release track.
538
-
539
- ## Entry
540
- PR merged.
184
+ Wrap up and record what shipped.
541
185
 
542
186
  ## Exit
543
- Epic marked done; optional retrospective scheduled.
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: 17,
189
+ rules: ["Change integrated", "Notes updated"],
190
+ position: 6,
550
191
  autoAssign: false,
551
- taskTemplates: null,
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 === "ai-sdlc") {
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);
@@ -1,4 +1,4 @@
1
- export const DEFAULT_WORKFLOW_TEMPLATE_ID = "empty";
1
+ export const DEFAULT_WORKFLOW_TEMPLATE_ID = "lean-sdlc";
2
2
  export function normalizeWorkflowTemplateId(value) {
3
3
  if (value === "")
4
4
  return DEFAULT_WORKFLOW_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(["ai-sdlc", DEFAULT_WORKFLOW_TEMPLATE_ID]);
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-Bl1ciVpY.js"></script>
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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umudik/task-bridge",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Task Bridge — self-hosted API + web UI server, runnable via npx.",
5
5
  "type": "module",
6
6
  "license": "MIT",