@beastmode-develeap/beastmode 0.1.213 → 0.1.215
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/board.html +631 -36
- package/dist/web/build-commit.txt +1 -1
- package/dist/web/build-stamp.txt +1 -1
- package/package.json +1 -1
package/dist/web/board.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
</script>
|
|
17
17
|
<!--BOARD_DATA-->
|
|
18
|
-
<script>window.__BUILD_STAMP__ = "20260509-
|
|
18
|
+
<script>window.__BUILD_STAMP__ = "20260509-231045-f21d1ff";</script>
|
|
19
19
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
20
20
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
21
21
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
@@ -2184,6 +2184,95 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
2184
2184
|
.filter-toggle:hover { border-color: var(--text-muted); color: var(--text); }
|
|
2185
2185
|
.filter-toggle.active { border-color: var(--accent); color: var(--accent); background: var(--accent-subtle); }
|
|
2186
2186
|
|
|
2187
|
+
/* Sort toggle button — mirrors filter-toggle */
|
|
2188
|
+
.sort-toggle {
|
|
2189
|
+
display: inline-flex;
|
|
2190
|
+
align-items: center;
|
|
2191
|
+
gap: 6px;
|
|
2192
|
+
padding: 0 14px;
|
|
2193
|
+
height: 36px;
|
|
2194
|
+
background: var(--bg-card);
|
|
2195
|
+
border: 1px solid var(--border);
|
|
2196
|
+
border-radius: var(--radius-sm);
|
|
2197
|
+
color: var(--text-secondary);
|
|
2198
|
+
font-size: 13px;
|
|
2199
|
+
font-family: var(--font-sans);
|
|
2200
|
+
cursor: pointer;
|
|
2201
|
+
transition: all 0.15s;
|
|
2202
|
+
white-space: nowrap;
|
|
2203
|
+
position: relative;
|
|
2204
|
+
}
|
|
2205
|
+
.sort-toggle:hover { border-color: var(--text-muted); color: var(--text); }
|
|
2206
|
+
.sort-toggle.active { border-color: var(--accent); color: var(--accent); background: var(--accent-subtle); }
|
|
2207
|
+
|
|
2208
|
+
.sort-active-label {
|
|
2209
|
+
font-size: 11px;
|
|
2210
|
+
font-weight: 600;
|
|
2211
|
+
color: var(--accent);
|
|
2212
|
+
margin-left: 2px;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
/* Sort dropdown */
|
|
2216
|
+
.sort-dropdown {
|
|
2217
|
+
position: absolute;
|
|
2218
|
+
top: 100%;
|
|
2219
|
+
right: 0;
|
|
2220
|
+
margin-top: 4px;
|
|
2221
|
+
background: var(--bg-card);
|
|
2222
|
+
border: 1px solid var(--border);
|
|
2223
|
+
border-radius: var(--radius-sm);
|
|
2224
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
2225
|
+
z-index: 100;
|
|
2226
|
+
min-width: 180px;
|
|
2227
|
+
padding: 4px;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
.sort-options {
|
|
2231
|
+
display: flex;
|
|
2232
|
+
flex-direction: column;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
.sort-option {
|
|
2236
|
+
display: flex;
|
|
2237
|
+
align-items: center;
|
|
2238
|
+
justify-content: space-between;
|
|
2239
|
+
padding: 8px 12px;
|
|
2240
|
+
background: none;
|
|
2241
|
+
border: none;
|
|
2242
|
+
color: var(--text-secondary);
|
|
2243
|
+
font-size: 13px;
|
|
2244
|
+
font-family: var(--font-sans);
|
|
2245
|
+
cursor: pointer;
|
|
2246
|
+
border-radius: var(--radius-sm);
|
|
2247
|
+
transition: background 0.1s;
|
|
2248
|
+
width: 100%;
|
|
2249
|
+
text-align: left;
|
|
2250
|
+
}
|
|
2251
|
+
.sort-option:hover { background: var(--bg-hover); color: var(--text); }
|
|
2252
|
+
.sort-option.active { color: var(--accent); font-weight: 600; }
|
|
2253
|
+
|
|
2254
|
+
.sort-dir-icon {
|
|
2255
|
+
font-size: 14px;
|
|
2256
|
+
font-weight: 700;
|
|
2257
|
+
color: var(--accent);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
.sort-clear {
|
|
2261
|
+
display: block;
|
|
2262
|
+
width: 100%;
|
|
2263
|
+
padding: 8px 12px;
|
|
2264
|
+
background: none;
|
|
2265
|
+
border: none;
|
|
2266
|
+
border-top: 1px solid var(--border);
|
|
2267
|
+
color: var(--text-muted);
|
|
2268
|
+
font-size: 12px;
|
|
2269
|
+
font-family: var(--font-sans);
|
|
2270
|
+
cursor: pointer;
|
|
2271
|
+
text-align: center;
|
|
2272
|
+
margin-top: 4px;
|
|
2273
|
+
}
|
|
2274
|
+
.sort-clear:hover { color: var(--danger); }
|
|
2275
|
+
|
|
2187
2276
|
.view-toggle {
|
|
2188
2277
|
display: inline-flex;
|
|
2189
2278
|
border: 1px solid var(--border);
|
|
@@ -3474,6 +3563,167 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
3474
3563
|
}
|
|
3475
3564
|
.btn-secondary:hover { background: var(--surface-elevated); border-color: var(--text-muted); }
|
|
3476
3565
|
|
|
3566
|
+
/* ================================================================
|
|
3567
|
+
ONBOARDING TOUR & CHECKLIST
|
|
3568
|
+
================================================================ */
|
|
3569
|
+
|
|
3570
|
+
.tour-backdrop {
|
|
3571
|
+
position: fixed;
|
|
3572
|
+
inset: 0;
|
|
3573
|
+
background: rgba(0, 0, 0, 0.6);
|
|
3574
|
+
z-index: 9000;
|
|
3575
|
+
animation: fadeIn 0.2s ease;
|
|
3576
|
+
}
|
|
3577
|
+
.tour-spotlight {
|
|
3578
|
+
position: fixed;
|
|
3579
|
+
z-index: 9001;
|
|
3580
|
+
border-radius: 8px;
|
|
3581
|
+
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.6);
|
|
3582
|
+
pointer-events: none;
|
|
3583
|
+
transition: top 0.3s ease, left 0.3s ease, width 0.3s ease, height 0.3s ease;
|
|
3584
|
+
}
|
|
3585
|
+
.tour-tooltip {
|
|
3586
|
+
position: fixed;
|
|
3587
|
+
z-index: 9002;
|
|
3588
|
+
background: var(--bg-card);
|
|
3589
|
+
border: 1px solid var(--border);
|
|
3590
|
+
border-radius: 12px;
|
|
3591
|
+
padding: 24px;
|
|
3592
|
+
max-width: 380px;
|
|
3593
|
+
width: 90vw;
|
|
3594
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
3595
|
+
animation: slideUp 0.25s ease;
|
|
3596
|
+
}
|
|
3597
|
+
.tour-tooltip h3 {
|
|
3598
|
+
font-size: 18px;
|
|
3599
|
+
font-weight: 600;
|
|
3600
|
+
color: var(--text);
|
|
3601
|
+
margin: 0 0 8px 0;
|
|
3602
|
+
}
|
|
3603
|
+
.tour-tooltip p {
|
|
3604
|
+
font-size: 14px;
|
|
3605
|
+
color: var(--text-secondary);
|
|
3606
|
+
line-height: 1.5;
|
|
3607
|
+
margin: 0 0 20px 0;
|
|
3608
|
+
}
|
|
3609
|
+
.tour-step-indicator {
|
|
3610
|
+
display: flex;
|
|
3611
|
+
gap: 6px;
|
|
3612
|
+
margin-bottom: 16px;
|
|
3613
|
+
}
|
|
3614
|
+
.tour-step-dot {
|
|
3615
|
+
width: 8px;
|
|
3616
|
+
height: 8px;
|
|
3617
|
+
border-radius: 50%;
|
|
3618
|
+
background: var(--border);
|
|
3619
|
+
}
|
|
3620
|
+
.tour-step-dot.active {
|
|
3621
|
+
background: var(--accent);
|
|
3622
|
+
}
|
|
3623
|
+
.tour-actions {
|
|
3624
|
+
display: flex;
|
|
3625
|
+
justify-content: space-between;
|
|
3626
|
+
align-items: center;
|
|
3627
|
+
}
|
|
3628
|
+
.tour-actions .tour-skip {
|
|
3629
|
+
background: none;
|
|
3630
|
+
border: none;
|
|
3631
|
+
color: var(--text-muted);
|
|
3632
|
+
cursor: pointer;
|
|
3633
|
+
font-size: 13px;
|
|
3634
|
+
font-family: var(--font-sans);
|
|
3635
|
+
text-decoration: underline;
|
|
3636
|
+
padding: 0;
|
|
3637
|
+
}
|
|
3638
|
+
.tour-actions .tour-skip:hover { color: var(--text); }
|
|
3639
|
+
.tour-actions-right { display: flex; gap: 8px; }
|
|
3640
|
+
|
|
3641
|
+
.onboarding-checklist {
|
|
3642
|
+
background: var(--bg-card);
|
|
3643
|
+
border: 1px solid var(--border);
|
|
3644
|
+
border-radius: 12px;
|
|
3645
|
+
padding: 20px;
|
|
3646
|
+
margin-bottom: 16px;
|
|
3647
|
+
}
|
|
3648
|
+
.onboarding-checklist h3 {
|
|
3649
|
+
font-size: 15px;
|
|
3650
|
+
font-weight: 600;
|
|
3651
|
+
color: var(--text);
|
|
3652
|
+
margin: 0 0 4px 0;
|
|
3653
|
+
}
|
|
3654
|
+
.onboarding-checklist .subtitle {
|
|
3655
|
+
font-size: 13px;
|
|
3656
|
+
color: var(--text-muted);
|
|
3657
|
+
margin: 0 0 16px 0;
|
|
3658
|
+
}
|
|
3659
|
+
.checklist-item {
|
|
3660
|
+
display: flex;
|
|
3661
|
+
align-items: center;
|
|
3662
|
+
gap: 10px;
|
|
3663
|
+
padding: 8px 0;
|
|
3664
|
+
font-size: 14px;
|
|
3665
|
+
color: var(--text-secondary);
|
|
3666
|
+
}
|
|
3667
|
+
.checklist-item.completed {
|
|
3668
|
+
color: var(--text-muted);
|
|
3669
|
+
text-decoration: line-through;
|
|
3670
|
+
}
|
|
3671
|
+
.checklist-check {
|
|
3672
|
+
width: 20px;
|
|
3673
|
+
height: 20px;
|
|
3674
|
+
border-radius: 50%;
|
|
3675
|
+
border: 2px solid var(--border);
|
|
3676
|
+
display: flex;
|
|
3677
|
+
align-items: center;
|
|
3678
|
+
justify-content: center;
|
|
3679
|
+
flex-shrink: 0;
|
|
3680
|
+
font-size: 12px;
|
|
3681
|
+
line-height: 1;
|
|
3682
|
+
}
|
|
3683
|
+
.checklist-check.done {
|
|
3684
|
+
background: var(--success);
|
|
3685
|
+
border-color: var(--success);
|
|
3686
|
+
color: #fff;
|
|
3687
|
+
}
|
|
3688
|
+
.onboarding-progress {
|
|
3689
|
+
height: 4px;
|
|
3690
|
+
background: var(--border);
|
|
3691
|
+
border-radius: 2px;
|
|
3692
|
+
margin: 16px 0 12px;
|
|
3693
|
+
overflow: hidden;
|
|
3694
|
+
}
|
|
3695
|
+
.onboarding-progress-fill {
|
|
3696
|
+
height: 100%;
|
|
3697
|
+
background: var(--accent);
|
|
3698
|
+
border-radius: 2px;
|
|
3699
|
+
transition: width 0.4s ease;
|
|
3700
|
+
}
|
|
3701
|
+
.onboarding-tour-btn {
|
|
3702
|
+
width: 100%;
|
|
3703
|
+
margin-top: 12px;
|
|
3704
|
+
}
|
|
3705
|
+
.onboarding-dismiss {
|
|
3706
|
+
display: block;
|
|
3707
|
+
text-align: center;
|
|
3708
|
+
margin-top: 8px;
|
|
3709
|
+
background: none;
|
|
3710
|
+
border: none;
|
|
3711
|
+
color: var(--text-muted);
|
|
3712
|
+
font-size: 12px;
|
|
3713
|
+
cursor: pointer;
|
|
3714
|
+
font-family: var(--font-sans);
|
|
3715
|
+
width: 100%;
|
|
3716
|
+
padding: 4px 0;
|
|
3717
|
+
}
|
|
3718
|
+
.onboarding-dismiss:hover { color: var(--text); }
|
|
3719
|
+
|
|
3720
|
+
@media (max-width: 900px) {
|
|
3721
|
+
.tour-tooltip {
|
|
3722
|
+
max-width: 90vw;
|
|
3723
|
+
padding: 20px;
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3477
3727
|
/* ================================================================
|
|
3478
3728
|
COSTS PAGE
|
|
3479
3729
|
================================================================ */
|
|
@@ -3852,6 +4102,7 @@ function Icon({ name, size = 18, className = '' }) {
|
|
|
3852
4102
|
lightbulb: html`<path d="M8 1a5 5 0 00-3 9c.5.6.8 1.2 1 1.8V13h4v-1.2c.2-.6.5-1.2 1-1.8A5 5 0 008 1z"/><line x1="6" y1="13.5" x2="10" y2="13.5"/><line x1="6.5" y1="15" x2="9.5" y2="15"/>`,
|
|
3853
4103
|
help: html`<circle cx="8" cy="8" r="6.5"/><path d="M6 6.5a2 2 0 013.7 1c0 1.5-1.7 1.5-1.7 3"/><circle cx="8" cy="12" r="0.5" fill="currentColor" stroke="none"/>`,
|
|
3854
4104
|
filter: html`<polygon points="1.5,2 14.5,2 9.5,8.5 9.5,13 6.5,14.5 6.5,8.5"/>`,
|
|
4105
|
+
'arrow-up-down': html`<path d="M4 6l4-4 4 4"/><path d="M4 10l4 4 4-4"/>`,
|
|
3855
4106
|
costs: html`<circle cx="8" cy="8" r="6.5"/><path d="M8 3.5v1m0 7v1M6 10.5c0 .8.9 1.5 2 1.5s2-.7 2-1.5S9.1 9 8 9s-2-.7-2-1.5S6.9 6 8 6s2 .7 2 1.5"/>`,
|
|
3856
4107
|
};
|
|
3857
4108
|
|
|
@@ -3953,7 +4204,7 @@ function Sidebar({ currentHash, factoryName, theme, onToggleTheme, selectedProje
|
|
|
3953
4204
|
];
|
|
3954
4205
|
|
|
3955
4206
|
return html`
|
|
3956
|
-
<aside class=${'sidebar' + (mobileOpen ? ' open' : '')}>
|
|
4207
|
+
<aside class=${'sidebar' + (mobileOpen ? ' open' : '')} data-tour="sidebar">
|
|
3957
4208
|
<div class="sidebar-brand">
|
|
3958
4209
|
<svg class="brand-logo-svg" viewBox="0 0 400 320" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="BeastMode — Dark Factory">
|
|
3959
4210
|
<g transform="translate(200,130)">
|
|
@@ -4003,7 +4254,8 @@ function Sidebar({ currentHash, factoryName, theme, onToggleTheme, selectedProje
|
|
|
4003
4254
|
${links.map(link => html`
|
|
4004
4255
|
<li key=${link.path}>
|
|
4005
4256
|
<a href=${link.path}
|
|
4006
|
-
class=${currentHash === link.path || (link.path !== '#/' && currentHash.startsWith(link.path)) ? 'active' : ''}
|
|
4257
|
+
class=${currentHash === link.path || (link.path !== '#/' && currentHash.startsWith(link.path)) ? 'active' : ''}
|
|
4258
|
+
data-tour=${link.path === '#/help' ? 'help-link' : null}>
|
|
4007
4259
|
<${Icon} name=${link.icon} />
|
|
4008
4260
|
${link.label}
|
|
4009
4261
|
</a>
|
|
@@ -4044,11 +4296,15 @@ function statusBadgeClass(status) {
|
|
|
4044
4296
|
function DashboardPage({ selectedProject, onProjectChange }) {
|
|
4045
4297
|
const [status, setStatus] = useState(null);
|
|
4046
4298
|
const [recentItems, setRecentItems] = useState([]);
|
|
4299
|
+
const [allItems, setAllItems] = useState([]);
|
|
4047
4300
|
const [runs, setRuns] = useState([]);
|
|
4048
4301
|
const [projects, setProjects] = useState([]);
|
|
4049
4302
|
const [runnerData, setRunnerData] = useState({ count: 0, offline: 0 });
|
|
4050
4303
|
const [loading, setLoading] = useState(true);
|
|
4051
4304
|
const [error, setError] = useState(null);
|
|
4305
|
+
const [onboardingDismissed, setOnboardingDismissed] = useState(() => {
|
|
4306
|
+
try { return !!localStorage.getItem('beastmode-onboarding-dismissed'); } catch (_) { return false; }
|
|
4307
|
+
});
|
|
4052
4308
|
|
|
4053
4309
|
useEffect(() => {
|
|
4054
4310
|
function fetchDashboard() {
|
|
@@ -4059,7 +4315,10 @@ function DashboardPage({ selectedProject, onProjectChange }) {
|
|
|
4059
4315
|
api('GET', '/api/projects').catch(() => ({ projects: [] })),
|
|
4060
4316
|
]).then(([s, boardData, runsData, projectsData]) => {
|
|
4061
4317
|
setStatus(s);
|
|
4062
|
-
const
|
|
4318
|
+
const allBoardItems = boardData.items || [];
|
|
4319
|
+
setAllItems(allBoardItems);
|
|
4320
|
+
const sorted = allBoardItems
|
|
4321
|
+
.slice()
|
|
4063
4322
|
.sort((a, b) => new Date(normalizeDt(b.updated_at || b.created_at) || 0) - new Date(normalizeDt(a.updated_at || a.created_at) || 0))
|
|
4064
4323
|
.slice(0, 10);
|
|
4065
4324
|
setRecentItems(sorted);
|
|
@@ -4246,6 +4505,48 @@ function DashboardPage({ selectedProject, onProjectChange }) {
|
|
|
4246
4505
|
</div>
|
|
4247
4506
|
|
|
4248
4507
|
<div class="page-dashboard-sidebar">
|
|
4508
|
+
${!onboardingDismissed && (() => {
|
|
4509
|
+
const hasProject = projects.length >= 1;
|
|
4510
|
+
const hasItem = allItems.length >= 1;
|
|
4511
|
+
const hasDone = allItems.some(i => (i.status || '').toLowerCase() === 'done');
|
|
4512
|
+
const checks = [
|
|
4513
|
+
{ label: 'Add a project', done: hasProject },
|
|
4514
|
+
{ label: 'Create your first task', done: hasItem },
|
|
4515
|
+
{ label: 'Complete a task', done: hasDone },
|
|
4516
|
+
];
|
|
4517
|
+
const completedCount = checks.filter(c => c.done).length;
|
|
4518
|
+
const allDone = completedCount === checks.length;
|
|
4519
|
+
const dismiss = () => {
|
|
4520
|
+
try { localStorage.setItem('beastmode-onboarding-dismissed', 'true'); } catch (_) {}
|
|
4521
|
+
setOnboardingDismissed(true);
|
|
4522
|
+
};
|
|
4523
|
+
return html`
|
|
4524
|
+
<div class="onboarding-checklist" data-testid="onboarding-checklist">
|
|
4525
|
+
<h3>Getting Started</h3>
|
|
4526
|
+
<p class="subtitle">Complete these steps to get up and running</p>
|
|
4527
|
+
${allDone ? html`
|
|
4528
|
+
<p style="font-size:14px;color:var(--success);font-weight:600;margin:8px 0 0;" data-testid="onboarding-congrats">
|
|
4529
|
+
You're all set! BeastMode is ready to build.
|
|
4530
|
+
</p>
|
|
4531
|
+
` : html`
|
|
4532
|
+
<div class="onboarding-progress">
|
|
4533
|
+
<div class="onboarding-progress-fill" style=${'width:' + ((completedCount / checks.length) * 100) + '%'}></div>
|
|
4534
|
+
</div>
|
|
4535
|
+
${checks.map((c, i) => html`
|
|
4536
|
+
<div key=${i} class=${'checklist-item' + (c.done ? ' completed' : '')} data-testid=${'checklist-item-' + i}>
|
|
4537
|
+
<div class=${'checklist-check' + (c.done ? ' done' : '')}>${c.done ? '✓' : ''}</div>
|
|
4538
|
+
<span>${c.label}</span>
|
|
4539
|
+
</div>
|
|
4540
|
+
`)}
|
|
4541
|
+
<button class="btn btn-primary btn-pill onboarding-tour-btn" data-testid="onboarding-tour-btn"
|
|
4542
|
+
onClick=${() => { if (typeof window.__bmStartTour === 'function') window.__bmStartTour(); }}>
|
|
4543
|
+
Take the guided tour
|
|
4544
|
+
</button>
|
|
4545
|
+
`}
|
|
4546
|
+
<button class="onboarding-dismiss" data-testid="onboarding-dismiss" onClick=${dismiss}>Dismiss</button>
|
|
4547
|
+
</div>
|
|
4548
|
+
`;
|
|
4549
|
+
})()}
|
|
4249
4550
|
<div class="card">
|
|
4250
4551
|
<div class="card-header">
|
|
4251
4552
|
<h3>Recent Activity</h3>
|
|
@@ -5895,8 +6196,7 @@ function PipelineView({
|
|
|
5895
6196
|
selectedProject,
|
|
5896
6197
|
setSelectedItem,
|
|
5897
6198
|
deleteItem,
|
|
5898
|
-
|
|
5899
|
-
cycleSort,
|
|
6199
|
+
globalSort,
|
|
5900
6200
|
sortColumnItems,
|
|
5901
6201
|
costsByItem,
|
|
5902
6202
|
envVerifyByItem,
|
|
@@ -5995,8 +6295,7 @@ function PipelineView({
|
|
|
5995
6295
|
${columns.map(colId => {
|
|
5996
6296
|
const meta = getColumnMeta(colId);
|
|
5997
6297
|
const rawColItems = laneItemList.filter(item => getItemColumn(item, columns) === colId);
|
|
5998
|
-
const colItems = sortColumnItems(rawColItems,
|
|
5999
|
-
const sortMode = columnSorts[colId] || '';
|
|
6298
|
+
const colItems = sortColumnItems(rawColItems, globalSort);
|
|
6000
6299
|
return html`
|
|
6001
6300
|
<div class="pipeline-column"
|
|
6002
6301
|
data-testid=${'pipeline-col-' + colId}
|
|
@@ -6007,9 +6306,8 @@ function PipelineView({
|
|
|
6007
6306
|
onDrop=${e => onDrop(e, colId)}>
|
|
6008
6307
|
<div class="pipeline-column-header"
|
|
6009
6308
|
style=${'--col-color: ' + meta.color}
|
|
6010
|
-
title=${meta.tooltip}
|
|
6011
|
-
|
|
6012
|
-
<span>${meta.label}${sortMode && html`<span class="sort-indicator">${sortMode === 'priority' ? '\u25BC' : '\u25B2'}</span>`}</span>
|
|
6309
|
+
title=${meta.tooltip}>
|
|
6310
|
+
<span>${meta.label}</span>
|
|
6013
6311
|
<span class=${'kanban-count' + (colItems.length === 0 ? ' kanban-count-zero' : '')}>${colItems.length}</span>
|
|
6014
6312
|
</div>
|
|
6015
6313
|
<div class="pipeline-column-items">
|
|
@@ -6260,7 +6558,14 @@ function BoardPage({ selectedProject }) {
|
|
|
6260
6558
|
const allKeys = ((window.PIPELINE_CONFIG && window.PIPELINE_CONFIG.swimlanes) || []).map(s => s.key);
|
|
6261
6559
|
return new Set(allKeys);
|
|
6262
6560
|
});
|
|
6263
|
-
const [
|
|
6561
|
+
const [globalSort, setGlobalSort] = useState(() => {
|
|
6562
|
+
try {
|
|
6563
|
+
const saved = localStorage.getItem('beastmode-sort-mode');
|
|
6564
|
+
if (saved) return JSON.parse(saved);
|
|
6565
|
+
} catch {}
|
|
6566
|
+
return { field: '', direction: 'asc' };
|
|
6567
|
+
});
|
|
6568
|
+
const [sortOpen, setSortOpen] = useState(false);
|
|
6264
6569
|
const [epicCollapseKey, setEpicCollapseKey] = useState(0);
|
|
6265
6570
|
const [costsByItem, setCostsByItem] = useState({});
|
|
6266
6571
|
const [envVerifyByItem, setEnvVerifyByItem] = useState({});
|
|
@@ -6699,6 +7004,25 @@ function BoardPage({ selectedProject }) {
|
|
|
6699
7004
|
} catch {}
|
|
6700
7005
|
}, [activeSwimlanesSet]);
|
|
6701
7006
|
|
|
7007
|
+
// Persist sort preference to localStorage whenever it changes.
|
|
7008
|
+
useEffect(() => {
|
|
7009
|
+
localStorage.setItem('beastmode-sort-mode', JSON.stringify(globalSort));
|
|
7010
|
+
}, [globalSort]);
|
|
7011
|
+
|
|
7012
|
+
// Close sort dropdown when clicking outside.
|
|
7013
|
+
useEffect(() => {
|
|
7014
|
+
if (!sortOpen) return;
|
|
7015
|
+
const handler = (e) => {
|
|
7016
|
+
const dropdown = document.querySelector('.sort-dropdown');
|
|
7017
|
+
const toggle = document.querySelector('[data-testid="sort-toggle"]');
|
|
7018
|
+
if (dropdown && !dropdown.contains(e.target) && toggle && !toggle.contains(e.target)) {
|
|
7019
|
+
setSortOpen(false);
|
|
7020
|
+
}
|
|
7021
|
+
};
|
|
7022
|
+
document.addEventListener('click', handler, true);
|
|
7023
|
+
return () => document.removeEventListener('click', handler, true);
|
|
7024
|
+
}, [sortOpen]);
|
|
7025
|
+
|
|
6702
7026
|
// Expose swimlane state for external scenario verification.
|
|
6703
7027
|
useEffect(() => {
|
|
6704
7028
|
const swimlanes = (window.PIPELINE_CONFIG && window.PIPELINE_CONFIG.swimlanes) || [];
|
|
@@ -6780,28 +7104,32 @@ function BoardPage({ selectedProject }) {
|
|
|
6780
7104
|
const activeFilterCount = _baseFilterCount + (_swimlaneFilterActive ? 1 : 0);
|
|
6781
7105
|
|
|
6782
7106
|
// ── Column sorting ──
|
|
7107
|
+
const SORT_LABELS = {
|
|
7108
|
+
priority: 'Priority',
|
|
7109
|
+
name: 'Name',
|
|
7110
|
+
created: 'Created',
|
|
7111
|
+
updated: 'Updated',
|
|
7112
|
+
status_age: 'Time in Status',
|
|
7113
|
+
};
|
|
6783
7114
|
const PRIORITY_ORDER = { 'Critical': 0, 'High': 1, 'Medium': 2, 'Low': 3, '': 4 };
|
|
6784
|
-
const sortColumnItems = (colItems,
|
|
6785
|
-
if (!
|
|
7115
|
+
const sortColumnItems = (colItems, sort) => {
|
|
7116
|
+
if (!sort || !sort.field) return colItems;
|
|
7117
|
+
const dir = sort.direction === 'desc' ? -1 : 1;
|
|
6786
7118
|
const sorted = [...colItems];
|
|
6787
|
-
if (
|
|
6788
|
-
sorted.sort((a, b) => (PRIORITY_ORDER[a.priority || ''] ?? 4) - (PRIORITY_ORDER[b.priority || ''] ?? 4));
|
|
6789
|
-
} else if (
|
|
6790
|
-
sorted.sort((a, b) => (a.name || a.title || '').localeCompare(b.name || b.title || ''));
|
|
7119
|
+
if (sort.field === 'priority') {
|
|
7120
|
+
sorted.sort((a, b) => dir * ((PRIORITY_ORDER[a.priority || ''] ?? 4) - (PRIORITY_ORDER[b.priority || ''] ?? 4)));
|
|
7121
|
+
} else if (sort.field === 'name') {
|
|
7122
|
+
sorted.sort((a, b) => dir * (a.name || a.title || '').localeCompare(b.name || b.title || ''));
|
|
7123
|
+
} else if (sort.field === 'created') {
|
|
7124
|
+
sorted.sort((a, b) => dir * ((a.created_at || '').localeCompare(b.created_at || '')));
|
|
7125
|
+
} else if (sort.field === 'updated') {
|
|
7126
|
+
sorted.sort((a, b) => dir * ((a.updated_at || '').localeCompare(b.updated_at || '')));
|
|
7127
|
+
} else if (sort.field === 'status_age') {
|
|
7128
|
+
sorted.sort((a, b) => dir * ((a.status_changed_at || a.updated_at || '').localeCompare(b.status_changed_at || b.updated_at || '')));
|
|
6791
7129
|
}
|
|
6792
7130
|
return sorted;
|
|
6793
7131
|
};
|
|
6794
7132
|
|
|
6795
|
-
const cycleSort = (colId) => {
|
|
6796
|
-
setColumnSorts(prev => {
|
|
6797
|
-
const current = prev[colId] || '';
|
|
6798
|
-
const next = current === '' ? 'priority' : current === 'priority' ? 'name' : '';
|
|
6799
|
-
const copy = { ...prev };
|
|
6800
|
-
if (next) copy[colId] = next; else delete copy[colId];
|
|
6801
|
-
return copy;
|
|
6802
|
-
});
|
|
6803
|
-
};
|
|
6804
|
-
|
|
6805
7133
|
// Unique project IDs and parent epics for filter dropdowns
|
|
6806
7134
|
const uniqueProjects = [...new Set(items.map(i => i.project_id).filter(Boolean))];
|
|
6807
7135
|
const uniqueEpics = [...new Set(items.map(i => i.parent_epic).filter(Boolean))].map(String);
|
|
@@ -6864,7 +7192,7 @@ function BoardPage({ selectedProject }) {
|
|
|
6864
7192
|
|
|
6865
7193
|
${error && html`<div class="error-msg">${error}</div>`}
|
|
6866
7194
|
|
|
6867
|
-
<div class="board-header-row">
|
|
7195
|
+
<div class="board-header-row" data-tour="board-header">
|
|
6868
7196
|
<div class="board-stats-bar">
|
|
6869
7197
|
<span class="stat-label">${viewMode === 'pipeline' ? 'Pipeline' : 'Board'}</span>
|
|
6870
7198
|
<span class="stat-item"><strong>${totalItems}</strong>${isFiltered ? '/' + items.length : ''} total</span>
|
|
@@ -6887,7 +7215,47 @@ function BoardPage({ selectedProject }) {
|
|
|
6887
7215
|
${activeFilterCount > 0 && html`<span class="filter-active-count">${activeFilterCount}</span>`}
|
|
6888
7216
|
</button>
|
|
6889
7217
|
|
|
6890
|
-
<
|
|
7218
|
+
<div style="position: relative;">
|
|
7219
|
+
<button class=${'sort-toggle' + (sortOpen || globalSort.field ? ' active' : '')}
|
|
7220
|
+
onClick=${() => setSortOpen(v => !v)}
|
|
7221
|
+
data-testid="sort-toggle">
|
|
7222
|
+
<${Icon} name="arrow-up-down" size=${13} />
|
|
7223
|
+
Sort
|
|
7224
|
+
${globalSort.field && html`<span class="sort-active-label">${SORT_LABELS[globalSort.field]}</span>`}
|
|
7225
|
+
</button>
|
|
7226
|
+
${sortOpen && html`
|
|
7227
|
+
<div class="sort-dropdown" data-testid="sort-dropdown">
|
|
7228
|
+
<div class="sort-options">
|
|
7229
|
+
${Object.entries(SORT_LABELS).map(([key, label]) => html`
|
|
7230
|
+
<button key=${key}
|
|
7231
|
+
class=${'sort-option' + (globalSort.field === key ? ' active' : '')}
|
|
7232
|
+
data-testid=${'sort-option-' + key}
|
|
7233
|
+
onClick=${() => {
|
|
7234
|
+
if (globalSort.field === key) {
|
|
7235
|
+
setGlobalSort(s => ({ ...s, direction: s.direction === 'asc' ? 'desc' : 'asc' }));
|
|
7236
|
+
} else {
|
|
7237
|
+
const defaultDir = (key === 'created' || key === 'updated') ? 'desc' : 'asc';
|
|
7238
|
+
setGlobalSort({ field: key, direction: defaultDir });
|
|
7239
|
+
}
|
|
7240
|
+
}}>
|
|
7241
|
+
<span>${label}</span>
|
|
7242
|
+
${globalSort.field === key && html`
|
|
7243
|
+
<span class="sort-dir-icon">${globalSort.direction === 'asc' ? '↑' : '↓'}</span>
|
|
7244
|
+
`}
|
|
7245
|
+
</button>
|
|
7246
|
+
`)}
|
|
7247
|
+
</div>
|
|
7248
|
+
${globalSort.field && html`
|
|
7249
|
+
<button class="sort-clear" data-testid="sort-clear"
|
|
7250
|
+
onClick=${() => { setGlobalSort({ field: '', direction: 'asc' }); setSortOpen(false); }}>
|
|
7251
|
+
Clear sort
|
|
7252
|
+
</button>
|
|
7253
|
+
`}
|
|
7254
|
+
</div>
|
|
7255
|
+
`}
|
|
7256
|
+
</div>
|
|
7257
|
+
|
|
7258
|
+
<button class="btn btn-primary" data-tour="add-item" onClick=${() => setShowCreateDialog(true)}>
|
|
6891
7259
|
<${Icon} name="plus" size=${14} />
|
|
6892
7260
|
New Task
|
|
6893
7261
|
</button>
|
|
@@ -6984,8 +7352,7 @@ function BoardPage({ selectedProject }) {
|
|
|
6984
7352
|
selectedProject=${selectedProject}
|
|
6985
7353
|
setSelectedItem=${setSelectedItem}
|
|
6986
7354
|
deleteItem=${deleteItem}
|
|
6987
|
-
|
|
6988
|
-
cycleSort=${cycleSort}
|
|
7355
|
+
globalSort=${globalSort}
|
|
6989
7356
|
sortColumnItems=${sortColumnItems}
|
|
6990
7357
|
costsByItem=${costsByItem}
|
|
6991
7358
|
envVerifyByItem=${envVerifyByItem}
|
|
@@ -7020,15 +7387,14 @@ function BoardPage({ selectedProject }) {
|
|
|
7020
7387
|
if (col.also && col.also.includes(i.status)) return true;
|
|
7021
7388
|
return false;
|
|
7022
7389
|
});
|
|
7023
|
-
const colItems = sortColumnItems(rawColItems,
|
|
7024
|
-
const sortMode = columnSorts[col.id] || '';
|
|
7390
|
+
const colItems = sortColumnItems(rawColItems, globalSort);
|
|
7025
7391
|
return html`
|
|
7026
7392
|
<div class="kanban-column" key=${col.id}
|
|
7027
7393
|
onDragOver=${e => onDragOver(e, col.id)}
|
|
7028
7394
|
onDragLeave=${onDragLeave}
|
|
7029
7395
|
onDrop=${e => onDrop(e, col.id)}>
|
|
7030
|
-
<div class="kanban-column-header" style=${'--col-color: ' + col.color} title=${col.tooltip}
|
|
7031
|
-
<span>${col.label}
|
|
7396
|
+
<div class="kanban-column-header" style=${'--col-color: ' + col.color} title=${col.tooltip}>
|
|
7397
|
+
<span>${col.label}</span>
|
|
7032
7398
|
<span class=${'kanban-count' + (colItems.length === 0 ? ' kanban-count-zero' : '')}>${colItems.length}</span>
|
|
7033
7399
|
</div>
|
|
7034
7400
|
<div class="kanban-items">
|
|
@@ -8877,10 +9243,33 @@ function HooksTab() {
|
|
|
8877
9243
|
// ================================================================
|
|
8878
9244
|
|
|
8879
9245
|
function HelpPage() {
|
|
9246
|
+
const startTour = () => {
|
|
9247
|
+
navigate('#/board');
|
|
9248
|
+
setTimeout(() => {
|
|
9249
|
+
if (typeof window.__bmStartTour === 'function') window.__bmStartTour();
|
|
9250
|
+
}, 300);
|
|
9251
|
+
};
|
|
8880
9252
|
return html`
|
|
8881
9253
|
<div>
|
|
8882
9254
|
<h2 style="margin:0 0 24px;font-size:22px;font-weight:700;color:var(--text-primary);">Help & Documentation</h2>
|
|
8883
9255
|
|
|
9256
|
+
<div style="margin-bottom: 24px; padding: 16px; background: var(--surface-elevated);
|
|
9257
|
+
border: 1px solid var(--border); border-radius: 8px;
|
|
9258
|
+
display: flex; align-items: center; justify-content: space-between;"
|
|
9259
|
+
data-testid="help-tour-card">
|
|
9260
|
+
<div>
|
|
9261
|
+
<div style="font-weight: 600; color: var(--text); margin-bottom: 4px;">
|
|
9262
|
+
Interactive Tour
|
|
9263
|
+
</div>
|
|
9264
|
+
<div style="font-size: 13px; color: var(--text-muted);">
|
|
9265
|
+
Walk through the key features of BeastMode step by step.
|
|
9266
|
+
</div>
|
|
9267
|
+
</div>
|
|
9268
|
+
<button class="btn btn-primary" data-testid="help-start-tour" onClick=${startTour}>
|
|
9269
|
+
Start Tour
|
|
9270
|
+
</button>
|
|
9271
|
+
</div>
|
|
9272
|
+
|
|
8884
9273
|
<div class="settings-section">
|
|
8885
9274
|
<h3>Getting Started</h3>
|
|
8886
9275
|
<div style="line-height:1.8;color:var(--text-secondary);">
|
|
@@ -10706,6 +11095,211 @@ function FactoryStatusBar() {
|
|
|
10706
11095
|
// App Root
|
|
10707
11096
|
// ================================================================
|
|
10708
11097
|
|
|
11098
|
+
// ================================================================
|
|
11099
|
+
// Onboarding Tour
|
|
11100
|
+
// ================================================================
|
|
11101
|
+
|
|
11102
|
+
const TOUR_STEPS = [
|
|
11103
|
+
{
|
|
11104
|
+
target: null,
|
|
11105
|
+
title: 'Welcome to BeastMode',
|
|
11106
|
+
body: "BeastMode turns your ideas into working software — describe what you want in plain language, and the pipeline writes the spec, code, and tests. Let's take a quick tour.",
|
|
11107
|
+
placement: 'center',
|
|
11108
|
+
},
|
|
11109
|
+
{
|
|
11110
|
+
target: '[data-tour="sidebar"]',
|
|
11111
|
+
title: 'Navigation',
|
|
11112
|
+
body: 'Use the sidebar to move between pages. The Board is where your tasks live. The Dashboard shows factory health and stats.',
|
|
11113
|
+
placement: 'right',
|
|
11114
|
+
},
|
|
11115
|
+
{
|
|
11116
|
+
target: '[data-tour="board-header"]',
|
|
11117
|
+
title: 'Your Task Board',
|
|
11118
|
+
body: 'Tasks flow through the pipeline from left to right: Ready → Working on it → Review → Done. BeastMode handles each stage automatically.',
|
|
11119
|
+
placement: 'bottom',
|
|
11120
|
+
},
|
|
11121
|
+
{
|
|
11122
|
+
target: '[data-tour="add-item"]',
|
|
11123
|
+
title: 'Create a Task',
|
|
11124
|
+
body: 'Click here to create a new task. Describe what you want to build — BeastMode writes the spec, plans the work, and implements it.',
|
|
11125
|
+
placement: 'bottom-start',
|
|
11126
|
+
},
|
|
11127
|
+
{
|
|
11128
|
+
target: '[data-tour="help-link"]',
|
|
11129
|
+
title: 'Need Help?',
|
|
11130
|
+
body: 'Visit the Help page anytime for guides on task types, pipeline stages, and troubleshooting tips.',
|
|
11131
|
+
placement: 'right',
|
|
11132
|
+
},
|
|
11133
|
+
{
|
|
11134
|
+
target: null,
|
|
11135
|
+
title: "You're Ready!",
|
|
11136
|
+
body: 'Start by creating your first task. BeastMode takes it from there. You can restart this tour anytime from the Help page.',
|
|
11137
|
+
placement: 'center',
|
|
11138
|
+
},
|
|
11139
|
+
];
|
|
11140
|
+
|
|
11141
|
+
function OnboardingTour() {
|
|
11142
|
+
const [currentStep, setCurrentStep] = useState(null);
|
|
11143
|
+
const [targetRect, setTargetRect] = useState(null);
|
|
11144
|
+
|
|
11145
|
+
// Auto-launch on first visit.
|
|
11146
|
+
useEffect(() => {
|
|
11147
|
+
let cancelled = false;
|
|
11148
|
+
try {
|
|
11149
|
+
if (localStorage.getItem('beastmode-tour-completed')) return;
|
|
11150
|
+
} catch (_) { return; }
|
|
11151
|
+
if (!location.hash || location.hash === '#/' || location.hash === '#') {
|
|
11152
|
+
navigate('#/board');
|
|
11153
|
+
}
|
|
11154
|
+
const t = setTimeout(() => {
|
|
11155
|
+
if (!cancelled) setCurrentStep(0);
|
|
11156
|
+
}, 600);
|
|
11157
|
+
return () => { cancelled = true; clearTimeout(t); };
|
|
11158
|
+
}, []);
|
|
11159
|
+
|
|
11160
|
+
// Expose manual launcher globally for Dashboard checklist + Help page button.
|
|
11161
|
+
useEffect(() => {
|
|
11162
|
+
window.__bmStartTour = () => setCurrentStep(0);
|
|
11163
|
+
return () => { try { delete window.__bmStartTour; } catch (_) {} };
|
|
11164
|
+
}, []);
|
|
11165
|
+
|
|
11166
|
+
// Recalculate target rect on step change and on resize.
|
|
11167
|
+
useEffect(() => {
|
|
11168
|
+
if (currentStep === null) {
|
|
11169
|
+
setTargetRect(null);
|
|
11170
|
+
return;
|
|
11171
|
+
}
|
|
11172
|
+
const step = TOUR_STEPS[currentStep];
|
|
11173
|
+
function measure() {
|
|
11174
|
+
if (!step || !step.target) {
|
|
11175
|
+
setTargetRect(null);
|
|
11176
|
+
return;
|
|
11177
|
+
}
|
|
11178
|
+
const el = document.querySelector(step.target);
|
|
11179
|
+
if (!el) {
|
|
11180
|
+
setTargetRect(null);
|
|
11181
|
+
return;
|
|
11182
|
+
}
|
|
11183
|
+
const r = el.getBoundingClientRect();
|
|
11184
|
+
setTargetRect({ top: r.top, left: r.left, right: r.right, bottom: r.bottom, width: r.width, height: r.height });
|
|
11185
|
+
}
|
|
11186
|
+
measure();
|
|
11187
|
+
let timer = null;
|
|
11188
|
+
const onResize = () => {
|
|
11189
|
+
if (timer) clearTimeout(timer);
|
|
11190
|
+
timer = setTimeout(measure, 100);
|
|
11191
|
+
};
|
|
11192
|
+
window.addEventListener('resize', onResize);
|
|
11193
|
+
window.addEventListener('scroll', onResize, true);
|
|
11194
|
+
return () => {
|
|
11195
|
+
window.removeEventListener('resize', onResize);
|
|
11196
|
+
window.removeEventListener('scroll', onResize, true);
|
|
11197
|
+
if (timer) clearTimeout(timer);
|
|
11198
|
+
};
|
|
11199
|
+
}, [currentStep]);
|
|
11200
|
+
|
|
11201
|
+
const completeTour = useCallback(() => {
|
|
11202
|
+
try { localStorage.setItem('beastmode-tour-completed', 'true'); } catch (_) {}
|
|
11203
|
+
setCurrentStep(null);
|
|
11204
|
+
}, []);
|
|
11205
|
+
|
|
11206
|
+
// Escape to skip.
|
|
11207
|
+
useEffect(() => {
|
|
11208
|
+
if (currentStep === null) return;
|
|
11209
|
+
const onKey = (e) => {
|
|
11210
|
+
if (e.key === 'Escape') {
|
|
11211
|
+
e.preventDefault();
|
|
11212
|
+
completeTour();
|
|
11213
|
+
}
|
|
11214
|
+
};
|
|
11215
|
+
document.addEventListener('keydown', onKey);
|
|
11216
|
+
return () => document.removeEventListener('keydown', onKey);
|
|
11217
|
+
}, [currentStep, completeTour]);
|
|
11218
|
+
|
|
11219
|
+
if (currentStep === null) return null;
|
|
11220
|
+
|
|
11221
|
+
const step = TOUR_STEPS[currentStep];
|
|
11222
|
+
const isLast = currentStep === TOUR_STEPS.length - 1;
|
|
11223
|
+
const padding = 8;
|
|
11224
|
+
|
|
11225
|
+
// Spotlight position
|
|
11226
|
+
const spotlightStyle = targetRect ? {
|
|
11227
|
+
top: (targetRect.top - padding) + 'px',
|
|
11228
|
+
left: (targetRect.left - padding) + 'px',
|
|
11229
|
+
width: (targetRect.width + padding * 2) + 'px',
|
|
11230
|
+
height: (targetRect.height + padding * 2) + 'px',
|
|
11231
|
+
} : null;
|
|
11232
|
+
|
|
11233
|
+
// Tooltip position
|
|
11234
|
+
const tooltipWidth = 380;
|
|
11235
|
+
const tooltipApproxHeight = 220;
|
|
11236
|
+
const margin = 16;
|
|
11237
|
+
let tooltipStyle = {};
|
|
11238
|
+
if (!targetRect || step.placement === 'center') {
|
|
11239
|
+
tooltipStyle = {
|
|
11240
|
+
top: '50%',
|
|
11241
|
+
left: '50%',
|
|
11242
|
+
transform: 'translate(-50%, -50%)',
|
|
11243
|
+
};
|
|
11244
|
+
} else if (step.placement === 'right') {
|
|
11245
|
+
let top = targetRect.top + targetRect.height / 2 - tooltipApproxHeight / 2;
|
|
11246
|
+
let left = targetRect.right + margin;
|
|
11247
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - tooltipApproxHeight - margin));
|
|
11248
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - tooltipWidth - margin));
|
|
11249
|
+
tooltipStyle = { top: top + 'px', left: left + 'px' };
|
|
11250
|
+
} else if (step.placement === 'bottom') {
|
|
11251
|
+
let top = targetRect.bottom + margin;
|
|
11252
|
+
let left = targetRect.left + targetRect.width / 2 - tooltipWidth / 2;
|
|
11253
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - tooltipApproxHeight - margin));
|
|
11254
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - tooltipWidth - margin));
|
|
11255
|
+
tooltipStyle = { top: top + 'px', left: left + 'px' };
|
|
11256
|
+
} else if (step.placement === 'bottom-start') {
|
|
11257
|
+
let top = targetRect.bottom + margin;
|
|
11258
|
+
let left = targetRect.left;
|
|
11259
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - tooltipApproxHeight - margin));
|
|
11260
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - tooltipWidth - margin));
|
|
11261
|
+
tooltipStyle = { top: top + 'px', left: left + 'px' };
|
|
11262
|
+
} else {
|
|
11263
|
+
tooltipStyle = { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' };
|
|
11264
|
+
}
|
|
11265
|
+
|
|
11266
|
+
const styleStr = (obj) => Object.entries(obj).map(([k, v]) => {
|
|
11267
|
+
const kebab = k.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
11268
|
+
return kebab + ':' + v;
|
|
11269
|
+
}).join(';');
|
|
11270
|
+
|
|
11271
|
+
return html`
|
|
11272
|
+
<div>
|
|
11273
|
+
<div class="tour-backdrop" onClick=${completeTour}></div>
|
|
11274
|
+
${spotlightStyle ? html`<div class="tour-spotlight" style=${styleStr(spotlightStyle)}></div>` : null}
|
|
11275
|
+
<div class="tour-tooltip" data-testid="tour-tooltip" style=${styleStr(tooltipStyle)} onClick=${(e) => e.stopPropagation()}>
|
|
11276
|
+
<div class="tour-step-indicator">
|
|
11277
|
+
${TOUR_STEPS.map((_, i) => html`
|
|
11278
|
+
<span key=${i} class=${'tour-step-dot' + (i === currentStep ? ' active' : '')}></span>
|
|
11279
|
+
`)}
|
|
11280
|
+
</div>
|
|
11281
|
+
<h3>${step.title}</h3>
|
|
11282
|
+
<p>${step.body}</p>
|
|
11283
|
+
<div class="tour-actions">
|
|
11284
|
+
<button class="tour-skip" data-testid="tour-skip" onClick=${completeTour}>Skip Tour</button>
|
|
11285
|
+
<div class="tour-actions-right">
|
|
11286
|
+
${currentStep > 0 && html`
|
|
11287
|
+
<button class="btn" data-testid="tour-back" onClick=${() => setCurrentStep(s => s - 1)}>Back</button>
|
|
11288
|
+
`}
|
|
11289
|
+
<button class="btn btn-primary" data-testid="tour-next" onClick=${() => {
|
|
11290
|
+
if (isLast) {
|
|
11291
|
+
completeTour();
|
|
11292
|
+
} else {
|
|
11293
|
+
setCurrentStep(s => s + 1);
|
|
11294
|
+
}
|
|
11295
|
+
}}>${isLast ? 'Get Started' : 'Next'}</button>
|
|
11296
|
+
</div>
|
|
11297
|
+
</div>
|
|
11298
|
+
</div>
|
|
11299
|
+
</div>
|
|
11300
|
+
`;
|
|
11301
|
+
}
|
|
11302
|
+
|
|
10709
11303
|
function App() {
|
|
10710
11304
|
const hash = useHash();
|
|
10711
11305
|
const { theme, toggle: toggleTheme } = useTheme();
|
|
@@ -10787,6 +11381,7 @@ function App() {
|
|
|
10787
11381
|
<${FactoryStatusBar} />
|
|
10788
11382
|
${page}
|
|
10789
11383
|
</main>
|
|
11384
|
+
<${OnboardingTour} />
|
|
10790
11385
|
</div>
|
|
10791
11386
|
`;
|
|
10792
11387
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
f21d1ff83dc59d5ca4ea41a6e71c4ede76ee5ac7
|
package/dist/web/build-stamp.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
20260509-
|
|
1
|
+
20260509-231045-f21d1ff
|