fraim-framework 2.0.126 → 2.0.128

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/src/ai-hub/catalog.js +280 -44
  2. package/dist/src/ai-hub/desktop-main.js +2 -2
  3. package/dist/src/ai-hub/hosts.js +384 -10
  4. package/dist/src/ai-hub/server.js +255 -9
  5. package/dist/src/cli/commands/add-ide.js +4 -3
  6. package/dist/src/cli/commands/first-run.js +61 -0
  7. package/dist/src/cli/commands/hub.js +4 -4
  8. package/dist/src/cli/commands/init-project.js +8 -4
  9. package/dist/src/cli/commands/setup.js +4 -3
  10. package/dist/src/cli/commands/sync.js +32 -6
  11. package/dist/src/cli/doctor/checks/ide-config-checks.js +20 -2
  12. package/dist/src/cli/fraim.js +2 -0
  13. package/dist/src/cli/mcp/ide-formats.js +29 -1
  14. package/dist/src/cli/mcp/mcp-server-registry.js +1 -0
  15. package/dist/src/cli/setup/auto-mcp-setup.js +14 -8
  16. package/dist/src/cli/setup/ide-detector.js +32 -1
  17. package/dist/src/cli/setup/ide-global-integration.js +5 -1
  18. package/dist/src/cli/setup/ide-invocation-surfaces.js +14 -0
  19. package/dist/src/cli/setup/mcp-config-generator.js +12 -1
  20. package/dist/src/cli/utils/agent-adapters.js +10 -0
  21. package/dist/src/core/utils/git-utils.js +14 -6
  22. package/dist/src/first-run/install-state.js +70 -0
  23. package/dist/src/first-run/server.js +158 -0
  24. package/dist/src/first-run/session-service.js +746 -0
  25. package/dist/src/first-run/types.js +97 -0
  26. package/dist/src/local-mcp-server/otlp-metrics-receiver.js +7 -1
  27. package/dist/src/local-mcp-server/stdio-server.js +41 -9
  28. package/package.json +3 -1
  29. package/public/ai-hub/index.html +149 -102
  30. package/public/ai-hub/script.js +1154 -271
  31. package/public/ai-hub/styles.css +753 -450
  32. package/public/first-run/error-frame.js +89 -0
  33. package/public/first-run/index.html +35 -0
  34. package/public/first-run/script.js +417 -0
  35. package/public/first-run/styles.css +386 -0
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Setup checklist surface — the row-keyed model that replaces the prior
4
+ * linear step machine for issue #352 v1.
5
+ *
6
+ * The same surface serves every user. What changes between a fresh-machine
7
+ * run and an experienced-engineer run is the distribution of row statuses,
8
+ * not the surface itself. Per R2.0 acceptance, the DOM and CSS chrome are
9
+ * byte-identical between cases — only `data-row-status`, the verb-text
10
+ * node, and the primary-button label differ.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.FIRST_RUN_AGENT_OPTIONS = exports.FIRST_RUN_RESOURCES_URL = exports.FIRST_RUN_PROMPT = exports.FIRST_RUN_ROW_IDS = void 0;
14
+ exports.createInitialRows = createInitialRows;
15
+ exports.derivePrimaryButtonLabel = derivePrimaryButtonLabel;
16
+ exports.FIRST_RUN_ROW_IDS = [
17
+ 'node',
18
+ 'git',
19
+ 'agent',
20
+ 'agent-login',
21
+ 'project',
22
+ ];
23
+ exports.FIRST_RUN_PROMPT = 'Onboard this project';
24
+ exports.FIRST_RUN_RESOURCES_URL = 'https://fraimworks.ai/resources.html';
25
+ /**
26
+ * Recommended-agent priority order (R2.2).
27
+ * Tie-break: Claude Code first, then Codex, then Gemini.
28
+ */
29
+ exports.FIRST_RUN_AGENT_OPTIONS = [
30
+ {
31
+ id: 'claude-code',
32
+ label: 'Claude Code',
33
+ detectAliases: ['claude-code', 'claude code', 'claude'],
34
+ loginCommand: 'claude',
35
+ launchCommand: 'claude',
36
+ installPackage: '@anthropic-ai/claude-code',
37
+ },
38
+ {
39
+ id: 'codex',
40
+ label: 'Codex',
41
+ detectAliases: ['codex'],
42
+ loginCommand: 'codex login',
43
+ launchCommand: 'codex',
44
+ installPackage: '@openai/codex',
45
+ },
46
+ {
47
+ id: 'gemini-cli',
48
+ label: 'Gemini CLI',
49
+ detectAliases: ['gemini-cli', 'gemini cli', 'gemini'],
50
+ loginCommand: 'gemini',
51
+ launchCommand: 'gemini',
52
+ installPackage: '@google/gemini-cli',
53
+ },
54
+ ];
55
+ /**
56
+ * The canonical row set, in display order. Each row starts in `pending`;
57
+ * detection updates statuses on session load.
58
+ */
59
+ function createInitialRows() {
60
+ return [
61
+ { id: 'node', label: 'Node.js', status: 'pending', verb: "we'll install" },
62
+ { id: 'git', label: 'git', status: 'pending', verb: "we'll install" },
63
+ { id: 'agent', label: 'AI agent', status: 'pending', verb: "we'll set up Claude Code (recommended)" },
64
+ { id: 'agent-login', label: 'Sign in', status: 'pending', verb: "you'll sign in after install" },
65
+ { id: 'project', label: 'Project folder', status: 'pending', verb: 'pick a folder where FRAIM should work' },
66
+ ];
67
+ }
68
+ /**
69
+ * State-derived primary-button label per R2.0. NEVER audience-derived.
70
+ *
71
+ * - "Open Hub": every row is ok, OR the project row is ok and every other
72
+ * row is ok-or-manual-required (i.e. the user explicitly handed
73
+ * themselves any remaining steps via Skip-and-continue, and there is no
74
+ * forward action the wizard can take).
75
+ * - "Continue": at least one row is ok and there is still wizard work to do.
76
+ * - "Set up FRAIM": no rows ok yet.
77
+ *
78
+ * Manual-required is treated as "user-handled" for the Open-Hub gate so a
79
+ * Skip-and-continue path doesn't strand the user on a dead-end Continue
80
+ * button. It is treated as "in-progress / waiting on user" for the
81
+ * Continue gate so a fresh state with only the manual project row open
82
+ * still asks the user to continue picking a folder.
83
+ */
84
+ function derivePrimaryButtonLabel(rows) {
85
+ const projectRow = rows.find((row) => row.id === 'project');
86
+ const projectOk = projectRow?.status === 'ok';
87
+ const allOk = rows.every((row) => row.status === 'ok');
88
+ if (allOk)
89
+ return 'Open Hub';
90
+ // User-skipped path: project ok and every other row ok-or-manual-required.
91
+ if (projectOk && rows.every((row) => row.status === 'ok' || row.status === 'manual-required')) {
92
+ return 'Open Hub';
93
+ }
94
+ if (rows.some((row) => row.status === 'ok'))
95
+ return 'Continue';
96
+ return 'Set up FRAIM';
97
+ }
@@ -257,6 +257,12 @@ function startOtlpReceiver(log) {
257
257
  * Stop the OTLP receiver and clear stored snapshots.
258
258
  */
259
259
  function stopOtlpReceiver(server) {
260
- server.close();
260
+ try {
261
+ server.close();
262
+ }
263
+ catch {
264
+ // The receiver may have failed to bind because another FRAIM proxy owns
265
+ // the port. In that case there is no local listener to close.
266
+ }
261
267
  snapshots.clear();
262
268
  }
@@ -402,6 +402,7 @@ class FraimLocalMCPServer {
402
402
  this.repoInfo = null;
403
403
  this.engine = null;
404
404
  this.otlpServer = null;
405
+ this.isShutdown = false;
405
406
  this.writer = writer || process.stdout.write.bind(process.stdout);
406
407
  this.remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
407
408
  this.apiKey = this.loadApiKey();
@@ -422,6 +423,16 @@ class FraimLocalMCPServer {
422
423
  // Start OTLP metrics receiver for Claude Code token telemetry
423
424
  this.otlpServer = (0, otlp_metrics_receiver_js_1.startOtlpReceiver)((msg) => this.log(`📊 ${msg}`));
424
425
  }
426
+ shutdown() {
427
+ if (this.isShutdown)
428
+ return;
429
+ this.isShutdown = true;
430
+ this.usageCollector.shutdown();
431
+ if (this.otlpServer?.server) {
432
+ (0, otlp_metrics_receiver_js_1.stopOtlpReceiver)(this.otlpServer.server);
433
+ this.otlpServer = null;
434
+ }
435
+ }
425
436
  /**
426
437
  * Load API key from environment variable or user config file
427
438
  * Priority: FRAIM_API_KEY env var > ~/.fraim/config.json
@@ -1436,6 +1447,33 @@ class FraimLocalMCPServer {
1436
1447
  projectPath: `${issueTracking.namespace}/${issueTracking.name}`
1437
1448
  };
1438
1449
  }
1450
+ hasRemoteRepoLocator(value) {
1451
+ if (typeof value !== 'string')
1452
+ return false;
1453
+ return /^[a-z]+:\/\//i.test(value) || /^[^@\s]+@[^:\s]+:/i.test(value);
1454
+ }
1455
+ mergeRepoContexts(agentRepo, detectedRepo) {
1456
+ const normalizedAgent = this.normalizeRepoContext(agentRepo);
1457
+ const normalizedDetected = this.normalizeRepoContext(detectedRepo);
1458
+ if (!normalizedAgent)
1459
+ return normalizedDetected;
1460
+ if (!normalizedDetected)
1461
+ return normalizedAgent;
1462
+ const agentHasRemoteUrl = this.hasRemoteRepoLocator(normalizedAgent.url);
1463
+ const detectedHasRemoteUrl = this.hasRemoteRepoLocator(normalizedDetected.url);
1464
+ // Preserve an explicit agent-supplied remote repository when local auto-detection
1465
+ // only found a workspace label or other non-remote fallback.
1466
+ if (agentHasRemoteUrl && !detectedHasRemoteUrl) {
1467
+ return this.normalizeRepoContext({
1468
+ ...normalizedDetected,
1469
+ ...normalizedAgent
1470
+ });
1471
+ }
1472
+ return this.normalizeRepoContext({
1473
+ ...normalizedAgent,
1474
+ ...normalizedDetected
1475
+ });
1476
+ }
1439
1477
  /**
1440
1478
  * Internal method to perform the actual proxy request to the remote server.
1441
1479
  * This method does NOT inject raw: true, as it is used for both top-level
@@ -1457,11 +1495,7 @@ class FraimLocalMCPServer {
1457
1495
  // REQUIRED: Auto-detect and inject repo info
1458
1496
  const detectedRepo = this.detectRepoInfo();
1459
1497
  if (detectedRepo) {
1460
- args.repo = {
1461
- ...args.repo, // Agent values as fallback
1462
- ...detectedRepo // Detected values override (always win)
1463
- };
1464
- args.repo = this.normalizeRepoContext(args.repo);
1498
+ args.repo = this.mergeRepoContexts(args.repo, detectedRepo);
1465
1499
  const repoLabel = args.repo.owner ? `${args.repo.owner}/${args.repo.name}` : args.repo.name;
1466
1500
  this.log(`[req:${requestId}] Auto-detected and injected repo info: ${repoLabel}`);
1467
1501
  }
@@ -1894,13 +1928,11 @@ class FraimLocalMCPServer {
1894
1928
  this.log(`⚠️ Failed to upload usage data: ${error.message}`);
1895
1929
  });
1896
1930
  }, 60000); // Upload every minute
1931
+ uploadInterval.unref?.();
1897
1932
  // Clean up interval on shutdown
1898
1933
  const cleanup = () => {
1899
1934
  clearInterval(uploadInterval);
1900
- this.usageCollector.shutdown();
1901
- if (this.otlpServer?.server) {
1902
- (0, otlp_metrics_receiver_js_1.stopOtlpReceiver)(this.otlpServer.server);
1903
- }
1935
+ this.shutdown();
1904
1936
  };
1905
1937
  process.stdin.on('data', async (chunk) => {
1906
1938
  buffer += chunk;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.126",
3
+ "version": "2.0.128",
4
4
  "description": "FRAIM: AI Workforce Infrastructure — the organizational capability that turns AI agents into an accountable workforce, their operators into capable AI managers, and executives into leaders with clear optics on AI proficiency.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -114,10 +114,12 @@
114
114
  "dist/src/local-mcp-server/",
115
115
  "dist/src/cli/",
116
116
  "dist/src/ai-hub/",
117
+ "dist/src/first-run/",
117
118
  "dist/src/core/",
118
119
  "bin/fraim.js",
119
120
  "bin/fraim-mcp.js",
120
121
  "public/ai-hub/",
122
+ "public/first-run/",
121
123
  "index.js",
122
124
  "README.md",
123
125
  "CHANGELOG.md",
@@ -3,127 +3,174 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Visa AI Hub</title>
7
- <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Crect width='48' height='48' rx='10' fill='%230d3f8a'/%3E%3Cpath d='M13 31h6l3-14h-6zm10 0h6l3-14h-6zm11-14h-6l-3 14h6z' fill='%23f7b600'/%3E%3C/svg%3E">
6
+ <title>AI Hub</title>
7
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Crect width='48' height='48' rx='10' fill='%233d8a6e'/%3E%3Ctext x='24' y='32' text-anchor='middle' font-family='Helvetica,Arial,sans-serif' font-size='22' font-weight='700' fill='white'%3EH%3C/text%3E%3C/svg%3E">
8
8
  <link rel="stylesheet" href="./styles.css">
9
9
  </head>
10
10
  <body>
11
- <div class="app-shell">
12
- <header class="app-header">
13
- <div class="brand-lockup">
14
- <p class="brand-title">Visa AI Hub</p>
15
- <p class="brand-subtitle">Powered by <a href="https://fraimworks.ai" target="_blank" rel="noreferrer">FRAIM</a></p>
16
- </div>
17
- <div class="topbar-card workspace-card">
18
- <div class="workspace-section workspace-section-inline">
19
- <span class="section-label">Employee</span>
20
- <div class="employee-picker" id="employee-picker"></div>
21
- </div>
22
- <details class="project-path workspace-project">
23
- <summary>
24
- <span>Project Path</span>
25
- <span class="project-path-preview" id="project-path-preview"></span>
26
- </summary>
27
- <div class="project-path-body">
28
- <input id="project-path-input" class="text-input" type="text" />
29
- <div class="project-actions">
30
- <button id="browse-project" class="secondary-button" type="button">Choose Folder</button>
31
- <button id="reload-project" class="secondary-button" type="button">Reload</button>
32
- </div>
33
- <p class="helper-text" id="project-status"></p>
34
- </div>
35
- </details>
36
- </div>
11
+
12
+ <div class="page">
13
+
14
+ <header class="header">
15
+ <h1>AI Hub</h1>
16
+ <button class="project-button" type="button" id="project-button">
17
+ <span class="folder">Project</span>
18
+ <strong id="project-name">Choose a folder</strong>
19
+ </button>
37
20
  </header>
38
21
 
39
- <main class="workspace">
40
- <section class="workspace-intro card">
41
- <div class="workspace-intro-copy">
42
- <p class="section-label">Pick A Job</p>
43
- <p class="panel-intro">Manage your AI employees just like you manage your teams. Tell them what jobs they need to complete, review their results, coach them, expect them to learn from you, and expect to learn from your employees.</p>
44
- </div>
45
- <div class="workspace-intro-actions">
46
- <p class="section-label">Job Category</p>
47
- <div class="category-picker" id="category-picker"></div>
48
- </div>
49
- </section>
50
-
51
- <section class="job-panel">
52
- <div class="panel-head card">
53
- <div class="panel-copy">
54
- <p class="section-label">Job Category</p>
55
- <h2 class="panel-title">Pick a job</h2>
56
- <p class="muted">Choose the next assignment for your employee.</p>
57
- </div>
58
- </div>
22
+ <section class="welcome">
23
+ Hi <strong class="you">there</strong>, remember, you are the
24
+ <span class="concept">AI Manager<button class="info" data-concept="manager" aria-label="What is AI Manager?">i</button>
25
+ <span class="popover" id="pop-manager">
26
+ <span class="pop-title">AI Manager</span>
27
+ That's you. You decide what outcome matters, pick the right job, and hold the bar on quality. You're not writing the work — you're directing it.
28
+ <a class="pop-link" href="#" data-target="jobs-manager">See manager jobs →</a>
29
+ <span class="pop-jobs" id="jobs-manager"></span>
30
+ </span></span>.
31
+ Delegate jobs to your
32
+ <span class="concept">AI Employees<button class="info" data-concept="employee" aria-label="What is AI Employee?">i</button>
33
+ <span class="popover" id="pop-employee">
34
+ <span class="pop-title">AI Employee</span>
35
+ The coding agent — Codex, Claude, or another — that actually does the work you assign. AI Hub is where you direct them; the agent itself is the employee.
36
+ <a class="pop-link" href="#" data-target="jobs-employee">See employee jobs →</a>
37
+ <span class="pop-jobs" id="jobs-employee"></span>
38
+ </span></span>,
39
+ <span class="concept">Coach<button class="info" data-concept="coach" aria-label="What is Coach?">i</button>
40
+ <span class="popover" id="pop-coach">
41
+ <span class="pop-title">Coach</span>
42
+ Add context, raise the bar, or correct course mid-flight without abandoning the conversation. Same job, sharper guidance.
43
+ <a class="pop-link" href="#" data-target="jobs-coach">See coaching jobs →</a>
44
+ <span class="pop-jobs" id="jobs-coach"></span>
45
+ </span></span>
46
+ them,
47
+ <span class="concept">Verify<button class="info" data-concept="verify" aria-label="What is Verify?">i</button>
48
+ <span class="popover" id="pop-verify">
49
+ <span class="pop-title">Verify</span>
50
+ Check that the artifact actually solved the problem before you trust completion. Read the result, not just the logs.
51
+ <a class="pop-link" href="#" data-target="jobs-verify">See verification jobs →</a>
52
+ <span class="pop-jobs" id="jobs-verify"></span>
53
+ </span></span>
54
+ their work, and expect them to
55
+ <span class="concept">Learn<button class="info" data-concept="learn" aria-label="What is Learn?">i</button>
56
+ <span class="popover" id="pop-learn">
57
+ <span class="pop-title">Learn</span>
58
+ Each run should make the next one better. Your coaching becomes their lasting habit.
59
+ <a class="pop-link" href="#" data-target="jobs-learn">See learning jobs →</a>
60
+ <span class="pop-jobs" id="jobs-learn"></span>
61
+ </span></span>
62
+ and get better. Let your employees make you shine.
63
+ </section>
59
64
 
60
- <div class="job-list-card card">
61
- <div class="job-list" id="job-list"></div>
65
+ <div class="layout">
66
+ <aside class="rail">
67
+ <button class="new-conv" type="button" id="new-conv-btn">+ New job</button>
68
+ <div class="conv-list" id="conv-list"></div>
69
+ </aside>
70
+
71
+ <main class="conversation" id="conversation">
72
+ <div class="empty-state" id="empty">
73
+ <h3>No job selected</h3>
74
+ <p>Pick an existing job from the left, or click <strong>+ New job</strong> to give your employee something to work on.</p>
62
75
  </div>
63
- </section>
64
-
65
- <section class="interaction-panel">
66
- <div class="card interaction-card">
67
- <div class="run-toolbar">
68
- <div class="selected-job-summary">
69
- <div>
70
- <p class="section-label">Selected Job</p>
71
- <h2 id="selected-job-title">Select a job</h2>
72
- <p id="selected-job-intent" class="muted"></p>
73
- </div>
74
- <ul class="job-outcomes" id="selected-job-outcomes"></ul>
75
- </div>
76
- <div class="run-toolbar-actions">
77
- <button id="start-job" class="primary-button" type="button" disabled>Start Job</button>
78
- </div>
76
+
77
+ <div id="active-conv" hidden>
78
+ <div class="conv-header">
79
+ <h2 id="active-title"></h2>
80
+ <div class="conv-job" id="active-job"></div>
81
+ <div id="artifact-slot"></div>
79
82
  </div>
80
83
 
81
- <div class="interaction-body">
82
- <div class="conversation-column">
83
- <div class="interaction-head">
84
- <div>
85
- <p class="section-label">Manager / Employee Interactions</p>
86
- <h2 id="conversation-title">No active job</h2>
87
- </div>
88
- <p class="muted" id="conversation-state">Select a job and coach your employee toward your desired outcome.</p>
89
- </div>
90
-
91
- <div class="timeline" id="timeline"></div>
92
- </div>
84
+ <!-- Issue #347 R1: pizza tracker. Hidden when the active job
85
+ declares no phases. Populated by renderTracker() in script.js. -->
86
+ <div class="tracker" id="tracker" aria-label="Job progress" hidden>
87
+ <div class="tracker-rows" id="tracker-rows"></div>
88
+ <div class="tracker-note" id="tracker-note" hidden></div>
89
+ </div>
93
90
 
94
- <div class="manager-tools">
95
- <div class="template-row">
96
- <span class="section-label">Manager Templates</span>
97
- <div class="template-chips" id="manager-templates"></div>
98
- </div>
91
+ <div class="progress" id="progress">
92
+ <span class="stage" id="stage">Getting started…</span>
93
+ <span class="latest" id="latest"></span>
94
+ </div>
99
95
 
100
- <label class="composer-label" for="manager-message">Coach your employee toward your desired outcome.</label>
101
- <textarea id="manager-message" class="composer" placeholder='Use FRAIM job "marketing-content-creation" and include the audience, desired outcome, and any constraints.'></textarea>
96
+ <div class="messages" id="messages"></div>
102
97
 
103
- <div class="composer-actions">
104
- <button id="send-coaching" class="secondary-button" type="button" disabled>Send Coaching</button>
105
- </div>
98
+ <div class="coach">
99
+ <div class="section-title">Coach the employee</div>
100
+ <textarea id="coach-text" placeholder="Tell the employee what to do next…"></textarea>
101
+ <div class="coach-actions">
102
+ <!-- Issue #347 R2: template picker. Hidden when the project
103
+ has no manager-job templates. -->
104
+ <button class="ghost" type="button" id="template-picker-btn" aria-haspopup="menu" aria-expanded="false" hidden>Use a template ▾</button>
105
+ <button class="send-button" type="button" id="send" disabled>Send</button>
106
+ <div class="template-popover" id="template-popover" role="menu" hidden></div>
106
107
  </div>
108
+ <!-- Issue #347 R4: run-level totals. Discoverable, not dominating.
109
+ Populated by renderTotals() each poll tick. -->
110
+ <div class="totals" id="totals" aria-label="Run totals" hidden></div>
107
111
  </div>
112
+
113
+ <details class="micro" id="micro-manage">
114
+ <summary>Micro-manage — raw host details</summary>
115
+ <pre class="micro-log" id="micro-log"></pre>
116
+ </details>
108
117
  </div>
118
+ </main>
119
+ </div>
109
120
 
110
- <details class="card micro-manage" id="micro-manage">
111
- <summary>Micro-manage</summary>
112
- <div class="micro-manage-body" id="raw-history"></div>
113
- </details>
114
- </section>
115
- </main>
121
+ <p class="status-line" id="status-line" role="status" aria-live="polite"></p>
116
122
  </div>
117
123
 
118
- <template id="timeline-message-template">
119
- <article class="message">
120
- <div class="message-meta">
121
- <span class="message-role"></span>
122
- <time class="message-time"></time>
124
+ <!-- Modal: New Job -->
125
+ <div class="modal-backdrop" id="modal" role="dialog" aria-modal="true" hidden>
126
+ <div class="modal">
127
+
128
+ <div id="step1">
129
+ <div class="modal-header">
130
+ <h2>What should the employee work on?</h2>
131
+ <p>Pick one job. You can always start a new job for different work.</p>
132
+ </div>
133
+ <div class="modal-body">
134
+ <input class="search" type="text" placeholder="Search jobs…" id="job-search">
135
+ <div id="job-catalog"></div>
136
+ </div>
137
+ <div class="modal-footer">
138
+ <span class="left" id="job-pick-status">Choose a job to continue</span>
139
+ <div class="right">
140
+ <button class="ghost" type="button" id="cancel1">Cancel</button>
141
+ <button class="send-button" type="button" id="next1" disabled>Next →</button>
142
+ </div>
143
+ </div>
123
144
  </div>
124
- <p class="message-text"></p>
125
- </article>
126
- </template>
145
+
146
+ <div id="step2" class="step2" hidden>
147
+ <div class="modal-header">
148
+ <h2>Tell the employee what you need</h2>
149
+ <p>A few sentences is enough. The employee will ask if anything is unclear.</p>
150
+ </div>
151
+ <div class="modal-body">
152
+ <div class="assigned-job">
153
+ <div class="label">Assigned job</div>
154
+ <div class="name" id="picked-name"></div>
155
+ <div class="desc" id="picked-desc"></div>
156
+ </div>
157
+ <textarea id="instructions" placeholder="What outcome do you want? Any context the employee should know?"></textarea>
158
+ <div class="employee-line">
159
+ <span class="employee-label">Employee:</span>
160
+ <select id="employee-select" class="employee-select"></select>
161
+ </div>
162
+ </div>
163
+ <div class="modal-footer">
164
+ <span class="left">You can coach the employee with more detail after they start.</span>
165
+ <div class="right">
166
+ <button class="ghost" type="button" id="back2">← Back</button>
167
+ <button class="send-button" type="button" id="start" disabled>Start</button>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ </div>
173
+ </div>
127
174
 
128
175
  <script src="./script.js"></script>
129
176
  </body>