@kitkat-vn/kitcode 0.2.0-staging.9 → 0.3.0-staging.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -28
- package/bin/kitcode.mjs +9 -1
- package/package.json +2 -2
- package/src/api.mjs +105 -3
- package/src/break-progress.mjs +94 -9
- package/src/claim-bundle.mjs +18 -5
- package/src/companion-window.mjs +7 -3
- package/src/cors.mjs +3 -1
- package/src/cycle-receipt.mjs +97 -0
- package/src/fonts/jetbrains-mono/OFL.txt +93 -0
- package/src/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/src/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/src/fonts/jetbrains-mono/jetbrains-mono-latin-700-normal.woff2 +0 -0
- package/src/fonts/jetbrains-mono/jetbrains-mono-vietnamese-400-normal.woff2 +0 -0
- package/src/fonts/jetbrains-mono/jetbrains-mono-vietnamese-500-normal.woff2 +0 -0
- package/src/fonts/jetbrains-mono/jetbrains-mono-vietnamese-700-normal.woff2 +0 -0
- package/src/hook-prompt.mjs +15 -3
- package/src/integration-spec.mjs +175 -48
- package/src/milestone-record.mjs +214 -0
- package/src/milestones-reached.mjs +163 -64
- package/src/next-break-reminder.mjs +4 -3
- package/src/onboarding-electron.mjs +12 -11
- package/src/onboarding-platform.mjs +5 -2
- package/src/onboarding-window.mjs +94 -63
- package/src/reward.mjs +214 -63
- package/src/runtime.mjs +48 -4
- package/src/welcome-words.mjs +115 -0
- package/src/window-page.mjs +16 -2
|
@@ -5,16 +5,35 @@ import {
|
|
|
5
5
|
setupPlatformTheme,
|
|
6
6
|
} from './onboarding-platform.mjs';
|
|
7
7
|
import {contentSecurityPolicy, fontFaces} from './window-page.mjs';
|
|
8
|
+
import {WELCOME_ENGLISH, WELCOME_TESTER_WORDS, WELCOME_WORDS} from './welcome-words.mjs';
|
|
9
|
+
|
|
10
|
+
// A word into the page's markup, where it is text: the words are this package's own, but a word is never markup.
|
|
11
|
+
function text(value) {
|
|
12
|
+
return String(value).replace(/[&<>"']/g, (character) => `&#${character.charCodeAt(0)};`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// A word into the page's script: JSON, with every "<" escaped, so that no word can end the script element.
|
|
16
|
+
function scriptValue(value) {
|
|
17
|
+
return JSON.stringify(value).replace(/</g, '\\u003c');
|
|
18
|
+
}
|
|
8
19
|
|
|
9
20
|
// `diagnostics`: whether this is a tester build (src/diagnostics.mjs), unless a test says. Then, and only then, Welcome says
|
|
10
|
-
// that the build sends diagnostic logs to KitCode's
|
|
11
|
-
//
|
|
12
|
-
//
|
|
21
|
+
// that the build sends diagnostic logs to KitCode's server, and what they never hold, and asks, optionally, for the email the
|
|
22
|
+
// tester was invited with, so that staff can find their logs by it (rows 78-80 of the words file, since 2026-10-22 in
|
|
23
|
+
// Vietnamese: src/welcome-words.mjs). Without it the page holds none of those words.
|
|
13
24
|
export function renderOnboardingWindow(platform = resolveSetupPlatform(), {diagnostics = diagnosticsEnabled()} = {}) {
|
|
14
25
|
const theme = setupPlatformTheme(platform);
|
|
26
|
+
// The words the page's script writes, and nothing else: a tester build's words are in its markup alone, so any other
|
|
27
|
+
// build's page holds none of them.
|
|
28
|
+
const scriptWords = {
|
|
29
|
+
messages: WELCOME_WORDS.messages,
|
|
30
|
+
buttons: WELCOME_WORDS.buttons,
|
|
31
|
+
projects: {empty: WELCOME_WORDS.projects.empty, removeTitle: WELCOME_WORDS.projects.removeTitle, removeName: WELCOME_WORDS.projects.removeName},
|
|
32
|
+
english: {pending: WELCOME_ENGLISH.pending, tracked: WELCOME_ENGLISH.tracked, added: WELCOME_ENGLISH.added},
|
|
33
|
+
};
|
|
15
34
|
// The page's one stylesheet and one script, kept apart so that its policy allows exactly them.
|
|
16
35
|
const style = `
|
|
17
|
-
${fontFaces(['
|
|
36
|
+
${fontFaces(['jetbrains-mono'])}
|
|
18
37
|
|
|
19
38
|
:root {
|
|
20
39
|
color-scheme: dark;
|
|
@@ -24,7 +43,7 @@ export function renderOnboardingWindow(platform = resolveSetupPlatform(), {diagn
|
|
|
24
43
|
--grey-text: #88888e;
|
|
25
44
|
--white-text: #e1e1e6;
|
|
26
45
|
--error: #ff9d9d;
|
|
27
|
-
--font-mono: '
|
|
46
|
+
--font-mono: 'JetBrains Mono', 'Courier New', Courier, ui-monospace, monospace;
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -227,9 +246,8 @@ export function renderOnboardingWindow(platform = resolveSetupPlatform(), {diagn
|
|
|
227
246
|
|
|
228
247
|
.option {
|
|
229
248
|
position: relative;
|
|
230
|
-
width: 72px;
|
|
231
249
|
height: 38px;
|
|
232
|
-
flex: 0 0
|
|
250
|
+
flex: 0 0 auto;
|
|
233
251
|
}
|
|
234
252
|
|
|
235
253
|
.option input {
|
|
@@ -243,14 +261,17 @@ export function renderOnboardingWindow(platform = resolveSetupPlatform(), {diagn
|
|
|
243
261
|
white-space: nowrap;
|
|
244
262
|
}
|
|
245
263
|
|
|
264
|
+
/* At least 72 px, as the English answers were, and as wide as its words with 12 px either side: "[ KHÔNG ]" filled a
|
|
265
|
+
72 px card, while "[ CÓ ]" keeps the 72 px it had, with about 13 px either side. */
|
|
246
266
|
.option-card {
|
|
247
267
|
display: inline-flex;
|
|
248
268
|
align-items: center;
|
|
249
269
|
justify-content: center;
|
|
250
|
-
width: 72px;
|
|
270
|
+
min-width: 72px;
|
|
251
271
|
height: 38px;
|
|
252
272
|
background: transparent;
|
|
253
|
-
padding: 0;
|
|
273
|
+
padding: 0 12px;
|
|
274
|
+
white-space: nowrap;
|
|
254
275
|
font-size: 11px;
|
|
255
276
|
font-weight: 700;
|
|
256
277
|
letter-spacing: 0.5px;
|
|
@@ -423,13 +444,14 @@ export function renderOnboardingWindow(platform = resolveSetupPlatform(), {diagn
|
|
|
423
444
|
margin: 12px 0;
|
|
424
445
|
}
|
|
425
446
|
|
|
447
|
+
/* The message line takes two lines when a message needs them, as the deck writes rows 66 and 76 (the plan's question
|
|
448
|
+
5c): never cut with an ellipsis. A message's own line break is the deck's. */
|
|
426
449
|
.message.comment-text {
|
|
427
450
|
min-width: 0;
|
|
428
451
|
color: var(--white-text);
|
|
429
452
|
font-size: 11px;
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
white-space: nowrap;
|
|
453
|
+
white-space: pre-line;
|
|
454
|
+
overflow-wrap: break-word;
|
|
433
455
|
}
|
|
434
456
|
|
|
435
457
|
.message[data-state="error"] { color: var(--error); }
|
|
@@ -498,9 +520,14 @@ ${diagnostics ? `
|
|
|
498
520
|
const setupContent = document.getElementById('setupContent');
|
|
499
521
|
const railItems = [...document.querySelectorAll('.rail-item')];
|
|
500
522
|
const stepSections = [...document.querySelectorAll('[data-setup-step]')];
|
|
501
|
-
const
|
|
523
|
+
const WORDS = ${scriptValue(scriptWords)};${diagnostics ? `
|
|
502
524
|
const invitedEmail = document.getElementById('invitedEmail');` : ''}
|
|
503
525
|
const FOLDER_ICON = '<svg class="dropzone-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>';
|
|
526
|
+
// A word with its count or name in place, by a function, so that a "$&" in a folder's name stays the name's own.
|
|
527
|
+
function fill(template, values) {
|
|
528
|
+
return template.replace(/\\{(N|name)\\}/g, (_match, key) => String(values[key]));
|
|
529
|
+
}
|
|
530
|
+
|
|
504
531
|
let persistedProjects = [];
|
|
505
532
|
let pendingProjects = [];
|
|
506
533
|
let saving = false;
|
|
@@ -572,7 +599,8 @@ ${diagnostics ? `
|
|
|
572
599
|
path.className = 'project-path';
|
|
573
600
|
path.textContent = project.repoRoot;
|
|
574
601
|
meta.className = 'project-meta';
|
|
575
|
-
meta.
|
|
602
|
+
meta.lang = 'en';
|
|
603
|
+
meta.textContent = (project.sourceType || 'vibe').toUpperCase() + ' · ' + (kind === 'pending' ? WORDS.english.pending : WORDS.english.tracked);
|
|
576
604
|
copy.append(name, path);
|
|
577
605
|
row.append(copy, meta);
|
|
578
606
|
|
|
@@ -580,20 +608,20 @@ ${diagnostics ? `
|
|
|
580
608
|
remove.type = 'button';
|
|
581
609
|
remove.dataset.testid = 'remove-project';
|
|
582
610
|
remove.textContent = 'x';
|
|
583
|
-
remove.title =
|
|
584
|
-
remove.setAttribute('aria-label',
|
|
611
|
+
remove.title = WORDS.projects.removeTitle;
|
|
612
|
+
remove.setAttribute('aria-label', fill(WORDS.projects.removeName, {name: projectName(project)}));
|
|
585
613
|
remove.addEventListener('click', async () => {
|
|
586
614
|
if (kind === 'pending') {
|
|
587
615
|
pendingProjects = pendingProjects.filter((candidate) => candidate.id !== project.id);
|
|
588
616
|
renderProjects();
|
|
589
|
-
setFeedback(
|
|
617
|
+
setFeedback(WORDS.messages.removed, 'normal', 'projects:removed');
|
|
590
618
|
return;
|
|
591
619
|
}
|
|
592
620
|
|
|
593
621
|
if (!bridge?.removeProject) {
|
|
594
622
|
persistedProjects = persistedProjects.filter((candidate) => candidate.id !== project.id);
|
|
595
623
|
renderProjects();
|
|
596
|
-
setFeedback(
|
|
624
|
+
setFeedback(WORDS.messages.removedFromSetup, 'normal', 'projects:removed');
|
|
597
625
|
return;
|
|
598
626
|
}
|
|
599
627
|
|
|
@@ -602,17 +630,17 @@ ${diagnostics ? `
|
|
|
602
630
|
const result = await bridge.removeProject(project.id);
|
|
603
631
|
if (!result.ok) {
|
|
604
632
|
remove.disabled = false;
|
|
605
|
-
setFeedback(result.error ||
|
|
633
|
+
setFeedback(result.error || WORDS.messages.removeFailed, 'error', 'error:remove-project');
|
|
606
634
|
return;
|
|
607
635
|
}
|
|
608
636
|
persistedProjects = Array.isArray(result.projects)
|
|
609
637
|
? result.projects
|
|
610
638
|
: persistedProjects.filter((candidate) => candidate.id !== project.id);
|
|
611
639
|
renderProjects();
|
|
612
|
-
setFeedback(
|
|
640
|
+
setFeedback(WORDS.messages.removed, 'normal', 'projects:removed');
|
|
613
641
|
} catch {
|
|
614
642
|
remove.disabled = false;
|
|
615
|
-
setFeedback(
|
|
643
|
+
setFeedback(WORDS.messages.removeFailedRetry, 'error', 'error:remove-project');
|
|
616
644
|
}
|
|
617
645
|
});
|
|
618
646
|
row.append(remove);
|
|
@@ -623,14 +651,17 @@ ${diagnostics ? `
|
|
|
623
651
|
function renderProjects() {
|
|
624
652
|
projectList.textContent = '';
|
|
625
653
|
const projects = persistedProjects.concat(pendingProjects);
|
|
626
|
-
projectCount.textContent = projects.length
|
|
654
|
+
projectCount.textContent = fill(WORDS.english.added, {N: projects.length});
|
|
627
655
|
projectList.classList.toggle('is-empty', !projects.length);
|
|
628
656
|
projectList.classList.toggle('tech-box-white', !projects.length);
|
|
629
657
|
|
|
630
658
|
if (!projects.length) {
|
|
631
659
|
const empty = document.createElement('div');
|
|
632
660
|
empty.className = 'empty-state';
|
|
633
|
-
empty.innerHTML = FOLDER_ICON
|
|
661
|
+
empty.innerHTML = FOLDER_ICON;
|
|
662
|
+
const label = document.createElement('span');
|
|
663
|
+
label.textContent = WORDS.projects.empty;
|
|
664
|
+
empty.append(label);
|
|
634
665
|
projectList.append(empty);
|
|
635
666
|
return;
|
|
636
667
|
}
|
|
@@ -692,11 +723,11 @@ ${diagnostics ? `
|
|
|
692
723
|
renderProjects();
|
|
693
724
|
|
|
694
725
|
pickFolders.addEventListener('click', async () => {
|
|
695
|
-
setFeedback(
|
|
726
|
+
setFeedback(WORDS.messages.picking, 'normal', 'projects:select');
|
|
696
727
|
try {
|
|
697
728
|
const result = await bridge.selectFolders();
|
|
698
729
|
if (result.canceled) {
|
|
699
|
-
setFeedback(
|
|
730
|
+
setFeedback(WORDS.messages.pickCanceled, 'normal', 'projects:unchanged');
|
|
700
731
|
return;
|
|
701
732
|
}
|
|
702
733
|
|
|
@@ -713,26 +744,26 @@ ${diagnostics ? `
|
|
|
713
744
|
renderProjects();
|
|
714
745
|
const added = (result.projects || []).length - duplicates;
|
|
715
746
|
setFeedback(
|
|
716
|
-
added ?
|
|
747
|
+
added ? fill(WORDS.messages.readyToAdd, {N: added}) : WORDS.messages.alreadyAdded,
|
|
717
748
|
'normal',
|
|
718
749
|
duplicates ? 'projects:deduplicated' : 'projects:pending'
|
|
719
750
|
);
|
|
720
751
|
} catch {
|
|
721
|
-
setFeedback(
|
|
752
|
+
setFeedback(WORDS.messages.pickerFailed, 'error', 'error:folder-picker');
|
|
722
753
|
}
|
|
723
754
|
});
|
|
724
755
|
|
|
725
756
|
saveButton.addEventListener('click', async () => {
|
|
726
757
|
if (saving) return;
|
|
727
758
|
if (!persistedProjects.length && !pendingProjects.length) {
|
|
728
|
-
setFeedback(
|
|
759
|
+
setFeedback(WORDS.messages.noProjects, 'error', 'error:no-projects');
|
|
729
760
|
pickFolders.focus();
|
|
730
761
|
return;
|
|
731
762
|
}
|
|
732
763
|
|
|
733
764
|
setBusy(true);
|
|
734
|
-
saveButton.textContent =
|
|
735
|
-
setFeedback(
|
|
765
|
+
saveButton.textContent = WORDS.buttons.saving;
|
|
766
|
+
setFeedback(WORDS.messages.saving, 'normal', 'setup:save');
|
|
736
767
|
const autoTrack = document.querySelector('input[name="autoTrack"]:checked').value === 'yes';
|
|
737
768
|
const companionView = 'mini';
|
|
738
769
|
|
|
@@ -753,17 +784,17 @@ ${diagnostics ? `
|
|
|
753
784
|
|
|
754
785
|
if (!result.ok) {
|
|
755
786
|
setBusy(false);
|
|
756
|
-
saveButton.textContent =
|
|
757
|
-
setFeedback(result.error ||
|
|
787
|
+
saveButton.textContent = WORDS.buttons.retry;
|
|
788
|
+
setFeedback(result.error || WORDS.messages.saveFailed, 'error', 'error:retry-available');
|
|
758
789
|
return;
|
|
759
790
|
}
|
|
760
791
|
|
|
761
|
-
saveButton.textContent =
|
|
762
|
-
setFeedback(
|
|
792
|
+
saveButton.textContent = WORDS.buttons.saved;
|
|
793
|
+
setFeedback(WORDS.messages.saved, 'success', 'setup:complete');
|
|
763
794
|
} catch {
|
|
764
795
|
setBusy(false);
|
|
765
|
-
saveButton.textContent =
|
|
766
|
-
setFeedback(
|
|
796
|
+
saveButton.textContent = WORDS.buttons.retry;
|
|
797
|
+
setFeedback(WORDS.messages.requestFailed, 'error', 'error:retry-available');
|
|
767
798
|
}
|
|
768
799
|
});
|
|
769
800
|
|
|
@@ -789,7 +820,7 @@ ${diagnostics ? `
|
|
|
789
820
|
|
|
790
821
|
if (!bridge) {
|
|
791
822
|
setBusy(true);
|
|
792
|
-
setFeedback(
|
|
823
|
+
setFeedback(WORDS.messages.noBridge, 'error', 'error:preload');
|
|
793
824
|
} else {
|
|
794
825
|
bridge.initialState().then((state) => {
|
|
795
826
|
persistedProjects = Array.isArray(state.projects) ? state.projects : [];
|
|
@@ -812,99 +843,99 @@ ${diagnostics ? `
|
|
|
812
843
|
|
|
813
844
|
if (suggestedCount) {
|
|
814
845
|
setFeedback(
|
|
815
|
-
|
|
846
|
+
fill(WORDS.messages.suggested, {N: suggestedCount}),
|
|
816
847
|
'normal',
|
|
817
848
|
'projects:chat-suggested'
|
|
818
849
|
);
|
|
819
850
|
} else {
|
|
820
851
|
setFeedback(
|
|
821
|
-
persistedProjects.length ?
|
|
852
|
+
persistedProjects.length ? fill(WORDS.messages.loaded, {N: persistedProjects.length}) : WORDS.messages.ready,
|
|
822
853
|
'normal',
|
|
823
854
|
persistedProjects.length ? 'projects:loaded' : 'setup:ready'
|
|
824
855
|
);
|
|
825
856
|
}
|
|
826
857
|
}).catch(() => {
|
|
827
858
|
renderProjects();
|
|
828
|
-
setFeedback(
|
|
859
|
+
setFeedback(WORDS.messages.stateFailed, 'error', 'error:initial-state');
|
|
829
860
|
});
|
|
830
861
|
}
|
|
831
862
|
`;
|
|
832
863
|
|
|
833
864
|
return `<!doctype html>
|
|
834
|
-
<html lang="
|
|
865
|
+
<html lang="vi">
|
|
835
866
|
<head>
|
|
836
867
|
<meta charset="utf-8">
|
|
837
868
|
<meta http-equiv="Content-Security-Policy" content="${contentSecurityPolicy({script, style})}">
|
|
838
869
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
839
|
-
<title
|
|
870
|
+
<title>${text(WELCOME_WORDS.title)}</title>
|
|
840
871
|
<style>${style}</style>
|
|
841
872
|
</head>
|
|
842
873
|
<body data-platform="${theme.id}" data-theme-marker="${theme.marker}">
|
|
843
874
|
<main class="shell window" data-testid="setup-shell">
|
|
844
875
|
${renderPlatformChrome(theme)}
|
|
845
876
|
<div class="container">
|
|
846
|
-
<div class="top-tag"
|
|
877
|
+
<div class="top-tag" lang="en">${text(WELCOME_ENGLISH.topTag)}</div>
|
|
847
878
|
<div class="dashed-divider section-divider" aria-hidden="true">==================================================================================================================================================================================================</div>
|
|
848
879
|
<div class="workspace layout steps-panel" id="setupContent" data-testid="setup-step-rail">
|
|
849
880
|
<aside class="sidebar">
|
|
850
881
|
<div class="rail-item is-active" data-step="tracking" data-testid="setup-rail-tracking">
|
|
851
882
|
<span class="rail-badge tech-box-red">01</span>
|
|
852
|
-
<span class="rail-label"
|
|
883
|
+
<span class="rail-label">${text(WELCOME_WORDS.rail.tracking.toUpperCase())}</span>
|
|
853
884
|
</div>
|
|
854
885
|
<div class="step-connector" aria-hidden="true"></div>
|
|
855
886
|
<div class="rail-item" data-step="projects" data-testid="setup-rail-projects">
|
|
856
887
|
<span class="rail-badge tech-box-red">02</span>
|
|
857
|
-
<span class="rail-label"
|
|
888
|
+
<span class="rail-label">${text(WELCOME_WORDS.rail.projects.toUpperCase())}</span>
|
|
858
889
|
</div>${diagnostics ? `
|
|
859
890
|
<div class="step-connector" aria-hidden="true"></div>
|
|
860
891
|
<div class="rail-item" data-step="diagnostics" data-testid="setup-rail-diagnostics">
|
|
861
892
|
<span class="rail-badge tech-box-red">03</span>
|
|
862
|
-
<span class="rail-label"
|
|
893
|
+
<span class="rail-label">${text(WELCOME_TESTER_WORDS.rail.toUpperCase())}</span>
|
|
863
894
|
</div>` : ''}
|
|
864
895
|
</aside>
|
|
865
896
|
<div class="content">
|
|
866
897
|
<fieldset class="step" id="trackingStep" data-setup-step="tracking">
|
|
867
|
-
<legend class="section-title"
|
|
868
|
-
<div class="option-group" id="autoTrackGroup" role="radiogroup" aria-label="
|
|
898
|
+
<legend class="section-title">${text(WELCOME_WORDS.tracking.question)}</legend>
|
|
899
|
+
<div class="option-group" id="autoTrackGroup" role="radiogroup" aria-label="${text(WELCOME_WORDS.tracking.groupName)}" data-next-focus="pickFolders">
|
|
869
900
|
<label class="option">
|
|
870
901
|
<input type="radio" name="autoTrack" value="yes" checked data-testid="auto-track-yes">
|
|
871
|
-
<span class="option-card btn btn-red tech-box-red option-mark">[
|
|
902
|
+
<span class="option-card btn btn-red tech-box-red option-mark">[ ${text(WELCOME_WORDS.tracking.yes)} ]</span>
|
|
872
903
|
</label>
|
|
873
904
|
<label class="option">
|
|
874
905
|
<input type="radio" name="autoTrack" value="no" data-testid="auto-track-no">
|
|
875
|
-
<span class="option-card btn btn-grey tech-box-grey option-mark">[
|
|
906
|
+
<span class="option-card btn btn-grey tech-box-grey option-mark">[ ${text(WELCOME_WORDS.tracking.no)} ]</span>
|
|
876
907
|
</label>
|
|
877
908
|
</div>
|
|
878
909
|
</fieldset>
|
|
879
910
|
<section class="step projects-step" aria-labelledby="projectsTitle" data-setup-step="projects">
|
|
880
911
|
<div class="step-head">
|
|
881
|
-
<h2 class="section-title" id="projectsTitle"
|
|
882
|
-
<span class="project-count" id="projectCount"
|
|
912
|
+
<h2 class="section-title" id="projectsTitle">${text(WELCOME_WORDS.projects.heading)}</h2>
|
|
913
|
+
<span class="project-count" id="projectCount" lang="en">${text(WELCOME_ENGLISH.added.replace('{N}', '0'))}</span>
|
|
883
914
|
</div>
|
|
884
|
-
<div class="project-list is-empty tech-box-white" id="projectList" role="list" aria-label="
|
|
915
|
+
<div class="project-list is-empty tech-box-white" id="projectList" role="list" aria-label="${text(WELCOME_WORDS.projects.listName)}" data-testid="project-list"></div>
|
|
885
916
|
<div class="project-actions">
|
|
886
|
-
<button class="terminal-button tech-box-red" id="pickFolders" type="button" data-testid="add-projects">+
|
|
887
|
-
<p class="hint"
|
|
917
|
+
<button class="terminal-button tech-box-red" id="pickFolders" type="button" data-testid="add-projects">+ ${text(WELCOME_WORDS.projects.add.toUpperCase())}</button>
|
|
918
|
+
<p class="hint">${WELCOME_WORDS.projects.hint.split('\n').map(text).join('<br>')}</p>
|
|
888
919
|
</div>
|
|
889
920
|
</section>${diagnostics ? `
|
|
890
921
|
<section class="step diagnostics-step" aria-labelledby="diagnosticsTitle" data-setup-step="diagnostics" data-testid="setup-diagnostics">
|
|
891
|
-
<h2 class="section-title" id="diagnosticsTitle"
|
|
892
|
-
|
|
893
|
-
<label for="invitedEmail"
|
|
922
|
+
<h2 class="section-title" id="diagnosticsTitle">${text(WELCOME_TESTER_WORDS.heading)}</h2>
|
|
923
|
+
${WELCOME_TESTER_WORDS.text.split('\n\n').map((paragraph) => `<p>${text(paragraph)}</p>`).join('\n ')}
|
|
924
|
+
<label for="invitedEmail">${text(WELCOME_TESTER_WORDS.emailLabel)}</label>
|
|
894
925
|
<input id="invitedEmail" name="invitedEmail" type="email" autocomplete="email" maxlength="254" spellcheck="false" data-testid="invited-email">
|
|
895
926
|
</section>` : ''}
|
|
896
927
|
</div>
|
|
897
928
|
</div>
|
|
898
929
|
<div class="dashed-divider section-divider" aria-hidden="true">==================================================================================================================================================================================================</div>
|
|
899
930
|
<footer class="content-footer footer-action">
|
|
900
|
-
<div class="message comment-text" id="message" aria-live="polite">/*
|
|
901
|
-
<button class="terminal-button primary-button tech-box-red" id="saveButton" type="button" data-testid="save-setup">+
|
|
931
|
+
<div class="message comment-text" id="message" aria-live="polite">/* ${text(WELCOME_WORDS.messages.ready)}</div>
|
|
932
|
+
<button class="terminal-button primary-button tech-box-red" id="saveButton" type="button" data-testid="save-setup">+ ${text(WELCOME_WORDS.buttons.save)} -></button>
|
|
902
933
|
</footer>
|
|
903
934
|
<div class="dashed-divider section-divider" aria-hidden="true">==================================================================================================================================================================================================</div>
|
|
904
935
|
<footer class="statusline status-bar">
|
|
905
|
-
<span class="normal-mode status-mode tech-box-red"
|
|
906
|
-
<span class="status-text" id="statusText" aria-live="polite">+++ setup: ready +++</span>
|
|
907
|
-
<span class="keyboard-hint status-shortcuts"
|
|
936
|
+
<span class="normal-mode status-mode tech-box-red" lang="en">${text(WELCOME_ENGLISH.mode)}</span>
|
|
937
|
+
<span class="status-text" id="statusText" aria-live="polite" lang="en">+++ setup: ready +++</span>
|
|
938
|
+
<span class="keyboard-hint status-shortcuts" lang="en">${text(WELCOME_ENGLISH.shortcuts)}</span>
|
|
908
939
|
</footer>
|
|
909
940
|
</div>
|
|
910
941
|
</main>
|