Phase 0 MUSE alpha skeleton and SRS docs
Browse files- .gitignore +7 -0
- README.md +34 -0
- docs/MUSE_SRS_v3.md +205 -0
- docs/MUSE_SRS_v4.md +170 -0
- index.html +12 -0
- package.json +27 -0
- src-tauri/Cargo.toml +34 -0
- src-tauri/build.rs +3 -0
- src-tauri/capabilities/default.json +25 -0
- src-tauri/icons/README.md +1 -0
- src-tauri/migrations/001_phase0_init.sql +21 -0
- src-tauri/src/lib.rs +38 -0
- src-tauri/src/main.rs +5 -0
- src-tauri/src/settings.rs +4 -0
- src-tauri/src/state.rs +4 -0
- src-tauri/tauri.conf.json +52 -0
- src/App.tsx +72 -0
- src/components/HomePage.tsx +37 -0
- src/components/Onboarding.tsx +38 -0
- src/components/PlaceholderPanel.tsx +9 -0
- src/components/Sidebar.tsx +34 -0
- src/components/ThemeSwitcher.tsx +30 -0
- src/components/Titlebar.tsx +24 -0
- src/index.tsx +7 -0
- src/store/settings.ts +45 -0
- src/styles/app.css +66 -0
- src/styles/tokens.css +18 -0
- src/types.ts +17 -0
- tsconfig.json +22 -0
- vite.config.ts +18 -0
.gitignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules/
|
| 2 |
+
dist/
|
| 3 |
+
src-tauri/target/
|
| 4 |
+
src-tauri/gen/
|
| 5 |
+
.DS_Store
|
| 6 |
+
*.log
|
| 7 |
+
.env
|
README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MUSE Alpha
|
| 2 |
+
|
| 3 |
+
Creative browser prototype: Tauri v2 + Rust + SolidJS.
|
| 4 |
+
|
| 5 |
+
This repo currently contains:
|
| 6 |
+
- `docs/MUSE_SRS_v3.md` — Master Software Requirements Specification v3.0
|
| 7 |
+
- `docs/MUSE_SRS_v4.md` — Master SRS v4.0 implementation guide
|
| 8 |
+
- Phase 0 implementation skeleton: custom titlebar, sidebar, onboarding, 8-theme system, home page, settings store, and SQLite migration setup.
|
| 9 |
+
|
| 10 |
+
## Phase 0 status
|
| 11 |
+
|
| 12 |
+
Implemented from current Tauri v2/SolidJS docs:
|
| 13 |
+
- Tauri v2 app skeleton (`src-tauri/`)
|
| 14 |
+
- SolidJS frontend (`src/`)
|
| 15 |
+
- Custom undecorated window titlebar
|
| 16 |
+
- Sidebar navigation shell
|
| 17 |
+
- Home page with greeting and static URL/search bar
|
| 18 |
+
- Theme token system with 8 presets
|
| 19 |
+
- Onboarding flow using Tauri Store
|
| 20 |
+
- SQLite plugin with Phase 0 migration
|
| 21 |
+
- Capabilities configured for window controls, store, SQL, FS, clipboard, stronghold/global shortcut preparation
|
| 22 |
+
|
| 23 |
+
## Dev
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
pnpm install
|
| 27 |
+
pnpm tauri dev
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
Frontend-only build:
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
pnpm build
|
| 34 |
+
```
|
docs/MUSE_SRS_v3.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MUSE — Master Software Requirements Specification v3.0
|
| 2 |
+
### Creative Browser · Tauri v2 + Rust · Complete Product Definition
|
| 3 |
+
|
| 4 |
+
> Canonical v3 requirements document supplied by the product owner in the implementation prompt.
|
| 5 |
+
|
| 6 |
+
This document defines the full Muse product vision, architecture, browser fundamentals, ad-block v3, creative modules, artist-exclusive features, UI/UX system, business model, roadmap, and non-goals.
|
| 7 |
+
|
| 8 |
+
## Document map
|
| 9 |
+
|
| 10 |
+
Incorporates and supersedes:
|
| 11 |
+
- SRS v1.0 (original vision + market research)
|
| 12 |
+
- SRS v2.0 (deep UX/UI + themes + ad-block)
|
| 13 |
+
- 01-DATABASE-ARCHITECTURE.md
|
| 14 |
+
- 02-ADBLOCK-INTEGRATION.md
|
| 15 |
+
- 03-RUST-ARCHITECTURE.md
|
| 16 |
+
- 04-USER-RESEARCH.md
|
| 17 |
+
- 05-PLATFORM-SPECIFIC.md
|
| 18 |
+
- 06-MOBILE-TABLET-UX.md
|
| 19 |
+
- 07-ALGORITHMS-THIRDPARTY.md
|
| 20 |
+
- 08-UI-UX-COMPLETE.md
|
| 21 |
+
|
| 22 |
+
## Core mission
|
| 23 |
+
|
| 24 |
+
Muse is the first browser designed specifically for the visual artist's creative loop:
|
| 25 |
+
|
| 26 |
+
```text
|
| 27 |
+
Discover → Collect → Organize → Reference → Create → Repeat
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
Muse replaces the fragmented five-app workflow:
|
| 31 |
+
- Chrome/Firefox: full browser
|
| 32 |
+
- PureRef: Board module / infinite reference canvas
|
| 33 |
+
- Eagle App: Library module
|
| 34 |
+
- Pinterest/Are.na: Boards + Library without algorithm/account/subscription
|
| 35 |
+
- Standalone color picker: integrated extraction, picker, search, export
|
| 36 |
+
|
| 37 |
+
## Locked non-goals
|
| 38 |
+
|
| 39 |
+
- No AI image generation
|
| 40 |
+
- No subscription model
|
| 41 |
+
- No hosted cloud storage
|
| 42 |
+
- No social feed or algorithm
|
| 43 |
+
- No drawing tools
|
| 44 |
+
- No real-time collaboration for v1 scope
|
| 45 |
+
- No telemetry without explicit consent
|
| 46 |
+
- No bundled Chromium
|
| 47 |
+
- No mandatory account
|
| 48 |
+
|
| 49 |
+
## Architecture summary
|
| 50 |
+
|
| 51 |
+
Stack:
|
| 52 |
+
- Tauri v2 + Rust core
|
| 53 |
+
- SolidJS shell frontend
|
| 54 |
+
- System WebViews for browser tabs
|
| 55 |
+
- SQLite + WAL for local data
|
| 56 |
+
- Stronghold for encrypted credentials
|
| 57 |
+
- `tauri-plugin-store` for settings
|
| 58 |
+
- `brave/adblock-rust` for filter-rule engine
|
| 59 |
+
|
| 60 |
+
Rust module plan:
|
| 61 |
+
- `browser/` tab lifecycle, navigation, injection, tab sleep, find
|
| 62 |
+
- `adblock/` engine, filter lists, user rules, subscriptions, stats, cosmetic injection
|
| 63 |
+
- `library/` SQLite CRUD, thumbnails, color extraction, dedupe, FTS5 search, smart folders
|
| 64 |
+
- `board/` infinite canvas, autosave, items, export, annotations
|
| 65 |
+
- `credentials/` vault, autofill, generator
|
| 66 |
+
- `downloads/`, `cookies/`, `sessions/`, `permissions/`, `privacy/`, `color/`, `workspace/`
|
| 67 |
+
|
| 68 |
+
## Database summary
|
| 69 |
+
|
| 70 |
+
SQLite with startup PRAGMAs:
|
| 71 |
+
|
| 72 |
+
```sql
|
| 73 |
+
PRAGMA journal_mode = WAL;
|
| 74 |
+
PRAGMA synchronous = NORMAL;
|
| 75 |
+
PRAGMA mmap_size = 268435456;
|
| 76 |
+
PRAGMA cache_size = -16384;
|
| 77 |
+
PRAGMA temp_store = MEMORY;
|
| 78 |
+
PRAGMA foreign_keys = ON;
|
| 79 |
+
PRAGMA busy_timeout = 5000;
|
| 80 |
+
PRAGMA auto_vacuum = INCREMENTAL;
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
New v3 tables include:
|
| 84 |
+
- `sessions`, `session_tabs`
|
| 85 |
+
- `credentials` pointers to Stronghold keys
|
| 86 |
+
- `downloads`
|
| 87 |
+
- `cookie_rules`
|
| 88 |
+
- `site_permissions`
|
| 89 |
+
- `site_zoom`
|
| 90 |
+
- `adblock_subscriptions`
|
| 91 |
+
- `adblock_user_rules`
|
| 92 |
+
- `board_annotations`
|
| 93 |
+
- `study_sessions`
|
| 94 |
+
|
| 95 |
+
## Browser fundamentals covered
|
| 96 |
+
|
| 97 |
+
- Cookie management
|
| 98 |
+
- Password and credential manager
|
| 99 |
+
- Download manager
|
| 100 |
+
- Tab sleep
|
| 101 |
+
- Session management
|
| 102 |
+
- Find in page
|
| 103 |
+
- Per-site zoom memory
|
| 104 |
+
- Permission system
|
| 105 |
+
- HTTPS-first and safe browsing without Google URL API
|
| 106 |
+
- DNS over HTTPS
|
| 107 |
+
- Print
|
| 108 |
+
- Autofill
|
| 109 |
+
- WebRTC and fingerprint protection
|
| 110 |
+
- Offline/error pages
|
| 111 |
+
- Media autoplay policy
|
| 112 |
+
- Certificate/security info
|
| 113 |
+
|
| 114 |
+
## Ad-block v3
|
| 115 |
+
|
| 116 |
+
Hybrid design in v3:
|
| 117 |
+
- Navigation blocking
|
| 118 |
+
- Native network blocking where available
|
| 119 |
+
- Cosmetic filtering
|
| 120 |
+
- Built-in multi-list subscriptions
|
| 121 |
+
- Custom uBlock-syntax rules
|
| 122 |
+
- Statistics and transparency
|
| 123 |
+
- Cookie consent auto-denial
|
| 124 |
+
|
| 125 |
+
Built-in lists:
|
| 126 |
+
- EasyList
|
| 127 |
+
- EasyPrivacy
|
| 128 |
+
- uBlock filters
|
| 129 |
+
- uBlock Unbreak
|
| 130 |
+
- Fanboy Annoyances
|
| 131 |
+
- Peter Lowe
|
| 132 |
+
- Malware Domains
|
| 133 |
+
- AdGuard Base
|
| 134 |
+
- AdGuard Social
|
| 135 |
+
- IndianList
|
| 136 |
+
|
| 137 |
+
## Core creative modules
|
| 138 |
+
|
| 139 |
+
- Home Page
|
| 140 |
+
- URL Bar System
|
| 141 |
+
- Sidebar & Tab System
|
| 142 |
+
- Library Module
|
| 143 |
+
- Board Module / PureRef replacement
|
| 144 |
+
- Split View System
|
| 145 |
+
- Color Tools
|
| 146 |
+
- Image Hover Overlay
|
| 147 |
+
|
| 148 |
+
## Artist-exclusive v3 modules
|
| 149 |
+
|
| 150 |
+
- Quick Study Mode
|
| 151 |
+
- Board annotations/markup
|
| 152 |
+
- Rotation, flip, light table, overlays
|
| 153 |
+
- Proportion and grid overlays
|
| 154 |
+
- Source site intelligence profiles
|
| 155 |
+
- Library timeline view
|
| 156 |
+
- Color export for artist apps
|
| 157 |
+
- Reference sheet export
|
| 158 |
+
- Workspace sessions for projects
|
| 159 |
+
|
| 160 |
+
## UI/UX system
|
| 161 |
+
|
| 162 |
+
Theme presets:
|
| 163 |
+
- Dusk
|
| 164 |
+
- Parchment
|
| 165 |
+
- Midnight
|
| 166 |
+
- Studio
|
| 167 |
+
- Moss
|
| 168 |
+
- Rose
|
| 169 |
+
- Obsidian
|
| 170 |
+
- Linen
|
| 171 |
+
|
| 172 |
+
Typography:
|
| 173 |
+
- Geist UI
|
| 174 |
+
- Lora reader body
|
| 175 |
+
- Geist Mono for code/dimensions
|
| 176 |
+
|
| 177 |
+
Onboarding:
|
| 178 |
+
1. Welcome
|
| 179 |
+
2. Make it yours (name + theme)
|
| 180 |
+
3. Name first workspace
|
| 181 |
+
|
| 182 |
+
Keyboard/command palette includes standard browser shortcuts plus Muse creative shortcuts.
|
| 183 |
+
|
| 184 |
+
## Business model
|
| 185 |
+
|
| 186 |
+
- Free tier forever
|
| 187 |
+
- Muse one-time purchase
|
| 188 |
+
- Muse Pro one-time upgrade
|
| 189 |
+
- Pay-What-You-Want launch window
|
| 190 |
+
- No subscription
|
| 191 |
+
|
| 192 |
+
## Roadmap
|
| 193 |
+
|
| 194 |
+
- Phase 0: Foundation
|
| 195 |
+
- Phase 1: Browser basics
|
| 196 |
+
- Phase 2: Browser fundamentals complete
|
| 197 |
+
- Phase 3: Creative core
|
| 198 |
+
- Phase 4: Power features
|
| 199 |
+
- Phase 5: Ship Windows
|
| 200 |
+
- Phase 6: Cross-platform
|
| 201 |
+
- Phase 7: Ecosystem
|
| 202 |
+
|
| 203 |
+
## Note
|
| 204 |
+
|
| 205 |
+
The original full v3 text was supplied in-chat and is treated as the requirements source for this repository. Phase 0 implementation begins from the v4 implementation guide while preserving these v3 product requirements.
|
docs/MUSE_SRS_v4.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MUSE — Master SRS v4.0
|
| 2 |
+
## Creative Browser · Tauri v2 + Rust · Complete Implementation Guide
|
| 3 |
+
|
| 4 |
+
> One window. Multiple child webviews. One ad-block engine. All platforms.
|
| 5 |
+
|
| 6 |
+
This implementation guide supersedes earlier architecture/code notes for v1.0 implementation sequencing.
|
| 7 |
+
|
| 8 |
+
## Critical architecture decisions
|
| 9 |
+
|
| 10 |
+
### 1. Single OS window + child webviews for tabs
|
| 11 |
+
|
| 12 |
+
Muse is one OS window containing:
|
| 13 |
+
- Shell WebView: SolidJS UI (titlebar, sidebar, URL bar, home, library, boards)
|
| 14 |
+
- Child tab WebViews: one per browser tab
|
| 15 |
+
|
| 16 |
+
Implemented later with `window.add_child()` behind Tauri's `unstable` feature. Phase 0 does **not** use child WebViews yet.
|
| 17 |
+
|
| 18 |
+
### 2. Cross-platform ad-block = Rust engine + JS injection
|
| 19 |
+
|
| 20 |
+
The v4 guide deliberately removes platform-native WebView2/WKContentRuleList/WebKitGTK-specific blocking from the v1 cross-platform plan.
|
| 21 |
+
|
| 22 |
+
Ad-block layers:
|
| 23 |
+
- Layer 0: Navigation handler in Rust
|
| 24 |
+
- Layer 1: JS fetch/XHR override
|
| 25 |
+
- Layer 2: DOM CSP injection
|
| 26 |
+
- Layer 3: Cosmetic CSS injection
|
| 27 |
+
- Layer 4: Scriptlet injection
|
| 28 |
+
|
| 29 |
+
### 3. `unstable` feature is acceptable for desktop tabs
|
| 30 |
+
|
| 31 |
+
`tauri = { version = "2", features = ["unstable"] }` is planned once Phase 1 implements child tab WebViews.
|
| 32 |
+
|
| 33 |
+
### 4. Mobile tab model differs
|
| 34 |
+
|
| 35 |
+
Mobile uses conceptual tab state plus `WebviewWindow` until Tauri supports child webviews on mobile. Desktop Windows remains v1 target.
|
| 36 |
+
|
| 37 |
+
## Repository structure target
|
| 38 |
+
|
| 39 |
+
```text
|
| 40 |
+
muse/
|
| 41 |
+
├── package.json
|
| 42 |
+
├── vite.config.ts
|
| 43 |
+
├── index.html
|
| 44 |
+
├── src/
|
| 45 |
+
│ ├── index.tsx
|
| 46 |
+
│ ├── App.tsx
|
| 47 |
+
│ ├── components/
|
| 48 |
+
│ ├── store/
|
| 49 |
+
│ └── styles/
|
| 50 |
+
└── src-tauri/
|
| 51 |
+
├── Cargo.toml
|
| 52 |
+
├── build.rs
|
| 53 |
+
├── tauri.conf.json
|
| 54 |
+
├── capabilities/default.json
|
| 55 |
+
├── migrations/
|
| 56 |
+
└── src/
|
| 57 |
+
├── main.rs
|
| 58 |
+
├── lib.rs
|
| 59 |
+
├── state.rs
|
| 60 |
+
├── settings.rs
|
| 61 |
+
├── browser/
|
| 62 |
+
├── adblock/
|
| 63 |
+
├── library/
|
| 64 |
+
├── board/
|
| 65 |
+
├── credentials/
|
| 66 |
+
├── downloads/
|
| 67 |
+
├── sessions/
|
| 68 |
+
└── db/
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
## Phase 0 implementation scope
|
| 72 |
+
|
| 73 |
+
Deliverable: running app with custom titlebar, theme, and sidebar navigation.
|
| 74 |
+
|
| 75 |
+
User can:
|
| 76 |
+
- Open Muse on Windows
|
| 77 |
+
- See Dusk theme, custom titlebar, sidebar
|
| 78 |
+
- Click sidebar icons (Library/Board placeholders)
|
| 79 |
+
- See Home page with greeting and static URL bar
|
| 80 |
+
- Switch all 8 themes
|
| 81 |
+
- Complete onboarding in under 60 seconds
|
| 82 |
+
|
| 83 |
+
Not included yet:
|
| 84 |
+
- Real browser tabs
|
| 85 |
+
- Ad-block
|
| 86 |
+
- Library/Board functionality
|
| 87 |
+
|
| 88 |
+
Phase 0 work items:
|
| 89 |
+
- SolidJS Tauri app skeleton
|
| 90 |
+
- Tauri v2 `Cargo.toml`
|
| 91 |
+
- `tauri.conf.json` with undecorated window
|
| 92 |
+
- Capabilities file
|
| 93 |
+
- Custom titlebar with window controls
|
| 94 |
+
- Sidebar shell
|
| 95 |
+
- Home page shell
|
| 96 |
+
- Theme token CSS for 8 themes
|
| 97 |
+
- App state module
|
| 98 |
+
- SQLite init and migration
|
| 99 |
+
- Tauri Store settings
|
| 100 |
+
- Onboarding flow
|
| 101 |
+
|
| 102 |
+
## Phase 1 target
|
| 103 |
+
|
| 104 |
+
Real multi-tab browser:
|
| 105 |
+
- `TabManager`
|
| 106 |
+
- `window.add_child()` child WebViews
|
| 107 |
+
- tab create/close/switch/navigate
|
| 108 |
+
- sidebar tab list
|
| 109 |
+
- URL bar navigation
|
| 110 |
+
- title/favicon observer
|
| 111 |
+
- resize handling
|
| 112 |
+
- per-site zoom
|
| 113 |
+
|
| 114 |
+
## Phase 2 target
|
| 115 |
+
|
| 116 |
+
Ad-block and privacy:
|
| 117 |
+
- adblock-rust engine
|
| 118 |
+
- 10 bundled lists
|
| 119 |
+
- JS network-request interception
|
| 120 |
+
- cookie consent scriptlet
|
| 121 |
+
- WebRTC protection
|
| 122 |
+
- canvas noise
|
| 123 |
+
- navigation blocking
|
| 124 |
+
- cosmetic injection
|
| 125 |
+
- updater
|
| 126 |
+
- allowlist
|
| 127 |
+
- shield UI
|
| 128 |
+
- tab sleep
|
| 129 |
+
- HTTPS-first
|
| 130 |
+
- Stronghold password vault basics
|
| 131 |
+
|
| 132 |
+
## Phase 3 target
|
| 133 |
+
|
| 134 |
+
Library + Board core:
|
| 135 |
+
- image hover overlay
|
| 136 |
+
- save-to-library
|
| 137 |
+
- library search/grid/detail
|
| 138 |
+
- board infinite canvas
|
| 139 |
+
- board item CRUD/autosave
|
| 140 |
+
- palette extraction
|
| 141 |
+
- download manager
|
| 142 |
+
- sessions auto-save
|
| 143 |
+
|
| 144 |
+
## Phase 4 target
|
| 145 |
+
|
| 146 |
+
Artist features and polish:
|
| 147 |
+
- Quick Study Mode
|
| 148 |
+
- annotations
|
| 149 |
+
- rotate/flip/light table
|
| 150 |
+
- overlays
|
| 151 |
+
- timeline
|
| 152 |
+
- source profiles
|
| 153 |
+
- export formats
|
| 154 |
+
- reference sheets
|
| 155 |
+
- session manager UI
|
| 156 |
+
- command palette
|
| 157 |
+
- permissions/cert/offline/print/DoH
|
| 158 |
+
|
| 159 |
+
## Phase 5 target
|
| 160 |
+
|
| 161 |
+
Windows v1 shipping:
|
| 162 |
+
- NSIS installer
|
| 163 |
+
- updater
|
| 164 |
+
- code signing
|
| 165 |
+
- optional crash reporting
|
| 166 |
+
- performance audit
|
| 167 |
+
|
| 168 |
+
## Current implementation note
|
| 169 |
+
|
| 170 |
+
The committed code implements the Phase 0 scope only. Phase 1+ modules are represented as directory placeholders and comments where useful, but no tab WebViews are created in Phase 0.
|
index.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>MUSE Alpha</title>
|
| 7 |
+
</head>
|
| 8 |
+
<body>
|
| 9 |
+
<div id="root"></div>
|
| 10 |
+
<script type="module" src="/src/index.tsx"></script>
|
| 11 |
+
</body>
|
| 12 |
+
</html>
|
package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "musealpha",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.1.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "tsc --noEmit && vite build",
|
| 9 |
+
"preview": "vite preview",
|
| 10 |
+
"tauri": "tauri"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"@tauri-apps/api": "^2.0.0",
|
| 14 |
+
"@tauri-apps/plugin-store": "^2.0.0",
|
| 15 |
+
"@tauri-apps/plugin-sql": "^2.0.0",
|
| 16 |
+
"@tauri-apps/plugin-opener": "^2.0.0",
|
| 17 |
+
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
|
| 18 |
+
"@tauri-apps/plugin-fs": "^2.0.0",
|
| 19 |
+
"solid-js": "^1.9.0"
|
| 20 |
+
},
|
| 21 |
+
"devDependencies": {
|
| 22 |
+
"@tauri-apps/cli": "^2.0.0",
|
| 23 |
+
"typescript": "~5.6.0",
|
| 24 |
+
"vite": "^6.0.0",
|
| 25 |
+
"vite-plugin-solid": "^2.11.0"
|
| 26 |
+
}
|
| 27 |
+
}
|
src-tauri/Cargo.toml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[package]
|
| 2 |
+
name = "muse"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "MUSE Alpha"
|
| 5 |
+
authors = ["asdf98"]
|
| 6 |
+
edition = "2021"
|
| 7 |
+
|
| 8 |
+
[lib]
|
| 9 |
+
name = "muse_lib"
|
| 10 |
+
crate-type = ["staticlib", "cdylib", "rlib"]
|
| 11 |
+
|
| 12 |
+
[build-dependencies]
|
| 13 |
+
tauri-build = { version = "2", features = [] }
|
| 14 |
+
|
| 15 |
+
[dependencies]
|
| 16 |
+
tauri = { version = "2", features = [] }
|
| 17 |
+
tauri-plugin-opener = "2"
|
| 18 |
+
tauri-plugin-store = "2"
|
| 19 |
+
tauri-plugin-fs = "2"
|
| 20 |
+
tauri-plugin-sql = { version = "2", features = ["sqlite"] }
|
| 21 |
+
tauri-plugin-clipboard-manager = "2"
|
| 22 |
+
tauri-plugin-stronghold = "2"
|
| 23 |
+
serde = { version = "1", features = ["derive"] }
|
| 24 |
+
serde_json = "1"
|
| 25 |
+
|
| 26 |
+
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
| 27 |
+
tauri-plugin-global-shortcut = "2"
|
| 28 |
+
|
| 29 |
+
[profile.dev.package.scrypt]
|
| 30 |
+
opt-level = 3
|
| 31 |
+
|
| 32 |
+
[features]
|
| 33 |
+
default = ["custom-protocol"]
|
| 34 |
+
custom-protocol = ["tauri/custom-protocol"]
|
src-tauri/build.rs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fn main() {
|
| 2 |
+
tauri_build::build()
|
| 3 |
+
}
|
src-tauri/capabilities/default.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "../gen/schemas/desktop-schema.json",
|
| 3 |
+
"identifier": "default",
|
| 4 |
+
"description": "MUSE Alpha Phase 0 default capability set",
|
| 5 |
+
"windows": ["main"],
|
| 6 |
+
"permissions": [
|
| 7 |
+
"core:default",
|
| 8 |
+
"opener:default",
|
| 9 |
+
"core:window:allow-start-dragging",
|
| 10 |
+
"core:window:allow-minimize",
|
| 11 |
+
"core:window:allow-toggle-maximize",
|
| 12 |
+
"core:window:allow-close",
|
| 13 |
+
"store:default",
|
| 14 |
+
"sql:default",
|
| 15 |
+
"sql:allow-execute",
|
| 16 |
+
"fs:default",
|
| 17 |
+
"clipboard-manager:allow-read-text",
|
| 18 |
+
"clipboard-manager:allow-write-text",
|
| 19 |
+
"global-shortcut:allow-register",
|
| 20 |
+
"global-shortcut:allow-unregister",
|
| 21 |
+
"global-shortcut:allow-unregister-all",
|
| 22 |
+
"global-shortcut:allow-is-registered",
|
| 23 |
+
"stronghold:default"
|
| 24 |
+
]
|
| 25 |
+
}
|
src-tauri/icons/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Add generated Tauri icon assets here before packaging (`pnpm tauri icon`).
|
src-tauri/migrations/001_phase0_init.sql
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CREATE TABLE IF NOT EXISTS app_meta (
|
| 2 |
+
key TEXT PRIMARY KEY NOT NULL,
|
| 3 |
+
value TEXT NOT NULL,
|
| 4 |
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 5 |
+
);
|
| 6 |
+
|
| 7 |
+
CREATE TABLE IF NOT EXISTS settings (
|
| 8 |
+
key TEXT PRIMARY KEY NOT NULL,
|
| 9 |
+
value_json TEXT NOT NULL,
|
| 10 |
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 11 |
+
);
|
| 12 |
+
|
| 13 |
+
CREATE TABLE IF NOT EXISTS workspaces (
|
| 14 |
+
id TEXT PRIMARY KEY NOT NULL,
|
| 15 |
+
name TEXT NOT NULL,
|
| 16 |
+
accent TEXT NOT NULL DEFAULT '#C49A3C',
|
| 17 |
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 18 |
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 19 |
+
);
|
| 20 |
+
|
| 21 |
+
INSERT OR IGNORE INTO app_meta (key, value) VALUES ('schema_version', '1');
|
src-tauri/src/lib.rs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
mod settings;
|
| 2 |
+
mod state;
|
| 3 |
+
|
| 4 |
+
use tauri_plugin_sql::{Migration, MigrationKind};
|
| 5 |
+
|
| 6 |
+
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
| 7 |
+
pub fn run() {
|
| 8 |
+
tauri::Builder::default()
|
| 9 |
+
.plugin(tauri_plugin_opener::init())
|
| 10 |
+
.plugin(tauri_plugin_store::Builder::default().build())
|
| 11 |
+
.plugin(tauri_plugin_fs::init())
|
| 12 |
+
.plugin(tauri_plugin_clipboard_manager::init())
|
| 13 |
+
.plugin(
|
| 14 |
+
tauri_plugin_sql::Builder::default()
|
| 15 |
+
.add_migrations("sqlite:muse.db", migrations())
|
| 16 |
+
.build(),
|
| 17 |
+
)
|
| 18 |
+
.manage(state::AppState::default())
|
| 19 |
+
.invoke_handler(tauri::generate_handler![
|
| 20 |
+
settings::phase0_status,
|
| 21 |
+
])
|
| 22 |
+
.setup(|app| {
|
| 23 |
+
#[cfg(desktop)]
|
| 24 |
+
app.handle().plugin(tauri_plugin_global_shortcut::Builder::new().build())?;
|
| 25 |
+
Ok(())
|
| 26 |
+
})
|
| 27 |
+
.run(tauri::generate_context!())
|
| 28 |
+
.expect("error while running Muse Alpha");
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
fn migrations() -> Vec<Migration> {
|
| 32 |
+
vec![Migration {
|
| 33 |
+
version: 1,
|
| 34 |
+
description: "phase0_init",
|
| 35 |
+
sql: include_str!("../migrations/001_phase0_init.sql"),
|
| 36 |
+
kind: MigrationKind::Up,
|
| 37 |
+
}]
|
| 38 |
+
}
|
src-tauri/src/main.rs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
| 2 |
+
|
| 3 |
+
fn main() {
|
| 4 |
+
muse_lib::run()
|
| 5 |
+
}
|
src-tauri/src/settings.rs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#[tauri::command]
|
| 2 |
+
pub fn phase0_status() -> &'static str {
|
| 3 |
+
"MUSE Alpha Phase 0 shell is installed"
|
| 4 |
+
}
|
src-tauri/src/state.rs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#[derive(Default)]
|
| 2 |
+
pub struct AppState {
|
| 3 |
+
pub phase: &'static str,
|
| 4 |
+
}
|
src-tauri/tauri.conf.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://schema.tauri.app/config/2",
|
| 3 |
+
"productName": "MUSE Alpha",
|
| 4 |
+
"version": "0.1.0",
|
| 5 |
+
"identifier": "app.muse.alpha",
|
| 6 |
+
"build": {
|
| 7 |
+
"beforeDevCommand": "pnpm dev",
|
| 8 |
+
"devUrl": "http://localhost:1420",
|
| 9 |
+
"beforeBuildCommand": "pnpm build",
|
| 10 |
+
"frontendDist": "../dist"
|
| 11 |
+
},
|
| 12 |
+
"app": {
|
| 13 |
+
"withGlobalTauri": false,
|
| 14 |
+
"windows": [
|
| 15 |
+
{
|
| 16 |
+
"label": "main",
|
| 17 |
+
"title": "MUSE Alpha",
|
| 18 |
+
"width": 1280,
|
| 19 |
+
"height": 800,
|
| 20 |
+
"minWidth": 960,
|
| 21 |
+
"minHeight": 640,
|
| 22 |
+
"resizable": true,
|
| 23 |
+
"fullscreen": false,
|
| 24 |
+
"decorations": false,
|
| 25 |
+
"transparent": false,
|
| 26 |
+
"shadow": true,
|
| 27 |
+
"center": true,
|
| 28 |
+
"visible": true,
|
| 29 |
+
"zoomHotkeys": false
|
| 30 |
+
}
|
| 31 |
+
],
|
| 32 |
+
"security": {
|
| 33 |
+
"csp": null
|
| 34 |
+
}
|
| 35 |
+
},
|
| 36 |
+
"plugins": {
|
| 37 |
+
"sql": {
|
| 38 |
+
"preload": ["sqlite:muse.db"]
|
| 39 |
+
}
|
| 40 |
+
},
|
| 41 |
+
"bundle": {
|
| 42 |
+
"active": true,
|
| 43 |
+
"targets": "all",
|
| 44 |
+
"icon": [
|
| 45 |
+
"icons/32x32.png",
|
| 46 |
+
"icons/128x128.png",
|
| 47 |
+
"icons/128x128@2x.png",
|
| 48 |
+
"icons/icon.icns",
|
| 49 |
+
"icons/icon.ico"
|
| 50 |
+
]
|
| 51 |
+
}
|
| 52 |
+
}
|
src/App.tsx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { createEffect, createResource, createSignal, Match, Switch } from "solid-js";
|
| 2 |
+
import { loadSettings, saveSettings, DEFAULT_SETTINGS } from "./store/settings";
|
| 3 |
+
import type { MuseSettings, ThemeName } from "./types";
|
| 4 |
+
import Titlebar from "./components/Titlebar";
|
| 5 |
+
import Sidebar from "./components/Sidebar";
|
| 6 |
+
import HomePage from "./components/HomePage";
|
| 7 |
+
import Onboarding from "./components/Onboarding";
|
| 8 |
+
import PlaceholderPanel from "./components/PlaceholderPanel";
|
| 9 |
+
import ThemeSwitcher from "./components/ThemeSwitcher";
|
| 10 |
+
|
| 11 |
+
function App() {
|
| 12 |
+
const [initial] = createResource(loadSettings);
|
| 13 |
+
const [settings, setSettings] = createSignal<MuseSettings>(DEFAULT_SETTINGS);
|
| 14 |
+
|
| 15 |
+
createEffect(() => {
|
| 16 |
+
const loaded = initial();
|
| 17 |
+
if (loaded) setSettings(loaded);
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
createEffect(() => {
|
| 21 |
+
document.documentElement.dataset.theme = settings().theme;
|
| 22 |
+
});
|
| 23 |
+
|
| 24 |
+
const updateSettings = (patch: Partial<MuseSettings>) => {
|
| 25 |
+
const next = { ...settings(), ...patch };
|
| 26 |
+
setSettings(next);
|
| 27 |
+
void saveSettings(next);
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
const completeOnboarding = (userName: string, workspaceName: string, theme: ThemeName) => {
|
| 31 |
+
updateSettings({ userName, workspaceName, theme, onboardingComplete: true, activeSection: "home" });
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
return (
|
| 35 |
+
<Switch>
|
| 36 |
+
<Match when={!settings().onboardingComplete}>
|
| 37 |
+
<Onboarding onComplete={completeOnboarding} />
|
| 38 |
+
</Match>
|
| 39 |
+
<Match when={settings().onboardingComplete}>
|
| 40 |
+
<div class="app-shell">
|
| 41 |
+
<Titlebar workspaceName={settings().workspaceName} />
|
| 42 |
+
<Sidebar active={settings().activeSection} onNavigate={(activeSection) => updateSettings({ activeSection })} />
|
| 43 |
+
<main class="content-shell">
|
| 44 |
+
<Switch>
|
| 45 |
+
<Match when={settings().activeSection === "home"}>
|
| 46 |
+
<HomePage name={settings().userName} workspaceName={settings().workspaceName} />
|
| 47 |
+
</Match>
|
| 48 |
+
<Match when={settings().activeSection === "library"}>
|
| 49 |
+
<PlaceholderPanel title="Library" subtitle="Phase 3 will add image import, grid, tags, search, and color metadata." />
|
| 50 |
+
</Match>
|
| 51 |
+
<Match when={settings().activeSection === "board"}>
|
| 52 |
+
<PlaceholderPanel title="Board" subtitle="Phase 3 will add the PureRef-style infinite canvas." />
|
| 53 |
+
</Match>
|
| 54 |
+
<Match when={settings().activeSection === "settings"}>
|
| 55 |
+
<section class="panel settings-panel">
|
| 56 |
+
<div>
|
| 57 |
+
<p class="eyebrow">Settings</p>
|
| 58 |
+
<h1>Appearance</h1>
|
| 59 |
+
<p class="muted">Phase 0 settings persist locally with Tauri Store.</p>
|
| 60 |
+
</div>
|
| 61 |
+
<ThemeSwitcher selected={settings().theme} onSelect={(theme) => updateSettings({ theme })} />
|
| 62 |
+
</section>
|
| 63 |
+
</Match>
|
| 64 |
+
</Switch>
|
| 65 |
+
</main>
|
| 66 |
+
</div>
|
| 67 |
+
</Match>
|
| 68 |
+
</Switch>
|
| 69 |
+
);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
export default App;
|
src/components/HomePage.tsx
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export default function HomePage(props: { name: string; workspaceName: string }) {
|
| 2 |
+
const hour = new Date().getHours();
|
| 3 |
+
const greeting = hour < 12 ? "Good morning" : hour < 18 ? "Good afternoon" : "Good evening";
|
| 4 |
+
|
| 5 |
+
return (
|
| 6 |
+
<section class="home-page">
|
| 7 |
+
<div class="hero-card">
|
| 8 |
+
<p class="eyebrow">{props.workspaceName}</p>
|
| 9 |
+
<h1>{greeting}, {props.name}.</h1>
|
| 10 |
+
<p class="muted">Muse Phase 0 is ready: shell, themes, onboarding, settings, and local database foundation.</p>
|
| 11 |
+
<div class="url-shell" aria-label="Static phase 0 URL bar">
|
| 12 |
+
<span>⌕</span>
|
| 13 |
+
<input disabled value="Search or enter a URL — enabled in Phase 1" />
|
| 14 |
+
<span class="kbd">Ctrl L</span>
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
|
| 18 |
+
<div class="dashboard-grid">
|
| 19 |
+
<article class="mini-card">
|
| 20 |
+
<p class="eyebrow">Recent boards</p>
|
| 21 |
+
<h2>Empty studio</h2>
|
| 22 |
+
<p>Boards arrive in Phase 3.</p>
|
| 23 |
+
</article>
|
| 24 |
+
<article class="mini-card">
|
| 25 |
+
<p class="eyebrow">Recently saved</p>
|
| 26 |
+
<h2>No library items yet</h2>
|
| 27 |
+
<p>The Library module lands after browser/ad-block foundations.</p>
|
| 28 |
+
</article>
|
| 29 |
+
<article class="mini-card">
|
| 30 |
+
<p class="eyebrow">Continue browsing</p>
|
| 31 |
+
<h2>Tabs next</h2>
|
| 32 |
+
<p>Phase 1 adds real WebView tabs and URL navigation.</p>
|
| 33 |
+
</article>
|
| 34 |
+
</div>
|
| 35 |
+
</section>
|
| 36 |
+
);
|
| 37 |
+
}
|
src/components/Onboarding.tsx
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { createSignal, Match, Switch } from "solid-js";
|
| 2 |
+
import type { ThemeName } from "../types";
|
| 3 |
+
import ThemeSwitcher from "./ThemeSwitcher";
|
| 4 |
+
|
| 5 |
+
export default function Onboarding(props: { onComplete: (name: string, workspace: string, theme: ThemeName) => void }) {
|
| 6 |
+
const [step, setStep] = createSignal(0);
|
| 7 |
+
const [name, setName] = createSignal("Artist");
|
| 8 |
+
const [workspace, setWorkspace] = createSignal("First Workspace");
|
| 9 |
+
const [theme, setTheme] = createSignal<ThemeName>("dusk");
|
| 10 |
+
|
| 11 |
+
return (
|
| 12 |
+
<main class="onboarding" data-theme={theme()}>
|
| 13 |
+
<div class="onboarding-card">
|
| 14 |
+
<Switch>
|
| 15 |
+
<Match when={step() === 0}>
|
| 16 |
+
<p class="eyebrow">Welcome to Muse</p>
|
| 17 |
+
<h1>One quiet studio for visual research.</h1>
|
| 18 |
+
<p class="muted">No account. No feed. Local-first by design.</p>
|
| 19 |
+
<button class="primary" onClick={() => setStep(1)}>Begin</button>
|
| 20 |
+
</Match>
|
| 21 |
+
<Match when={step() === 1}>
|
| 22 |
+
<p class="eyebrow">Make it yours</p>
|
| 23 |
+
<h1>Choose your name and atmosphere.</h1>
|
| 24 |
+
<label>Name<input value={name()} onInput={(e) => setName(e.currentTarget.value)} /></label>
|
| 25 |
+
<ThemeSwitcher selected={theme()} onSelect={setTheme} />
|
| 26 |
+
<button class="primary" onClick={() => setStep(2)}>Next</button>
|
| 27 |
+
</Match>
|
| 28 |
+
<Match when={step() === 2}>
|
| 29 |
+
<p class="eyebrow">First workspace</p>
|
| 30 |
+
<h1>Name the project room.</h1>
|
| 31 |
+
<label>Workspace<input value={workspace()} onInput={(e) => setWorkspace(e.currentTarget.value)} /></label>
|
| 32 |
+
<button class="primary" onClick={() => props.onComplete(name(), workspace(), theme())}>Enter Muse</button>
|
| 33 |
+
</Match>
|
| 34 |
+
</Switch>
|
| 35 |
+
</div>
|
| 36 |
+
</main>
|
| 37 |
+
);
|
| 38 |
+
}
|
src/components/PlaceholderPanel.tsx
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export default function PlaceholderPanel(props: { title: string; subtitle: string }) {
|
| 2 |
+
return (
|
| 3 |
+
<section class="panel placeholder-panel">
|
| 4 |
+
<p class="eyebrow">Phase placeholder</p>
|
| 5 |
+
<h1>{props.title}</h1>
|
| 6 |
+
<p class="muted">{props.subtitle}</p>
|
| 7 |
+
</section>
|
| 8 |
+
);
|
| 9 |
+
}
|
src/components/Sidebar.tsx
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { MuseSettings } from "../types";
|
| 2 |
+
|
| 3 |
+
type Section = MuseSettings["activeSection"];
|
| 4 |
+
|
| 5 |
+
const items: Array<{ id: Section; icon: string; label: string }> = [
|
| 6 |
+
{ id: "home", icon: "⌂", label: "Home" },
|
| 7 |
+
{ id: "library", icon: "▦", label: "Library" },
|
| 8 |
+
{ id: "board", icon: "✣", label: "Board" },
|
| 9 |
+
{ id: "settings", icon: "⚙", label: "Settings" }
|
| 10 |
+
];
|
| 11 |
+
|
| 12 |
+
export default function Sidebar(props: { active: Section; onNavigate: (section: Section) => void }) {
|
| 13 |
+
return (
|
| 14 |
+
<aside class="sidebar">
|
| 15 |
+
<nav>
|
| 16 |
+
{items.map((item) => (
|
| 17 |
+
<button
|
| 18 |
+
type="button"
|
| 19 |
+
classList={{ "nav-item": true, active: props.active === item.id }}
|
| 20 |
+
onClick={() => props.onNavigate(item.id)}
|
| 21 |
+
title={item.label}
|
| 22 |
+
>
|
| 23 |
+
<span class="nav-icon">{item.icon}</span>
|
| 24 |
+
<span class="nav-label">{item.label}</span>
|
| 25 |
+
</button>
|
| 26 |
+
))}
|
| 27 |
+
</nav>
|
| 28 |
+
<div class="sidebar-footer">
|
| 29 |
+
<span class="shield-dot" />
|
| 30 |
+
<span class="footer-label">Shield ready</span>
|
| 31 |
+
</div>
|
| 32 |
+
</aside>
|
| 33 |
+
);
|
| 34 |
+
}
|
src/components/ThemeSwitcher.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { ThemeName } from "../types";
|
| 2 |
+
|
| 3 |
+
const themes: Array<{ id: ThemeName; label: string }> = [
|
| 4 |
+
{ id: "dusk", label: "Dusk" },
|
| 5 |
+
{ id: "parchment", label: "Parchment" },
|
| 6 |
+
{ id: "midnight", label: "Midnight" },
|
| 7 |
+
{ id: "studio", label: "Studio" },
|
| 8 |
+
{ id: "moss", label: "Moss" },
|
| 9 |
+
{ id: "rose", label: "Rose" },
|
| 10 |
+
{ id: "obsidian", label: "Obsidian" },
|
| 11 |
+
{ id: "linen", label: "Linen" }
|
| 12 |
+
];
|
| 13 |
+
|
| 14 |
+
export default function ThemeSwitcher(props: { selected: ThemeName; onSelect: (theme: ThemeName) => void }) {
|
| 15 |
+
return (
|
| 16 |
+
<div class="theme-grid">
|
| 17 |
+
{themes.map((theme) => (
|
| 18 |
+
<button
|
| 19 |
+
type="button"
|
| 20 |
+
classList={{ "theme-tile": true, selected: props.selected === theme.id }}
|
| 21 |
+
data-preview-theme={theme.id}
|
| 22 |
+
onClick={() => props.onSelect(theme.id)}
|
| 23 |
+
>
|
| 24 |
+
<span class="theme-swatch" />
|
| 25 |
+
{theme.label}
|
| 26 |
+
</button>
|
| 27 |
+
))}
|
| 28 |
+
</div>
|
| 29 |
+
);
|
| 30 |
+
}
|
src/components/Titlebar.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getCurrentWindow } from "@tauri-apps/api/window";
|
| 2 |
+
|
| 3 |
+
const appWindow = getCurrentWindow();
|
| 4 |
+
|
| 5 |
+
export default function Titlebar(props: { workspaceName: string }) {
|
| 6 |
+
return (
|
| 7 |
+
<header class="titlebar" data-tauri-drag-region>
|
| 8 |
+
<div class="workspace-dots" data-tauri-drag-region>
|
| 9 |
+
<span class="dot active" />
|
| 10 |
+
<span class="dot" />
|
| 11 |
+
<span class="dot" />
|
| 12 |
+
</div>
|
| 13 |
+
<div class="titlebar-copy" data-tauri-drag-region>
|
| 14 |
+
<strong>MUSE</strong>
|
| 15 |
+
<span>{props.workspaceName}</span>
|
| 16 |
+
</div>
|
| 17 |
+
<div class="window-controls" data-tauri-drag-region="false">
|
| 18 |
+
<button type="button" aria-label="Minimize" onClick={() => appWindow.minimize()}>—</button>
|
| 19 |
+
<button type="button" aria-label="Maximize" onClick={() => appWindow.toggleMaximize()}>□</button>
|
| 20 |
+
<button type="button" class="close" aria-label="Close" onClick={() => appWindow.close()}>×</button>
|
| 21 |
+
</div>
|
| 22 |
+
</header>
|
| 23 |
+
);
|
| 24 |
+
}
|
src/index.tsx
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* @refresh reload */
|
| 2 |
+
import { render } from "solid-js/web";
|
| 3 |
+
import App from "./App";
|
| 4 |
+
import "./styles/tokens.css";
|
| 5 |
+
import "./styles/app.css";
|
| 6 |
+
|
| 7 |
+
render(() => <App />, document.getElementById("root") as HTMLElement);
|
src/store/settings.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Store } from "@tauri-apps/plugin-store";
|
| 2 |
+
import type { MuseSettings, ThemeName } from "../types";
|
| 3 |
+
|
| 4 |
+
export const DEFAULT_SETTINGS: MuseSettings = {
|
| 5 |
+
onboardingComplete: false,
|
| 6 |
+
userName: "Artist",
|
| 7 |
+
workspaceName: "First Workspace",
|
| 8 |
+
theme: "dusk",
|
| 9 |
+
activeSection: "home"
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
let storePromise: Promise<Store> | undefined;
|
| 13 |
+
|
| 14 |
+
export function settingsStore() {
|
| 15 |
+
if (!storePromise) {
|
| 16 |
+
storePromise = Store.load("settings.json", {
|
| 17 |
+
defaults: DEFAULT_SETTINGS,
|
| 18 |
+
autoSave: 250
|
| 19 |
+
});
|
| 20 |
+
}
|
| 21 |
+
return storePromise;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
export async function loadSettings(): Promise<MuseSettings> {
|
| 25 |
+
const store = await settingsStore();
|
| 26 |
+
return {
|
| 27 |
+
onboardingComplete: (await store.get<boolean>("onboardingComplete")) ?? DEFAULT_SETTINGS.onboardingComplete,
|
| 28 |
+
userName: (await store.get<string>("userName")) ?? DEFAULT_SETTINGS.userName,
|
| 29 |
+
workspaceName: (await store.get<string>("workspaceName")) ?? DEFAULT_SETTINGS.workspaceName,
|
| 30 |
+
theme: ((await store.get<ThemeName>("theme")) ?? DEFAULT_SETTINGS.theme),
|
| 31 |
+
activeSection: ((await store.get<MuseSettings["activeSection"]>("activeSection")) ?? DEFAULT_SETTINGS.activeSection)
|
| 32 |
+
};
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
export async function saveSettings(settings: MuseSettings) {
|
| 36 |
+
const store = await settingsStore();
|
| 37 |
+
await Promise.all([
|
| 38 |
+
store.set("onboardingComplete", settings.onboardingComplete),
|
| 39 |
+
store.set("userName", settings.userName),
|
| 40 |
+
store.set("workspaceName", settings.workspaceName),
|
| 41 |
+
store.set("theme", settings.theme),
|
| 42 |
+
store.set("activeSection", settings.activeSection)
|
| 43 |
+
]);
|
| 44 |
+
await store.save();
|
| 45 |
+
}
|
src/styles/app.css
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
* { box-sizing: border-box; }
|
| 2 |
+
html, body, #root { width: 100%; height: 100%; margin: 0; overflow: hidden; }
|
| 3 |
+
body { background: var(--bg-base); color: var(--text-primary); }
|
| 4 |
+
button, input { font: inherit; }
|
| 5 |
+
button { color: inherit; }
|
| 6 |
+
|
| 7 |
+
.app-shell {
|
| 8 |
+
width: 100vw; height: 100vh;
|
| 9 |
+
display: grid;
|
| 10 |
+
grid-template-rows: var(--titlebar-h) 1fr;
|
| 11 |
+
grid-template-columns: var(--sidebar-w) 1fr;
|
| 12 |
+
background: radial-gradient(circle at 70% 10%, var(--accent-glow), transparent 34%), var(--bg-base);
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.titlebar { grid-column: 1 / -1; height: var(--titlebar-h); display: flex; align-items: center; gap: 14px; padding: 0 10px; background: var(--bg-overlay); border-bottom: 1px solid var(--border); user-select: none; app-region: drag; }
|
| 16 |
+
[data-tauri-drag-region] { app-region: drag; }
|
| 17 |
+
[data-tauri-drag-region="false"], button, input { app-region: no-drag; }
|
| 18 |
+
.workspace-dots { display: flex; gap: 7px; }
|
| 19 |
+
.dot { width: 10px; height: 10px; border-radius: 50%; background: var(--border); display: inline-block; }
|
| 20 |
+
.dot.active { background: var(--accent); box-shadow: 0 0 16px var(--accent-glow); }
|
| 21 |
+
.titlebar-copy { display: flex; align-items: center; gap: 10px; flex: 1; color: var(--text-secondary); font-size: 12px; }
|
| 22 |
+
.titlebar-copy strong { color: var(--text-primary); letter-spacing: .18em; }
|
| 23 |
+
.window-controls { display: flex; gap: 2px; }
|
| 24 |
+
.window-controls button { width: 34px; height: 26px; border: 0; border-radius: 6px; background: transparent; cursor: pointer; }
|
| 25 |
+
.window-controls button:hover { background: var(--bg-raised); }
|
| 26 |
+
.window-controls .close:hover { background: var(--error); color: white; }
|
| 27 |
+
|
| 28 |
+
.sidebar { grid-column: 1; grid-row: 2; width: var(--sidebar-w); background: color-mix(in srgb, var(--bg-surface) 88%, transparent); border-right: 1px solid var(--border); display: flex; flex-direction: column; justify-content: space-between; overflow: hidden; transition: width var(--transition-slow); z-index: 10; }
|
| 29 |
+
.sidebar:hover { width: var(--sidebar-w-expanded); }
|
| 30 |
+
.sidebar nav { display: flex; flex-direction: column; gap: 8px; padding: 12px 8px; }
|
| 31 |
+
.nav-item { height: 40px; display: flex; align-items: center; gap: 12px; border: 0; border-radius: 10px; background: transparent; cursor: pointer; padding: 0 12px; white-space: nowrap; }
|
| 32 |
+
.nav-item:hover, .nav-item.active { background: var(--bg-raised); }
|
| 33 |
+
.nav-item.active { color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent-glow); }
|
| 34 |
+
.nav-icon { min-width: 16px; text-align: center; }
|
| 35 |
+
.nav-label, .footer-label { opacity: 0; transition: opacity var(--transition); }
|
| 36 |
+
.sidebar:hover .nav-label, .sidebar:hover .footer-label { opacity: 1; }
|
| 37 |
+
.sidebar-footer { display: flex; align-items: center; gap: 10px; padding: 12px 16px; color: var(--text-muted); font-size: 12px; }
|
| 38 |
+
.shield-dot { width: 9px; height: 9px; background: var(--success); border-radius: 999px; box-shadow: 0 0 10px color-mix(in srgb, var(--success) 60%, transparent); }
|
| 39 |
+
|
| 40 |
+
.content-shell { grid-column: 2; grid-row: 2; min-width: 0; overflow: auto; padding: 40px; }
|
| 41 |
+
.home-page { max-width: 1120px; margin: 0 auto; }
|
| 42 |
+
.hero-card, .panel, .mini-card, .onboarding-card { background: var(--bg-overlay); border: 1px solid var(--border); border-radius: 24px; box-shadow: 0 24px 80px rgba(0,0,0,.22); backdrop-filter: blur(20px); }
|
| 43 |
+
.hero-card { padding: 48px; }
|
| 44 |
+
.eyebrow { color: var(--accent); text-transform: uppercase; letter-spacing: .14em; font-size: 12px; font-weight: 700; margin: 0 0 12px; }
|
| 45 |
+
h1 { font-size: clamp(34px, 5vw, 68px); line-height: .96; margin: 0 0 18px; letter-spacing: -0.055em; }
|
| 46 |
+
h2 { margin: 4px 0 8px; }
|
| 47 |
+
.muted, .mini-card p, .placeholder-panel p { color: var(--text-secondary); }
|
| 48 |
+
.url-shell { margin-top: 34px; height: 54px; display: flex; align-items: center; gap: 12px; padding: 0 16px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-surface); }
|
| 49 |
+
.url-shell input { flex: 1; border: 0; background: transparent; color: var(--text-secondary); outline: 0; }
|
| 50 |
+
.kbd { border: 1px solid var(--border); border-radius: 7px; padding: 3px 7px; color: var(--text-muted); font-size: 12px; }
|
| 51 |
+
.dashboard-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; margin-top: 18px; }
|
| 52 |
+
.mini-card { padding: 24px; }
|
| 53 |
+
.panel { padding: 40px; min-height: calc(100vh - 116px); }
|
| 54 |
+
.settings-panel { display: flex; flex-direction: column; gap: 28px; }
|
| 55 |
+
|
| 56 |
+
.onboarding { width: 100vw; height: 100vh; display: grid; place-items: center; padding: 24px; background: radial-gradient(circle at top right, var(--accent-glow), transparent 36%), var(--bg-base); color: var(--text-primary); }
|
| 57 |
+
.onboarding-card { width: min(720px, 100%); padding: 48px; }
|
| 58 |
+
.onboarding label { display: grid; gap: 8px; margin: 20px 0; color: var(--text-secondary); }
|
| 59 |
+
.onboarding input { width: 100%; height: 46px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg-surface); color: var(--text-primary); padding: 0 14px; }
|
| 60 |
+
.primary { margin-top: 24px; height: 44px; padding: 0 20px; border: 0; border-radius: 999px; background: var(--accent); color: var(--bg-base); font-weight: 800; cursor: pointer; }
|
| 61 |
+
.theme-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 10px; }
|
| 62 |
+
.theme-tile { display: flex; align-items: center; gap: 10px; padding: 12px; border-radius: 14px; border: 1px solid var(--border); background: var(--bg-surface); cursor: pointer; text-align: left; }
|
| 63 |
+
.theme-tile.selected { outline: 2px solid var(--accent); }
|
| 64 |
+
.theme-swatch { width: 24px; height: 24px; border-radius: 50%; background: linear-gradient(135deg, var(--accent), var(--bg-raised)); border: 1px solid var(--border); }
|
| 65 |
+
|
| 66 |
+
@media (max-width: 860px) { .content-shell { padding: 20px; } .dashboard-grid { grid-template-columns: 1fr; } .hero-card { padding: 30px; } }
|
src/styles/tokens.css
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
font-family: Geist, Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
| 3 |
+
color: var(--text-primary);
|
| 4 |
+
background: var(--bg-base);
|
| 5 |
+
--space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-12: 48px;
|
| 6 |
+
--radius-sm: 4px; --radius: 8px; --radius-lg: 12px; --radius-xl: 16px; --radius-full: 9999px;
|
| 7 |
+
--titlebar-h: 36px; --sidebar-w: 56px; --sidebar-w-expanded: 240px;
|
| 8 |
+
--transition-fast: 80ms ease-out; --transition: 150ms ease-out; --transition-slow: 250ms ease-out;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
:root[data-theme="dusk"], [data-theme="dusk"] { --bg-base:#100E0B; --bg-surface:#1A1712; --bg-raised:#232019; --bg-overlay:rgba(10,8,6,.82); --border:#3A3628; --text-primary:#F0EDE6; --text-secondary:#9A9488; --text-muted:#5A5450; --accent:#C49A3C; --accent-glow:rgba(196,154,60,.18); --success:#4CAF6E; --error:#E05252; }
|
| 12 |
+
:root[data-theme="parchment"], [data-theme="parchment"] { --bg-base:#F4EBDD; --bg-surface:#FFF8EA; --bg-raised:#F9F0DF; --bg-overlay:rgba(255,248,234,.86); --border:#D9C7A8; --text-primary:#2A2118; --text-secondary:#695A48; --text-muted:#9D8C76; --accent:#A36B2C; --accent-glow:rgba(163,107,44,.18); --success:#477A4A; --error:#B64C45; }
|
| 13 |
+
:root[data-theme="midnight"], [data-theme="midnight"] { --bg-base:#090D18; --bg-surface:#111827; --bg-raised:#172033; --bg-overlay:rgba(9,13,24,.84); --border:#27334D; --text-primary:#EEF3FF; --text-secondary:#9CA9C7; --text-muted:#586176; --accent:#8C7CF6; --accent-glow:rgba(140,124,246,.22); --success:#56C28B; --error:#F07178; }
|
| 14 |
+
:root[data-theme="studio"], [data-theme="studio"] { --bg-base:#F7F8FA; --bg-surface:#FFFFFF; --bg-raised:#EEF1F5; --bg-overlay:rgba(255,255,255,.88); --border:#D7DDE5; --text-primary:#151922; --text-secondary:#5E6978; --text-muted:#98A1AD; --accent:#2F6FED; --accent-glow:rgba(47,111,237,.18); --success:#1E8E5A; --error:#C83E3E; }
|
| 15 |
+
:root[data-theme="moss"], [data-theme="moss"] { --bg-base:#0D1410; --bg-surface:#162018; --bg-raised:#1E2A20; --bg-overlay:rgba(13,20,16,.84); --border:#304134; --text-primary:#EAF2E8; --text-secondary:#A3B39E; --text-muted:#60705D; --accent:#87A96B; --accent-glow:rgba(135,169,107,.2); --success:#82C785; --error:#D85E5E; }
|
| 16 |
+
:root[data-theme="rose"], [data-theme="rose"] { --bg-base:#FFF4F6; --bg-surface:#FFFFFF; --bg-raised:#FBE5EA; --bg-overlay:rgba(255,244,246,.88); --border:#EAC3CC; --text-primary:#2B1820; --text-secondary:#785B65; --text-muted:#AC8994; --accent:#C86B85; --accent-glow:rgba(200,107,133,.18); --success:#4F936A; --error:#C64D62; }
|
| 17 |
+
:root[data-theme="obsidian"], [data-theme="obsidian"] { --bg-base:#050606; --bg-surface:#0B1010; --bg-raised:#111818; --bg-overlay:rgba(5,6,6,.88); --border:#1D2A2A; --text-primary:#EAFDFC; --text-secondary:#8DA5A4; --text-muted:#4A5A5A; --accent:#2ED3C6; --accent-glow:rgba(46,211,198,.18); --success:#42C985; --error:#EF5555; }
|
| 18 |
+
:root[data-theme="linen"], [data-theme="linen"] { --bg-base:#F8EFE4; --bg-surface:#FFF9F0; --bg-raised:#F1DFCC; --bg-overlay:rgba(255,249,240,.88); --border:#D9BFA2; --text-primary:#2D2119; --text-secondary:#6F5A49; --text-muted:#A58D78; --accent:#C46F45; --accent-glow:rgba(196,111,69,.18); --success:#548B5C; --error:#BC4F45; }
|
src/types.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export type ThemeName =
|
| 2 |
+
| "dusk"
|
| 3 |
+
| "parchment"
|
| 4 |
+
| "midnight"
|
| 5 |
+
| "studio"
|
| 6 |
+
| "moss"
|
| 7 |
+
| "rose"
|
| 8 |
+
| "obsidian"
|
| 9 |
+
| "linen";
|
| 10 |
+
|
| 11 |
+
export interface MuseSettings {
|
| 12 |
+
onboardingComplete: boolean;
|
| 13 |
+
userName: string;
|
| 14 |
+
workspaceName: string;
|
| 15 |
+
theme: ThemeName;
|
| 16 |
+
activeSection: "home" | "library" | "board" | "settings";
|
| 17 |
+
}
|
tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2020",
|
| 4 |
+
"useDefineForClassFields": true,
|
| 5 |
+
"module": "ESNext",
|
| 6 |
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
| 7 |
+
"allowJs": false,
|
| 8 |
+
"skipLibCheck": true,
|
| 9 |
+
"esModuleInterop": true,
|
| 10 |
+
"allowSyntheticDefaultImports": true,
|
| 11 |
+
"strict": true,
|
| 12 |
+
"forceConsistentCasingInFileNames": true,
|
| 13 |
+
"moduleResolution": "Node",
|
| 14 |
+
"resolveJsonModule": true,
|
| 15 |
+
"isolatedModules": true,
|
| 16 |
+
"noEmit": true,
|
| 17 |
+
"jsx": "preserve",
|
| 18 |
+
"jsxImportSource": "solid-js"
|
| 19 |
+
},
|
| 20 |
+
"include": ["src"],
|
| 21 |
+
"references": []
|
| 22 |
+
}
|
vite.config.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from "vite";
|
| 2 |
+
import solid from "vite-plugin-solid";
|
| 3 |
+
// @ts-expect-error process is available in Vite config runtime
|
| 4 |
+
import process from "node:process";
|
| 5 |
+
|
| 6 |
+
const host = process.env.TAURI_DEV_HOST;
|
| 7 |
+
|
| 8 |
+
export default defineConfig(() => ({
|
| 9 |
+
plugins: [solid()],
|
| 10 |
+
clearScreen: false,
|
| 11 |
+
server: {
|
| 12 |
+
port: 1420,
|
| 13 |
+
strictPort: true,
|
| 14 |
+
host: host || false,
|
| 15 |
+
hmr: host ? { protocol: "ws", host, port: 1421 } : undefined,
|
| 16 |
+
watch: { ignored: ["**/src-tauri/**"] }
|
| 17 |
+
}
|
| 18 |
+
}));
|