Add first-run setup and theme synchronization

Introduce first-run setup flow and live chrome theme syncing.

- Add first_run_state.cpp/.h to read/write a first_run_state.json under user data and decide whether to show the setup UI.
- Wire first-run logic into NebulaController: track first_run_setup_active_, create initial setup tab, defer/bring up chrome browser accordingly, and add CompleteFirstRunSetup() to persist state and finish setup.
- Add SendThemeToChromeSurfaces() and handle "theme-update" and "complete-first-run" chrome commands; restrict setup completion to setup frame.
- Expose GetFirstRunStatePath() and GetSetupUrl() in UI path helpers and include the state file in the build list (CMakeLists.txt).
- Update chrome UI: new CSS variables and styles for tabs/url-bar; chrome.js can apply themes (applyTheme), persist/load theme, and listen for storage updates to apply theme changes live.
- Update customization.js, settings.js, and setup.js to normalize/persist themes, send theme updates to the native host (or fallback), and communicate completion via the native bridge when available; include customization.js in setup.html.

These changes allow the app to run an interactive first-run setup and keep the separate chrome UI in sync with user-selected themes.
This commit is contained in:
Andrew Zambazos
2026-05-20 20:14:43 +12:00
parent bbba5b2927
commit 302753cd3d
14 changed files with 416 additions and 59 deletions
+41 -31
View File
@@ -6,10 +6,20 @@
--text: #e8e8f0;
--muted: #7a7e90;
--accent: #7b2eff;
--primary: #7b2eff;
--accent-2: #00c6ff;
--outline: #1f2533;
--outline-soft: rgba(255, 255, 255, 0.06);
--danger: #e0445c;
--url-bar-bg: #1c2030;
--url-bar-text: #e0e0e0;
--url-bar-border: #3e4652;
--tab-bg: #161925;
--tab-text: #a4a7b3;
--tab-active: #1c2030;
--tab-active-text: #e0e0e0;
--tab-border: #2b3040;
--chrome-hover: color-mix(in srgb, var(--text) 10%, transparent);
color-scheme: dark;
}
@@ -102,21 +112,21 @@ button:disabled {
border: 1px solid transparent;
border-bottom: none;
background: transparent;
color: var(--muted);
color: var(--tab-text);
cursor: pointer;
font-size: 0.82rem;
transition: background 120ms, color 120ms;
}
.tab:hover:not(.active) {
background: var(--surface);
color: var(--text);
background: var(--tab-bg);
color: var(--tab-active-text);
}
.tab.active {
background: var(--surface-raised);
border-color: var(--outline);
color: var(--text);
background: var(--tab-active);
border-color: var(--tab-border);
color: var(--tab-active-text);
}
.tab-title {
@@ -139,7 +149,7 @@ button:disabled {
display: flex;
align-items: center;
justify-content: center;
background: var(--accent);
background: var(--primary);
opacity: 0.85;
border-radius: 999px;
overflow: hidden;
@@ -166,8 +176,8 @@ button:disabled {
.tab-loading {
width: 13px;
height: 13px;
border: 2px solid rgba(0, 198, 255, 0.2);
border-top-color: var(--accent-2);
border: 2px solid color-mix(in srgb, var(--accent) 20%, transparent);
border-top-color: var(--accent);
border-radius: 999px;
animation: spin 0.8s linear infinite;
}
@@ -181,7 +191,7 @@ button:disabled {
padding: 0;
border-radius: 6px;
background: transparent;
color: var(--muted);
color: var(--tab-text);
opacity: 0;
transition: background 120ms, color 120ms, opacity 120ms;
}
@@ -193,8 +203,8 @@ button:disabled {
}
.tab-close:hover {
background: var(--surface-hover);
color: var(--text);
background: var(--chrome-hover);
color: var(--tab-active-text);
}
.tab-add {
@@ -207,14 +217,14 @@ button:disabled {
border-radius: 8px;
border: 1px solid transparent;
background: transparent;
color: var(--muted);
color: var(--tab-text);
transition: background 120ms, color 120ms, border-color 120ms;
}
.tab-add:hover {
background: var(--surface-hover);
border-color: var(--outline);
color: var(--text);
background: var(--chrome-hover);
border-color: var(--tab-border);
color: var(--tab-active-text);
}
/* ── Window controls ────────────────────────────────────────── */
@@ -233,13 +243,13 @@ button:disabled {
justify-content: center;
width: 46px;
background: transparent;
color: var(--muted);
color: var(--tab-text);
transition: background 100ms, color 100ms;
}
.window-controls button:hover {
background: var(--surface-hover);
color: var(--text);
background: var(--chrome-hover);
color: var(--tab-active-text);
}
.window-controls .close:hover {
@@ -252,8 +262,8 @@ button:disabled {
.toolbar {
gap: 4px;
padding: 0 12px;
background: var(--surface-raised);
border-top: 1px solid var(--outline);
background: var(--tab-active);
border-top: 1px solid var(--tab-border);
overflow: visible;
}
@@ -283,15 +293,15 @@ button:disabled {
border-radius: 9px;
background: transparent;
border: 1px solid transparent;
color: var(--muted);
color: var(--tab-text);
flex-shrink: 0;
transition: background 120ms, color 120ms, border-color 120ms;
}
.icon-button:hover:not(:disabled) {
background: var(--surface-hover);
border-color: var(--outline);
color: var(--text);
background: var(--chrome-hover);
border-color: var(--tab-border);
color: var(--tab-active-text);
}
/* ── Address bar ────────────────────────────────────────────── */
@@ -305,15 +315,15 @@ button:disabled {
flex: 1;
margin: 0 4px;
overflow: hidden;
border: 1px solid var(--outline);
border: 1px solid var(--url-bar-border);
border-radius: 10px;
background: var(--surface);
background: var(--url-bar-bg);
transition: border-color 140ms, box-shadow 140ms;
}
.address-shell:focus-within {
border-color: rgba(123, 46, 255, 0.55);
box-shadow: 0 0 0 3px rgba(123, 46, 255, 0.12);
border-color: color-mix(in srgb, var(--primary) 70%, var(--url-bar-border));
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent);
}
.address-shell input {
@@ -323,12 +333,12 @@ button:disabled {
outline: 0;
padding: 0 16px;
background: transparent;
color: var(--text);
color: var(--url-bar-text);
font-size: 0.84rem;
}
.address-shell input::placeholder {
color: var(--muted);
color: color-mix(in srgb, var(--url-bar-text) 55%, transparent);
}
.progress-bar {