Complete Phase E theming, onboarding, and Windows share integration
Adds theme presets (Toffee/Slate/Evergreen/Lavender) with a "follow system" mode and high-contrast awareness, a first-run welcome screen, and the Windows analog of Android's share sheet for a Win32 app: an aerofetch:// protocol handler plus an Explorer "Send to AeroFetch" entry, both routed through a single-instance lock so a second launch hands its link to the already-running window instead of opening a duplicate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+60
-8
@@ -155,18 +155,70 @@ parallel storage. All items verified in the UI preview (typecheck + `npm run tes
|
||||
|
||||
Some items are Android-specific in Seal and adapted to Windows here.
|
||||
|
||||
- [ ] **Theme presets + "follow system"** and high-contrast (toffee light/dark already exists;
|
||||
add system-follow + a few accent presets). *Material-You dynamic color is Android-only;
|
||||
closest Windows analog is accent presets / system accent.*
|
||||
- [ ] **i18n / language setting** (Seal ships ~40 languages). Large but optional.
|
||||
- [ ] **Windows "open with" / share integration** — register a URL-protocol handler or Explorer
|
||||
"Send to" entry (analog of Android's share sheet). *Lower priority, platform-specific.*
|
||||
- [ ] **Onboarding / welcome screen** on first run.
|
||||
- [x] **Theme presets + "follow system" and high-contrast.** Settings → Appearance has a
|
||||
Theme select (Light / Dark / **Follow system**) and four accent presets — Toffee
|
||||
(original), Slate, Evergreen, Lavender — each a 16-stop `BrandVariants` ramp sharing
|
||||
toffee's lightness curve at a different hue (`src/renderer/src/theme.ts`). "Follow
|
||||
system" reads Electron's `nativeTheme.shouldUseDarkColors` in the main process
|
||||
(`src/main/index.ts`'s `resolveBackgroundMode`/`registerSystemThemeBridge`, pushed to
|
||||
the renderer over a new `system-theme:update` channel into
|
||||
`src/renderer/src/store/systemTheme.ts`) so the native window background stays in sync
|
||||
too, not just the in-page theme. High contrast: `nativeTheme.shouldUseHighContrastColors`
|
||||
surfaces as a status row in Settings → Appearance with a shortcut to Windows' contrast
|
||||
settings (`ms-settings:easeofaccess-highcontrast`); actual color overrides are left to
|
||||
Chromium's native `forced-colors` handling since nothing in the app sets
|
||||
`forced-color-adjust: none`. The sidebar's quick toggle still works under "system" — it
|
||||
sets an explicit light/dark away from whatever's currently resolved, breaking out of
|
||||
auto. Verified in the UI preview (typecheck + `npm run test` clean): default Toffee,
|
||||
switching to Slate/Evergreen/Lavender, Light/Dark/Follow-system, and the sidebar
|
||||
toggle's break-out-of-system behavior all checked visually.
|
||||
- [ ] **i18n / language setting** (Seal ships ~40 languages). Large but optional — deferred,
|
||||
since shipping ~40 real translations isn't something to fake; doing this properly means
|
||||
wiring up an i18n library + extracting every string now, with translation content
|
||||
arriving incrementally from contributors.
|
||||
- [x] **Windows "open with" / share integration.** Both mechanisms named above, since a Win32
|
||||
(non-MSIX) app can't register as an actual Share Target:
|
||||
- **`aerofetch://` protocol** (`aerofetch://download?url=<encoded>`) — registered at
|
||||
install time via `electron-builder.yml`'s `protocols` field (NSIS), and at runtime via
|
||||
`app.setAsDefaultProtocolClient` in `src/main/index.ts` (covers the portable build and
|
||||
dev, which have no installer step to do it for them).
|
||||
- **Explorer "Send to AeroFetch"** — `src/main/deeplink.ts`'s `registerSendToShortcut()`
|
||||
writes a `.lnk` into `%APPDATA%\Microsoft\Windows\SendTo` via Electron's
|
||||
`shell.writeShortcutLink` (no native deps). Sending a `.url` Internet Shortcut file
|
||||
(what Explorer/browsers create from "Create shortcut"/dragging a tab to the desktop)
|
||||
passes its path as argv; `extractIncomingUrl()` parses the `URL=` line.
|
||||
|
||||
Both paths funnel through the same `extractIncomingUrl()` (only ever forwards http(s)
|
||||
targets — same restriction as the existing external-link window-open handler) and a new
|
||||
`external-url` IPC push channel. The renderer surfaces the incoming link via
|
||||
`DownloadBar.tsx`'s existing clipboard-suggestion banner (now labeled "Link received:"
|
||||
for this source instead of "Use copied link?") rather than a new UI element. Added
|
||||
`app.requestSingleInstanceLock()` + a `second-instance` handler so a second invocation
|
||||
(the OS relaunching us for the protocol/SendTo case) hands its link to the already-running
|
||||
window instead of opening a duplicate. `extractIncomingUrl()`'s parsing/validation logic
|
||||
has 10 new unit tests (`test/deeplink.test.ts`); typecheck and the full suite are clean.
|
||||
*Caveat: the OS-level wiring (protocol registration, second-instance routing, the SendTo
|
||||
shortcut) is main-process/Electron-only and can't be exercised in the Vite browser
|
||||
preview used elsewhere in this phase — worth a real install + a manual
|
||||
`aerofetch://download?url=...` / "Send to" smoke test, same spirit as Phases A–C's
|
||||
real-download caveats.*
|
||||
- [x] **Onboarding / welcome screen** on first run. `Settings.hasCompletedOnboarding`
|
||||
(default `false`, persisted) gates a full-screen `Onboarding.tsx` rendered in place of
|
||||
the sidebar + main content in `App.tsx` — not a Fluent `Dialog`, since this app avoids
|
||||
Fluent's portal-based overlays entirely (GPU/driver blank-overlay issue noted in
|
||||
`src/main/index.ts` and `Select.tsx`). Shows the brand mark, a one-line pitch, the
|
||||
download-folder picker (same field as Settings → Downloads), three tip bullets, and a
|
||||
"Get started" button that flips the flag. Gated on the settings store's `loaded` flag
|
||||
so a returning user's real settings can never get clobbered by a one-frame flash of the
|
||||
default-false state. Verified in the UI preview (typecheck + `npm run test` clean):
|
||||
screen renders, folder field/browse work, "Get started" dismisses into the normal app.
|
||||
|
||||
---
|
||||
|
||||
## Not portable from Seal (noted for completeness)
|
||||
|
||||
- SAF directory picker (Android storage) → already covered by the native folder dialog.
|
||||
- Android share-sheet / quick-download-on-share → partially mapped to Phase E.
|
||||
- Android share-sheet / quick-download-on-share → mapped to Phase E's `aerofetch://` protocol
|
||||
+ Explorer "Send to" integration (a true Share Target needs an MSIX package, out of scope
|
||||
for this app's NSIS/portable distribution).
|
||||
- F-Droid distribution → N/A (AeroFetch ships NSIS + portable).
|
||||
|
||||
Reference in New Issue
Block a user