search and/or read your saved and liked bluesky posts
wails go svelte sqlite desktop bluesky
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: SQLite schema models

* integrate Tailwind CSS for frontend

+1258 -166
+69
TODO.md
··· 1 + # Wails Desktop App — Roadmap 2 + 3 + ## Milestone 1 — Scaffold & Tooling 4 + 5 + - [x] Install Tailwind v4 (`tailwindcss`, `@tailwindcss/vite`) and wire `vite.config.ts` 6 + - [x] Install Fontsource packages (`@fontsource/jetbrains-mono`, `@fontsource-variable/geist`, `@fontsource-variable/lora`) 7 + - [x] Create `frontend/src/index.css` with `@import "tailwindcss"` and `@theme` tokens (palette, fonts) 8 + - [x] Put font CSS imports in `App.svelte` 9 + - [x] Set up `Taskfile.yml` for build tasks 10 + - [x] Verify `wails dev` hot-reloads a "Hello World" page with correct fonts and theme 11 + 12 + ## Milestone 2 — Database Layer 13 + 14 + - [x] Implement `database.go` — `Open()`, `Close()`, embedded migrations via `//go:embed` 15 + - [x] Copy existing migrations from CLI (`000`–`003`) and add `004_add_facets_column.sql` (`ALTER TABLE posts ADD COLUMN facets TEXT`) 16 + - [x] Enable WAL mode (`PRAGMA journal_mode=WAL`) on connection open 17 + - [x] Implement `models.go` — `Post`, `Auth`, `SearchResult` structs (add `Facets` field to `Post`) 18 + - [x] Implement `PostExists`, `InsertPost`, `UpsertAuth`, `GetAuth`, `SearchPosts`, `CountPosts` 19 + - [x] Verify the desktop app and CLI can read/write the same database concurrently 20 + 21 + ## Milestone 3 — Authentication 22 + 23 + - [ ] Implement `AuthService` struct with Wails service binding 24 + - [ ] `Login(handle)` — loopback OAuth via `oauth.NewLocalhostConfig`, persist tokens to shared DB 25 + - [ ] `Whoami(force)` — load auth from DB, optionally resolve handle from DID 26 + - [ ] `IsAuthenticated()` — check for valid auth row 27 + - [ ] Automatic token refresh on `OnStartup` lifecycle hook 28 + - [ ] Frontend: login view with handle input, "Login" button, and status display 29 + 30 + ## Milestone 4 — Indexing & Progress 31 + 32 + - [ ] Implement `IndexService` struct with Wails service binding 33 + - [ ] `Refresh(limit)` — concurrent bookmark + like fetch, batch insert (port `RefreshAndIndex` logic) 34 + - [ ] Populate `facets` column from `FeedPost.Facets` during `convertPostView` 35 + - [ ] `IsIndexing()` — thread-safe boolean guard to prevent concurrent refreshes 36 + - [ ] Emit Wails events: `index:started`, `index:progress`, `index:done` 37 + - [ ] Frontend: "Refresh" button in header, optional limit input 38 + - [ ] Frontend: bottom-pinned progress bar component driven by `index:*` events 39 + 40 + ## Milestone 5 — Search & Data Table 41 + 42 + - [ ] Implement `SearchService` struct with Wails service binding 43 + - [ ] `Search(query, source)` — FTS5 query with BM25 ranking and source filter 44 + - [ ] `CountPosts()` — total indexed post count 45 + - [ ] Frontend: search bar with query input and source filter (All / Saved / Liked segmented control) 46 + - [ ] Frontend: tabbed data table component (Saved / Liked / All tabs) 47 + - [ ] Columns: Author Handle, Text (truncated), Created At, ♥ Likes, 🔁 Reposts, 💬 Replies, Source 48 + - [ ] Client-side column sorting (click header to toggle asc/desc) 49 + - [ ] Row click → open post URL in default browser via `runtime.BrowserOpenURL` 50 + 51 + ## Milestone 6 — Facets & Log Viewer 52 + 53 + - [ ] Frontend: facet parser — convert UTF-8 byte offsets to JS string indices 54 + - [ ] Frontend: facet renderer — links (`<a>`), mentions (`@handle`), hashtags (`#tag`) 55 + - [ ] Integrate rendered facets into post text in data table rows 56 + - [ ] Implement `LogService` — custom `io.Writer` that emits `log:line` events 57 + - [ ] Wire `LogService` writer into `log.NewWithOptions` alongside file writer 58 + - [ ] Frontend: log viewer panel with terminal-style dark background, monospace text 59 + - [ ] Auto-scroll to bottom with scroll-lock toggle 60 + - [ ] Level filter buttons: Debug, Info, Warn, Error 61 + 62 + ## Milestone 7 — Polish 63 + 64 + - [ ] Empty state: show "No posts indexed" with prompt to refresh 65 + - [ ] Error handling: toast/notification for network failures, auth expiry 66 + - [ ] Keyboard shortcuts: `Cmd+K` focus search, `Cmd+R` refresh, `Cmd+L` toggle log viewer 67 + - [ ] Window title and app icon (`build/appicon.png`) 68 + - [ ] Production build verification (`wails3 build` → macOS `.app` bundle) 69 + - [ ] README with build instructions, screenshots, and usage
+32
Taskfile.yml
··· 1 + version: '3' 2 + 3 + tasks: 4 + dev: 5 + desc: Run the application in development mode with hot reload 6 + cmds: 7 + - wails dev 8 + 9 + build: 10 + desc: Build the application for production 11 + cmds: 12 + - wails build 13 + 14 + build:clean: 15 + desc: Clean and build the application for production 16 + cmds: 17 + - wails build -clean 18 + 19 + generate: 20 + desc: Generate Wails bindings 21 + cmds: 22 + - wails generate module 23 + 24 + init: 25 + desc: Initialize the project (install dependencies) 26 + cmds: 27 + - cd frontend && npm install 28 + 29 + check: 30 + desc: Check TypeScript and Svelte 31 + cmds: 32 + - cd frontend && npm run check
+32 -2
app.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 + "os" 7 + "path/filepath" 6 8 ) 7 9 8 10 // App struct ··· 15 17 return &App{} 16 18 } 17 19 18 - // startup is called when the app starts. The context is saved 19 - // so we can call the runtime methods 20 + // startup is called when the app starts. 21 + // 22 + // The context is saved so we can call the runtime methods 20 23 func (a *App) startup(ctx context.Context) { 21 24 a.ctx = ctx 25 + 26 + dbPath := getDBPath() 27 + if err := Open(dbPath); err != nil { 28 + fmt.Printf("failed to open database: %v\n", err) 29 + } 30 + } 31 + 32 + // shutdown is called when the app shuts down 33 + func (a *App) shutdown(ctx context.Context) { 34 + if err := Close(); err != nil { 35 + fmt.Printf("failed to close database: %v\n", err) 36 + } 37 + } 38 + 39 + // getDBPath returns the path to the shared database 40 + func getDBPath() string { 41 + if dir := os.Getenv("BSKY_BROWSER_DATA"); dir != "" { 42 + return filepath.Join(dir, "bsky-browser.db") 43 + } 44 + 45 + configDir := os.Getenv("XDG_CONFIG_HOME") 46 + if configDir == "" { 47 + home, _ := os.UserHomeDir() 48 + configDir = filepath.Join(home, ".config") 49 + } 50 + 51 + return filepath.Join(configDir, "bsky-browser", "bsky-browser.db") 22 52 } 23 53 24 54 // Greet returns a greeting for the given name
+323
database.go
··· 1 + package main 2 + 3 + import ( 4 + "database/sql" 5 + "embed" 6 + "fmt" 7 + "os" 8 + "path/filepath" 9 + "time" 10 + 11 + _ "modernc.org/sqlite" 12 + ) 13 + 14 + var db *sql.DB 15 + 16 + //go:embed migrations/*.sql 17 + var migrationsFS embed.FS 18 + 19 + // Open opens the database connection and runs migrations 20 + func Open(dbPath string) error { 21 + fmt.Printf("opening database: %s\n", dbPath) 22 + 23 + dir := filepath.Dir(dbPath) 24 + if err := os.MkdirAll(dir, 0755); err != nil { 25 + return fmt.Errorf("failed to create database directory: %w", err) 26 + } 27 + 28 + var err error 29 + db, err = sql.Open("sqlite", dbPath+"?_pragma=foreign_keys(1)") 30 + if err != nil { 31 + return fmt.Errorf("failed to open database: %w", err) 32 + } 33 + 34 + if err := db.Ping(); err != nil { 35 + return fmt.Errorf("failed to ping database: %w", err) 36 + } 37 + 38 + _, err = db.Exec("PRAGMA journal_mode=WAL") 39 + if err != nil { 40 + return fmt.Errorf("failed to enable WAL mode: %w", err) 41 + } 42 + 43 + fmt.Println("database connection established with WAL mode") 44 + 45 + if err := runMigrations(); err != nil { 46 + return fmt.Errorf("failed to run migrations: %w", err) 47 + } 48 + 49 + fmt.Println("database migrations completed successfully") 50 + return nil 51 + } 52 + 53 + func runMigrations() error { 54 + content, err := migrationsFS.ReadFile("migrations/000_initial_schema.sql") 55 + if err != nil { 56 + return fmt.Errorf("failed to read migration: %w", err) 57 + } 58 + 59 + if _, err := db.Exec(string(content)); err != nil { 60 + return fmt.Errorf("failed to execute migration: %w", err) 61 + } 62 + 63 + return nil 64 + } 65 + 66 + // Close closes the database connection 67 + func Close() error { 68 + fmt.Println("closing database connection") 69 + if db != nil { 70 + err := db.Close() 71 + if err != nil { 72 + fmt.Printf("failed to close database: %v\n", err) 73 + return err 74 + } 75 + fmt.Println("database connection closed") 76 + } 77 + return nil 78 + } 79 + 80 + // PostExists checks if a post with the given URI already exists in the database 81 + func PostExists(uri string) (bool, error) { 82 + var exists bool 83 + err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM posts WHERE uri = ?)", uri).Scan(&exists) 84 + if err != nil { 85 + return false, err 86 + } 87 + return exists, nil 88 + } 89 + 90 + // InsertPost inserts a post into the database 91 + func InsertPost(post *Post) error { 92 + fmt.Printf("inserting post: %s by %s\n", post.URI, post.AuthorHandle) 93 + 94 + exists, err := PostExists(post.URI) 95 + if err != nil { 96 + fmt.Printf("failed to check if post exists: %s, error: %v\n", post.URI, err) 97 + return err 98 + } 99 + 100 + if exists { 101 + fmt.Printf("skipping already indexed post: %s\n", post.URI) 102 + return nil 103 + } 104 + 105 + query := ` 106 + INSERT INTO posts (uri, cid, author_did, author_handle, text, created_at, like_count, repost_count, reply_count, source, facets) 107 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 108 + ON CONFLICT(uri) DO UPDATE SET 109 + cid = excluded.cid, 110 + author_did = excluded.author_did, 111 + author_handle = excluded.author_handle, 112 + text = excluded.text, 113 + created_at = excluded.created_at, 114 + like_count = excluded.like_count, 115 + repost_count = excluded.repost_count, 116 + reply_count = excluded.reply_count, 117 + source = excluded.source, 118 + facets = excluded.facets, 119 + indexed_at = CURRENT_TIMESTAMP 120 + ` 121 + 122 + _, err = db.Exec(query, 123 + post.URI, 124 + post.CID, 125 + post.AuthorDID, 126 + post.AuthorHandle, 127 + post.Text, 128 + post.CreatedAt, 129 + post.LikeCount, 130 + post.RepostCount, 131 + post.ReplyCount, 132 + post.Source, 133 + post.Facets, 134 + ) 135 + 136 + if err != nil { 137 + fmt.Printf("failed to insert post: %s, error: %v\n", post.URI, err) 138 + } 139 + 140 + return err 141 + } 142 + 143 + // UpsertAuth inserts or updates auth information 144 + func UpsertAuth(auth *Auth) error { 145 + fmt.Printf("upserting auth: %s (%s)\n", auth.DID, auth.Handle) 146 + 147 + query := ` 148 + INSERT INTO auth (did, handle, access_jwt, refresh_jwt, pds_url, session_id, 149 + auth_server_url, auth_server_token_endpoint, auth_server_revocation_endpoint, 150 + dpop_auth_nonce, dpop_host_nonce, dpop_private_key, updated_at) 151 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) 152 + ON CONFLICT(did) DO UPDATE SET 153 + handle = excluded.handle, 154 + access_jwt = excluded.access_jwt, 155 + refresh_jwt = excluded.refresh_jwt, 156 + pds_url = excluded.pds_url, 157 + session_id = excluded.session_id, 158 + auth_server_url = excluded.auth_server_url, 159 + auth_server_token_endpoint = excluded.auth_server_token_endpoint, 160 + auth_server_revocation_endpoint = excluded.auth_server_revocation_endpoint, 161 + dpop_auth_nonce = excluded.dpop_auth_nonce, 162 + dpop_host_nonce = excluded.dpop_host_nonce, 163 + dpop_private_key = excluded.dpop_private_key, 164 + updated_at = CURRENT_TIMESTAMP 165 + ` 166 + 167 + _, err := db.Exec(query, 168 + auth.DID, 169 + auth.Handle, 170 + auth.AccessJWT, 171 + auth.RefreshJWT, 172 + auth.PDSURL, 173 + auth.SessionID, 174 + auth.AuthServerURL, 175 + auth.AuthServerTokenEndpoint, 176 + auth.AuthServerRevocationEndpoint, 177 + auth.DPoPAuthNonce, 178 + auth.DPoPHostNonce, 179 + auth.DPoPPrivateKey, 180 + ) 181 + 182 + if err != nil { 183 + fmt.Printf("failed to upsert auth: %s, error: %v\n", auth.DID, err) 184 + } 185 + 186 + return err 187 + } 188 + 189 + // GetAuth loads the auth record from the database 190 + func GetAuth() (*Auth, error) { 191 + fmt.Println("loading auth from database") 192 + 193 + query := `SELECT did, handle, access_jwt, refresh_jwt, pds_url, session_id, 194 + auth_server_url, auth_server_token_endpoint, auth_server_revocation_endpoint, 195 + dpop_auth_nonce, dpop_host_nonce, dpop_private_key, updated_at 196 + FROM auth LIMIT 1` 197 + 198 + var auth Auth 199 + var updatedAt string 200 + 201 + var sessionID, authServerURL, authServerTokenEndpoint, authServerRevocationEndpoint, dpopAuthNonce, dpopHostNonce, dpopPrivateKey sql.NullString 202 + 203 + err := db.QueryRow(query).Scan( 204 + &auth.DID, 205 + &auth.Handle, 206 + &auth.AccessJWT, 207 + &auth.RefreshJWT, 208 + &auth.PDSURL, 209 + &sessionID, 210 + &authServerURL, 211 + &authServerTokenEndpoint, 212 + &authServerRevocationEndpoint, 213 + &dpopAuthNonce, 214 + &dpopHostNonce, 215 + &dpopPrivateKey, 216 + &updatedAt, 217 + ) 218 + 219 + if sessionID.Valid { 220 + auth.SessionID = sessionID.String 221 + } 222 + if authServerURL.Valid { 223 + auth.AuthServerURL = authServerURL.String 224 + } 225 + if authServerTokenEndpoint.Valid { 226 + auth.AuthServerTokenEndpoint = authServerTokenEndpoint.String 227 + } 228 + if authServerRevocationEndpoint.Valid { 229 + auth.AuthServerRevocationEndpoint = authServerRevocationEndpoint.String 230 + } 231 + if dpopAuthNonce.Valid { 232 + auth.DPoPAuthNonce = dpopAuthNonce.String 233 + } 234 + if dpopHostNonce.Valid { 235 + auth.DPoPHostNonce = dpopHostNonce.String 236 + } 237 + if dpopPrivateKey.Valid { 238 + auth.DPoPPrivateKey = dpopPrivateKey.String 239 + } 240 + 241 + if err == sql.ErrNoRows { 242 + fmt.Println("no auth record found in database") 243 + return nil, nil 244 + } 245 + if err != nil { 246 + fmt.Printf("failed to load auth: %v\n", err) 247 + return nil, err 248 + } 249 + 250 + auth.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", updatedAt) 251 + fmt.Printf("auth loaded successfully: %s (%s)\n", auth.DID, auth.Handle) 252 + return &auth, nil 253 + } 254 + 255 + // SearchPosts searches posts using FTS5 256 + func SearchPosts(query string, source string) ([]SearchResult, error) { 257 + fmt.Printf("searching posts: query=%s, source=%s\n", query, source) 258 + 259 + sqlQuery := ` 260 + SELECT p.uri, p.cid, p.author_did, p.author_handle, p.text, p.created_at, 261 + p.like_count, p.repost_count, p.reply_count, p.source, p.indexed_at, 262 + bm25(posts_fts, 5.0, 1.0) AS rank 263 + FROM posts_fts 264 + JOIN posts p ON posts_fts.rowid = p.rowid 265 + WHERE posts_fts MATCH ? 266 + AND (? = '' OR p.source = ?) 267 + ORDER BY rank 268 + LIMIT 25 269 + ` 270 + 271 + rows, err := db.Query(sqlQuery, query, source, source) 272 + if err != nil { 273 + fmt.Printf("failed to execute search query: %v\n", err) 274 + return nil, err 275 + } 276 + defer rows.Close() 277 + 278 + var results []SearchResult 279 + for rows.Next() { 280 + var r SearchResult 281 + var createdAt, indexedAt string 282 + 283 + err := rows.Scan( 284 + &r.URI, 285 + &r.CID, 286 + &r.AuthorDID, 287 + &r.AuthorHandle, 288 + &r.Text, 289 + &createdAt, 290 + &r.LikeCount, 291 + &r.RepostCount, 292 + &r.ReplyCount, 293 + &r.Source, 294 + &indexedAt, 295 + &r.Rank, 296 + ) 297 + if err != nil { 298 + return nil, err 299 + } 300 + 301 + r.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt) 302 + r.IndexedAt, _ = time.Parse("2006-01-02 15:04:05", indexedAt) 303 + results = append(results, r) 304 + } 305 + 306 + fmt.Printf("search completed: %d results\n", len(results)) 307 + return results, rows.Err() 308 + } 309 + 310 + // CountPosts returns the total number of posts in the database 311 + func CountPosts() (int, error) { 312 + fmt.Println("counting posts in database") 313 + 314 + var count int 315 + err := db.QueryRow("SELECT COUNT(*) FROM posts").Scan(&count) 316 + if err != nil { 317 + fmt.Printf("failed to count posts: %v\n", err) 318 + return 0, err 319 + } 320 + 321 + fmt.Printf("post count: %d\n", count) 322 + return count, nil 323 + }
+621 -70
frontend/package-lock.json
··· 7 7 "": { 8 8 "name": "frontend", 9 9 "version": "0.0.0", 10 + "dependencies": { 11 + "@fontsource-variable/geist": "^5.2.8", 12 + "@fontsource-variable/jetbrains-mono": "^5.2.8", 13 + "@fontsource-variable/lora": "^5.2.8", 14 + "@tailwindcss/vite": "^4.2.1", 15 + "tailwindcss": "^4.2.1" 16 + }, 10 17 "devDependencies": { 11 18 "@sveltejs/vite-plugin-svelte": "^6.2.4", 12 19 "svelte": "^5.53.12", ··· 23 30 "cpu": [ 24 31 "ppc64" 25 32 ], 26 - "dev": true, 27 33 "license": "MIT", 28 34 "optional": true, 29 35 "os": [ ··· 40 46 "cpu": [ 41 47 "arm" 42 48 ], 43 - "dev": true, 44 49 "license": "MIT", 45 50 "optional": true, 46 51 "os": [ ··· 57 62 "cpu": [ 58 63 "arm64" 59 64 ], 60 - "dev": true, 61 65 "license": "MIT", 62 66 "optional": true, 63 67 "os": [ ··· 74 78 "cpu": [ 75 79 "x64" 76 80 ], 77 - "dev": true, 78 81 "license": "MIT", 79 82 "optional": true, 80 83 "os": [ ··· 91 94 "cpu": [ 92 95 "arm64" 93 96 ], 94 - "dev": true, 95 97 "license": "MIT", 96 98 "optional": true, 97 99 "os": [ ··· 108 110 "cpu": [ 109 111 "x64" 110 112 ], 111 - "dev": true, 112 113 "license": "MIT", 113 114 "optional": true, 114 115 "os": [ ··· 125 126 "cpu": [ 126 127 "arm64" 127 128 ], 128 - "dev": true, 129 129 "license": "MIT", 130 130 "optional": true, 131 131 "os": [ ··· 142 142 "cpu": [ 143 143 "x64" 144 144 ], 145 - "dev": true, 146 145 "license": "MIT", 147 146 "optional": true, 148 147 "os": [ ··· 159 158 "cpu": [ 160 159 "arm" 161 160 ], 162 - "dev": true, 163 161 "license": "MIT", 164 162 "optional": true, 165 163 "os": [ ··· 176 174 "cpu": [ 177 175 "arm64" 178 176 ], 179 - "dev": true, 180 177 "license": "MIT", 181 178 "optional": true, 182 179 "os": [ ··· 193 190 "cpu": [ 194 191 "ia32" 195 192 ], 196 - "dev": true, 197 193 "license": "MIT", 198 194 "optional": true, 199 195 "os": [ ··· 210 206 "cpu": [ 211 207 "loong64" 212 208 ], 213 - "dev": true, 214 209 "license": "MIT", 215 210 "optional": true, 216 211 "os": [ ··· 227 222 "cpu": [ 228 223 "mips64el" 229 224 ], 230 - "dev": true, 231 225 "license": "MIT", 232 226 "optional": true, 233 227 "os": [ ··· 244 238 "cpu": [ 245 239 "ppc64" 246 240 ], 247 - "dev": true, 248 241 "license": "MIT", 249 242 "optional": true, 250 243 "os": [ ··· 261 254 "cpu": [ 262 255 "riscv64" 263 256 ], 264 - "dev": true, 265 257 "license": "MIT", 266 258 "optional": true, 267 259 "os": [ ··· 278 270 "cpu": [ 279 271 "s390x" 280 272 ], 281 - "dev": true, 282 273 "license": "MIT", 283 274 "optional": true, 284 275 "os": [ ··· 295 286 "cpu": [ 296 287 "x64" 297 288 ], 298 - "dev": true, 299 289 "license": "MIT", 300 290 "optional": true, 301 291 "os": [ ··· 312 302 "cpu": [ 313 303 "x64" 314 304 ], 315 - "dev": true, 316 305 "license": "MIT", 317 306 "optional": true, 318 307 "os": [ ··· 329 318 "cpu": [ 330 319 "arm64" 331 320 ], 332 - "dev": true, 333 321 "license": "MIT", 334 322 "optional": true, 335 323 "os": [ ··· 346 334 "cpu": [ 347 335 "x64" 348 336 ], 349 - "dev": true, 350 337 "license": "MIT", 351 338 "optional": true, 352 339 "os": [ ··· 363 350 "cpu": [ 364 351 "arm64" 365 352 ], 366 - "dev": true, 367 353 "license": "MIT", 368 354 "optional": true, 369 355 "os": [ ··· 380 366 "cpu": [ 381 367 "x64" 382 368 ], 383 - "dev": true, 384 369 "license": "MIT", 385 370 "optional": true, 386 371 "os": [ ··· 397 382 "cpu": [ 398 383 "arm64" 399 384 ], 400 - "dev": true, 401 385 "license": "MIT", 402 386 "optional": true, 403 387 "os": [ ··· 414 398 "cpu": [ 415 399 "ia32" 416 400 ], 417 - "dev": true, 418 401 "license": "MIT", 419 402 "optional": true, 420 403 "os": [ ··· 431 414 "cpu": [ 432 415 "x64" 433 416 ], 434 - "dev": true, 435 417 "license": "MIT", 436 418 "optional": true, 437 419 "os": [ ··· 441 423 "node": ">=18" 442 424 } 443 425 }, 426 + "node_modules/@fontsource-variable/geist": { 427 + "version": "5.2.8", 428 + "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.2.8.tgz", 429 + "integrity": "sha512-cJ6m9e+8MQ5dCYJsLylfZrgBh6KkG4bOLckB35Tr9J/EqdkEM6QllH5PxqP1dhTvFup+HtMRPuz9xOjxXJggxw==", 430 + "license": "OFL-1.1", 431 + "funding": { 432 + "url": "https://github.com/sponsors/ayuhito" 433 + } 434 + }, 435 + "node_modules/@fontsource-variable/jetbrains-mono": { 436 + "version": "5.2.8", 437 + "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz", 438 + "integrity": "sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q==", 439 + "license": "OFL-1.1", 440 + "funding": { 441 + "url": "https://github.com/sponsors/ayuhito" 442 + } 443 + }, 444 + "node_modules/@fontsource-variable/lora": { 445 + "version": "5.2.8", 446 + "resolved": "https://registry.npmjs.org/@fontsource-variable/lora/-/lora-5.2.8.tgz", 447 + "integrity": "sha512-cxjTJ9BbOWIzusewR4UMBLVePvTSWV6dtNaNsCkF/oKoyA68fJGWfaYCILOOP1BObE4dmjfZ3xo6m9hdHhtYhg==", 448 + "license": "OFL-1.1", 449 + "funding": { 450 + "url": "https://github.com/sponsors/ayuhito" 451 + } 452 + }, 444 453 "node_modules/@jridgewell/gen-mapping": { 445 454 "version": "0.3.13", 446 455 "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", 447 456 "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", 448 - "dev": true, 449 457 "license": "MIT", 450 458 "dependencies": { 451 459 "@jridgewell/sourcemap-codec": "^1.5.0", ··· 456 464 "version": "2.3.5", 457 465 "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 458 466 "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 459 - "dev": true, 460 467 "license": "MIT", 461 468 "dependencies": { 462 469 "@jridgewell/gen-mapping": "^0.3.5", ··· 467 474 "version": "3.1.2", 468 475 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 469 476 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 470 - "dev": true, 471 477 "license": "MIT", 472 478 "engines": { 473 479 "node": ">=6.0.0" ··· 477 483 "version": "1.5.5", 478 484 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 479 485 "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 480 - "dev": true, 481 486 "license": "MIT" 482 487 }, 483 488 "node_modules/@jridgewell/trace-mapping": { 484 489 "version": "0.3.31", 485 490 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 486 491 "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 487 - "dev": true, 488 492 "license": "MIT", 489 493 "dependencies": { 490 494 "@jridgewell/resolve-uri": "^3.1.0", ··· 498 502 "cpu": [ 499 503 "arm" 500 504 ], 501 - "dev": true, 502 505 "license": "MIT", 503 506 "optional": true, 504 507 "os": [ ··· 512 515 "cpu": [ 513 516 "arm64" 514 517 ], 515 - "dev": true, 516 518 "license": "MIT", 517 519 "optional": true, 518 520 "os": [ ··· 526 528 "cpu": [ 527 529 "arm64" 528 530 ], 529 - "dev": true, 530 531 "license": "MIT", 531 532 "optional": true, 532 533 "os": [ ··· 540 541 "cpu": [ 541 542 "x64" 542 543 ], 543 - "dev": true, 544 544 "license": "MIT", 545 545 "optional": true, 546 546 "os": [ ··· 554 554 "cpu": [ 555 555 "arm64" 556 556 ], 557 - "dev": true, 558 557 "license": "MIT", 559 558 "optional": true, 560 559 "os": [ ··· 568 567 "cpu": [ 569 568 "x64" 570 569 ], 571 - "dev": true, 572 570 "license": "MIT", 573 571 "optional": true, 574 572 "os": [ ··· 582 580 "cpu": [ 583 581 "arm" 584 582 ], 585 - "dev": true, 586 583 "libc": [ 587 584 "glibc" 588 585 ], ··· 599 596 "cpu": [ 600 597 "arm" 601 598 ], 602 - "dev": true, 603 599 "libc": [ 604 600 "musl" 605 601 ], ··· 616 612 "cpu": [ 617 613 "arm64" 618 614 ], 619 - "dev": true, 620 615 "libc": [ 621 616 "glibc" 622 617 ], ··· 633 628 "cpu": [ 634 629 "arm64" 635 630 ], 636 - "dev": true, 637 631 "libc": [ 638 632 "musl" 639 633 ], ··· 650 644 "cpu": [ 651 645 "loong64" 652 646 ], 653 - "dev": true, 654 647 "libc": [ 655 648 "glibc" 656 649 ], ··· 667 660 "cpu": [ 668 661 "loong64" 669 662 ], 670 - "dev": true, 671 663 "libc": [ 672 664 "musl" 673 665 ], ··· 684 676 "cpu": [ 685 677 "ppc64" 686 678 ], 687 - "dev": true, 688 679 "libc": [ 689 680 "glibc" 690 681 ], ··· 701 692 "cpu": [ 702 693 "ppc64" 703 694 ], 704 - "dev": true, 705 695 "libc": [ 706 696 "musl" 707 697 ], ··· 718 708 "cpu": [ 719 709 "riscv64" 720 710 ], 721 - "dev": true, 722 711 "libc": [ 723 712 "glibc" 724 713 ], ··· 735 724 "cpu": [ 736 725 "riscv64" 737 726 ], 738 - "dev": true, 739 727 "libc": [ 740 728 "musl" 741 729 ], ··· 752 740 "cpu": [ 753 741 "s390x" 754 742 ], 755 - "dev": true, 756 743 "libc": [ 757 744 "glibc" 758 745 ], ··· 769 756 "cpu": [ 770 757 "x64" 771 758 ], 772 - "dev": true, 773 759 "libc": [ 774 760 "glibc" 775 761 ], ··· 786 772 "cpu": [ 787 773 "x64" 788 774 ], 789 - "dev": true, 790 775 "libc": [ 791 776 "musl" 792 777 ], ··· 803 788 "cpu": [ 804 789 "x64" 805 790 ], 806 - "dev": true, 807 791 "license": "MIT", 808 792 "optional": true, 809 793 "os": [ ··· 817 801 "cpu": [ 818 802 "arm64" 819 803 ], 820 - "dev": true, 821 804 "license": "MIT", 822 805 "optional": true, 823 806 "os": [ ··· 831 814 "cpu": [ 832 815 "arm64" 833 816 ], 834 - "dev": true, 835 817 "license": "MIT", 836 818 "optional": true, 837 819 "os": [ ··· 845 827 "cpu": [ 846 828 "ia32" 847 829 ], 848 - "dev": true, 849 830 "license": "MIT", 850 831 "optional": true, 851 832 "os": [ ··· 859 840 "cpu": [ 860 841 "x64" 861 842 ], 862 - "dev": true, 863 843 "license": "MIT", 864 844 "optional": true, 865 845 "os": [ ··· 873 853 "cpu": [ 874 854 "x64" 875 855 ], 876 - "dev": true, 877 856 "license": "MIT", 878 857 "optional": true, 879 858 "os": [ ··· 929 908 "vite": "^6.3.0 || ^7.0.0" 930 909 } 931 910 }, 911 + "node_modules/@tailwindcss/node": { 912 + "version": "4.2.1", 913 + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", 914 + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", 915 + "license": "MIT", 916 + "dependencies": { 917 + "@jridgewell/remapping": "^2.3.5", 918 + "enhanced-resolve": "^5.19.0", 919 + "jiti": "^2.6.1", 920 + "lightningcss": "1.31.1", 921 + "magic-string": "^0.30.21", 922 + "source-map-js": "^1.2.1", 923 + "tailwindcss": "4.2.1" 924 + } 925 + }, 926 + "node_modules/@tailwindcss/oxide": { 927 + "version": "4.2.1", 928 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", 929 + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", 930 + "license": "MIT", 931 + "engines": { 932 + "node": ">= 20" 933 + }, 934 + "optionalDependencies": { 935 + "@tailwindcss/oxide-android-arm64": "4.2.1", 936 + "@tailwindcss/oxide-darwin-arm64": "4.2.1", 937 + "@tailwindcss/oxide-darwin-x64": "4.2.1", 938 + "@tailwindcss/oxide-freebsd-x64": "4.2.1", 939 + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", 940 + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", 941 + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", 942 + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", 943 + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", 944 + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", 945 + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", 946 + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" 947 + } 948 + }, 949 + "node_modules/@tailwindcss/oxide-android-arm64": { 950 + "version": "4.2.1", 951 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", 952 + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", 953 + "cpu": [ 954 + "arm64" 955 + ], 956 + "license": "MIT", 957 + "optional": true, 958 + "os": [ 959 + "android" 960 + ], 961 + "engines": { 962 + "node": ">= 20" 963 + } 964 + }, 965 + "node_modules/@tailwindcss/oxide-darwin-arm64": { 966 + "version": "4.2.1", 967 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", 968 + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", 969 + "cpu": [ 970 + "arm64" 971 + ], 972 + "license": "MIT", 973 + "optional": true, 974 + "os": [ 975 + "darwin" 976 + ], 977 + "engines": { 978 + "node": ">= 20" 979 + } 980 + }, 981 + "node_modules/@tailwindcss/oxide-darwin-x64": { 982 + "version": "4.2.1", 983 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", 984 + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", 985 + "cpu": [ 986 + "x64" 987 + ], 988 + "license": "MIT", 989 + "optional": true, 990 + "os": [ 991 + "darwin" 992 + ], 993 + "engines": { 994 + "node": ">= 20" 995 + } 996 + }, 997 + "node_modules/@tailwindcss/oxide-freebsd-x64": { 998 + "version": "4.2.1", 999 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", 1000 + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", 1001 + "cpu": [ 1002 + "x64" 1003 + ], 1004 + "license": "MIT", 1005 + "optional": true, 1006 + "os": [ 1007 + "freebsd" 1008 + ], 1009 + "engines": { 1010 + "node": ">= 20" 1011 + } 1012 + }, 1013 + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { 1014 + "version": "4.2.1", 1015 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", 1016 + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", 1017 + "cpu": [ 1018 + "arm" 1019 + ], 1020 + "license": "MIT", 1021 + "optional": true, 1022 + "os": [ 1023 + "linux" 1024 + ], 1025 + "engines": { 1026 + "node": ">= 20" 1027 + } 1028 + }, 1029 + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { 1030 + "version": "4.2.1", 1031 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", 1032 + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", 1033 + "cpu": [ 1034 + "arm64" 1035 + ], 1036 + "libc": [ 1037 + "glibc" 1038 + ], 1039 + "license": "MIT", 1040 + "optional": true, 1041 + "os": [ 1042 + "linux" 1043 + ], 1044 + "engines": { 1045 + "node": ">= 20" 1046 + } 1047 + }, 1048 + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { 1049 + "version": "4.2.1", 1050 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", 1051 + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", 1052 + "cpu": [ 1053 + "arm64" 1054 + ], 1055 + "libc": [ 1056 + "musl" 1057 + ], 1058 + "license": "MIT", 1059 + "optional": true, 1060 + "os": [ 1061 + "linux" 1062 + ], 1063 + "engines": { 1064 + "node": ">= 20" 1065 + } 1066 + }, 1067 + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { 1068 + "version": "4.2.1", 1069 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", 1070 + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", 1071 + "cpu": [ 1072 + "x64" 1073 + ], 1074 + "libc": [ 1075 + "glibc" 1076 + ], 1077 + "license": "MIT", 1078 + "optional": true, 1079 + "os": [ 1080 + "linux" 1081 + ], 1082 + "engines": { 1083 + "node": ">= 20" 1084 + } 1085 + }, 1086 + "node_modules/@tailwindcss/oxide-linux-x64-musl": { 1087 + "version": "4.2.1", 1088 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", 1089 + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", 1090 + "cpu": [ 1091 + "x64" 1092 + ], 1093 + "libc": [ 1094 + "musl" 1095 + ], 1096 + "license": "MIT", 1097 + "optional": true, 1098 + "os": [ 1099 + "linux" 1100 + ], 1101 + "engines": { 1102 + "node": ">= 20" 1103 + } 1104 + }, 1105 + "node_modules/@tailwindcss/oxide-wasm32-wasi": { 1106 + "version": "4.2.1", 1107 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", 1108 + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", 1109 + "bundleDependencies": [ 1110 + "@napi-rs/wasm-runtime", 1111 + "@emnapi/core", 1112 + "@emnapi/runtime", 1113 + "@tybys/wasm-util", 1114 + "@emnapi/wasi-threads", 1115 + "tslib" 1116 + ], 1117 + "cpu": [ 1118 + "wasm32" 1119 + ], 1120 + "license": "MIT", 1121 + "optional": true, 1122 + "dependencies": { 1123 + "@emnapi/core": "^1.8.1", 1124 + "@emnapi/runtime": "^1.8.1", 1125 + "@emnapi/wasi-threads": "^1.1.0", 1126 + "@napi-rs/wasm-runtime": "^1.1.1", 1127 + "@tybys/wasm-util": "^0.10.1", 1128 + "tslib": "^2.8.1" 1129 + }, 1130 + "engines": { 1131 + "node": ">=14.0.0" 1132 + } 1133 + }, 1134 + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { 1135 + "version": "4.2.1", 1136 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", 1137 + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", 1138 + "cpu": [ 1139 + "arm64" 1140 + ], 1141 + "license": "MIT", 1142 + "optional": true, 1143 + "os": [ 1144 + "win32" 1145 + ], 1146 + "engines": { 1147 + "node": ">= 20" 1148 + } 1149 + }, 1150 + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { 1151 + "version": "4.2.1", 1152 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", 1153 + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", 1154 + "cpu": [ 1155 + "x64" 1156 + ], 1157 + "license": "MIT", 1158 + "optional": true, 1159 + "os": [ 1160 + "win32" 1161 + ], 1162 + "engines": { 1163 + "node": ">= 20" 1164 + } 1165 + }, 1166 + "node_modules/@tailwindcss/vite": { 1167 + "version": "4.2.1", 1168 + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", 1169 + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", 1170 + "license": "MIT", 1171 + "dependencies": { 1172 + "@tailwindcss/node": "4.2.1", 1173 + "@tailwindcss/oxide": "4.2.1", 1174 + "tailwindcss": "4.2.1" 1175 + }, 1176 + "peerDependencies": { 1177 + "vite": "^5.2.0 || ^6 || ^7" 1178 + } 1179 + }, 932 1180 "node_modules/@types/estree": { 933 1181 "version": "1.0.8", 934 1182 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 935 1183 "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 936 - "dev": true, 937 1184 "license": "MIT" 938 1185 }, 939 1186 "node_modules/@types/trusted-types": { ··· 1024 1271 "license": "MIT", 1025 1272 "engines": { 1026 1273 "node": ">=0.10.0" 1274 + } 1275 + }, 1276 + "node_modules/detect-libc": { 1277 + "version": "2.1.2", 1278 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1279 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1280 + "license": "Apache-2.0", 1281 + "engines": { 1282 + "node": ">=8" 1027 1283 } 1028 1284 }, 1029 1285 "node_modules/devalue": { ··· 1033 1289 "dev": true, 1034 1290 "license": "MIT" 1035 1291 }, 1292 + "node_modules/enhanced-resolve": { 1293 + "version": "5.20.0", 1294 + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", 1295 + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", 1296 + "license": "MIT", 1297 + "dependencies": { 1298 + "graceful-fs": "^4.2.4", 1299 + "tapable": "^2.3.0" 1300 + }, 1301 + "engines": { 1302 + "node": ">=10.13.0" 1303 + } 1304 + }, 1036 1305 "node_modules/esbuild": { 1037 1306 "version": "0.27.4", 1038 1307 "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", 1039 1308 "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", 1040 - "dev": true, 1041 1309 "hasInstallScript": true, 1042 1310 "license": "MIT", 1043 1311 "bin": { ··· 1076 1344 } 1077 1345 }, 1078 1346 "node_modules/esbuild/node_modules/@esbuild/netbsd-arm64": { 1079 - "dev": true, 1080 1347 "optional": true 1081 1348 }, 1082 1349 "node_modules/esm-env": { ··· 1101 1368 "version": "6.5.0", 1102 1369 "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 1103 1370 "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 1104 - "dev": true, 1105 1371 "license": "MIT", 1106 1372 "engines": { 1107 1373 "node": ">=12.0.0" ··· 1119 1385 "version": "2.3.3", 1120 1386 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1121 1387 "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1122 - "dev": true, 1123 1388 "hasInstallScript": true, 1124 1389 "license": "MIT", 1125 1390 "optional": true, ··· 1130 1395 "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1131 1396 } 1132 1397 }, 1398 + "node_modules/graceful-fs": { 1399 + "version": "4.2.11", 1400 + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 1401 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 1402 + "license": "ISC" 1403 + }, 1133 1404 "node_modules/is-reference": { 1134 1405 "version": "3.0.3", 1135 1406 "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", ··· 1140 1411 "@types/estree": "^1.0.6" 1141 1412 } 1142 1413 }, 1414 + "node_modules/jiti": { 1415 + "version": "2.6.1", 1416 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", 1417 + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", 1418 + "license": "MIT", 1419 + "bin": { 1420 + "jiti": "lib/jiti-cli.mjs" 1421 + } 1422 + }, 1423 + "node_modules/lightningcss": { 1424 + "version": "1.31.1", 1425 + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", 1426 + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", 1427 + "license": "MPL-2.0", 1428 + "dependencies": { 1429 + "detect-libc": "^2.0.3" 1430 + }, 1431 + "engines": { 1432 + "node": ">= 12.0.0" 1433 + }, 1434 + "funding": { 1435 + "type": "opencollective", 1436 + "url": "https://opencollective.com/parcel" 1437 + }, 1438 + "optionalDependencies": { 1439 + "lightningcss-android-arm64": "1.31.1", 1440 + "lightningcss-darwin-arm64": "1.31.1", 1441 + "lightningcss-darwin-x64": "1.31.1", 1442 + "lightningcss-freebsd-x64": "1.31.1", 1443 + "lightningcss-linux-arm-gnueabihf": "1.31.1", 1444 + "lightningcss-linux-arm64-gnu": "1.31.1", 1445 + "lightningcss-linux-arm64-musl": "1.31.1", 1446 + "lightningcss-linux-x64-gnu": "1.31.1", 1447 + "lightningcss-linux-x64-musl": "1.31.1", 1448 + "lightningcss-win32-arm64-msvc": "1.31.1", 1449 + "lightningcss-win32-x64-msvc": "1.31.1" 1450 + } 1451 + }, 1452 + "node_modules/lightningcss-android-arm64": { 1453 + "version": "1.31.1", 1454 + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", 1455 + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", 1456 + "cpu": [ 1457 + "arm64" 1458 + ], 1459 + "license": "MPL-2.0", 1460 + "optional": true, 1461 + "os": [ 1462 + "android" 1463 + ], 1464 + "engines": { 1465 + "node": ">= 12.0.0" 1466 + }, 1467 + "funding": { 1468 + "type": "opencollective", 1469 + "url": "https://opencollective.com/parcel" 1470 + } 1471 + }, 1472 + "node_modules/lightningcss-darwin-arm64": { 1473 + "version": "1.31.1", 1474 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", 1475 + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", 1476 + "cpu": [ 1477 + "arm64" 1478 + ], 1479 + "license": "MPL-2.0", 1480 + "optional": true, 1481 + "os": [ 1482 + "darwin" 1483 + ], 1484 + "engines": { 1485 + "node": ">= 12.0.0" 1486 + }, 1487 + "funding": { 1488 + "type": "opencollective", 1489 + "url": "https://opencollective.com/parcel" 1490 + } 1491 + }, 1492 + "node_modules/lightningcss-darwin-x64": { 1493 + "version": "1.31.1", 1494 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", 1495 + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", 1496 + "cpu": [ 1497 + "x64" 1498 + ], 1499 + "license": "MPL-2.0", 1500 + "optional": true, 1501 + "os": [ 1502 + "darwin" 1503 + ], 1504 + "engines": { 1505 + "node": ">= 12.0.0" 1506 + }, 1507 + "funding": { 1508 + "type": "opencollective", 1509 + "url": "https://opencollective.com/parcel" 1510 + } 1511 + }, 1512 + "node_modules/lightningcss-freebsd-x64": { 1513 + "version": "1.31.1", 1514 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", 1515 + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", 1516 + "cpu": [ 1517 + "x64" 1518 + ], 1519 + "license": "MPL-2.0", 1520 + "optional": true, 1521 + "os": [ 1522 + "freebsd" 1523 + ], 1524 + "engines": { 1525 + "node": ">= 12.0.0" 1526 + }, 1527 + "funding": { 1528 + "type": "opencollective", 1529 + "url": "https://opencollective.com/parcel" 1530 + } 1531 + }, 1532 + "node_modules/lightningcss-linux-arm-gnueabihf": { 1533 + "version": "1.31.1", 1534 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", 1535 + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", 1536 + "cpu": [ 1537 + "arm" 1538 + ], 1539 + "license": "MPL-2.0", 1540 + "optional": true, 1541 + "os": [ 1542 + "linux" 1543 + ], 1544 + "engines": { 1545 + "node": ">= 12.0.0" 1546 + }, 1547 + "funding": { 1548 + "type": "opencollective", 1549 + "url": "https://opencollective.com/parcel" 1550 + } 1551 + }, 1552 + "node_modules/lightningcss-linux-arm64-gnu": { 1553 + "version": "1.31.1", 1554 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", 1555 + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", 1556 + "cpu": [ 1557 + "arm64" 1558 + ], 1559 + "libc": [ 1560 + "glibc" 1561 + ], 1562 + "license": "MPL-2.0", 1563 + "optional": true, 1564 + "os": [ 1565 + "linux" 1566 + ], 1567 + "engines": { 1568 + "node": ">= 12.0.0" 1569 + }, 1570 + "funding": { 1571 + "type": "opencollective", 1572 + "url": "https://opencollective.com/parcel" 1573 + } 1574 + }, 1575 + "node_modules/lightningcss-linux-arm64-musl": { 1576 + "version": "1.31.1", 1577 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", 1578 + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", 1579 + "cpu": [ 1580 + "arm64" 1581 + ], 1582 + "libc": [ 1583 + "musl" 1584 + ], 1585 + "license": "MPL-2.0", 1586 + "optional": true, 1587 + "os": [ 1588 + "linux" 1589 + ], 1590 + "engines": { 1591 + "node": ">= 12.0.0" 1592 + }, 1593 + "funding": { 1594 + "type": "opencollective", 1595 + "url": "https://opencollective.com/parcel" 1596 + } 1597 + }, 1598 + "node_modules/lightningcss-linux-x64-gnu": { 1599 + "version": "1.31.1", 1600 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", 1601 + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", 1602 + "cpu": [ 1603 + "x64" 1604 + ], 1605 + "libc": [ 1606 + "glibc" 1607 + ], 1608 + "license": "MPL-2.0", 1609 + "optional": true, 1610 + "os": [ 1611 + "linux" 1612 + ], 1613 + "engines": { 1614 + "node": ">= 12.0.0" 1615 + }, 1616 + "funding": { 1617 + "type": "opencollective", 1618 + "url": "https://opencollective.com/parcel" 1619 + } 1620 + }, 1621 + "node_modules/lightningcss-linux-x64-musl": { 1622 + "version": "1.31.1", 1623 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", 1624 + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", 1625 + "cpu": [ 1626 + "x64" 1627 + ], 1628 + "libc": [ 1629 + "musl" 1630 + ], 1631 + "license": "MPL-2.0", 1632 + "optional": true, 1633 + "os": [ 1634 + "linux" 1635 + ], 1636 + "engines": { 1637 + "node": ">= 12.0.0" 1638 + }, 1639 + "funding": { 1640 + "type": "opencollective", 1641 + "url": "https://opencollective.com/parcel" 1642 + } 1643 + }, 1644 + "node_modules/lightningcss-win32-arm64-msvc": { 1645 + "version": "1.31.1", 1646 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", 1647 + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", 1648 + "cpu": [ 1649 + "arm64" 1650 + ], 1651 + "license": "MPL-2.0", 1652 + "optional": true, 1653 + "os": [ 1654 + "win32" 1655 + ], 1656 + "engines": { 1657 + "node": ">= 12.0.0" 1658 + }, 1659 + "funding": { 1660 + "type": "opencollective", 1661 + "url": "https://opencollective.com/parcel" 1662 + } 1663 + }, 1664 + "node_modules/lightningcss-win32-x64-msvc": { 1665 + "version": "1.31.1", 1666 + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", 1667 + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", 1668 + "cpu": [ 1669 + "x64" 1670 + ], 1671 + "license": "MPL-2.0", 1672 + "optional": true, 1673 + "os": [ 1674 + "win32" 1675 + ], 1676 + "engines": { 1677 + "node": ">= 12.0.0" 1678 + }, 1679 + "funding": { 1680 + "type": "opencollective", 1681 + "url": "https://opencollective.com/parcel" 1682 + } 1683 + }, 1143 1684 "node_modules/locate-character": { 1144 1685 "version": "3.0.0", 1145 1686 "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", ··· 1151 1692 "version": "0.30.21", 1152 1693 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", 1153 1694 "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", 1154 - "dev": true, 1155 1695 "license": "MIT", 1156 1696 "dependencies": { 1157 1697 "@jridgewell/sourcemap-codec": "^1.5.5" ··· 1171 1711 "version": "3.3.11", 1172 1712 "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 1173 1713 "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 1174 - "dev": true, 1175 1714 "funding": [ 1176 1715 { 1177 1716 "type": "github", ··· 1201 1740 "version": "1.1.1", 1202 1741 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1203 1742 "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1204 - "dev": true, 1205 1743 "license": "ISC" 1206 1744 }, 1207 1745 "node_modules/picomatch": { 1208 1746 "version": "4.0.3", 1209 1747 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 1210 1748 "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 1211 - "dev": true, 1212 1749 "license": "MIT", 1213 1750 "engines": { 1214 1751 "node": ">=12" ··· 1221 1758 "version": "8.5.8", 1222 1759 "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", 1223 1760 "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", 1224 - "dev": true, 1225 1761 "funding": [ 1226 1762 { 1227 1763 "type": "opencollective", ··· 1264 1800 "version": "4.59.0", 1265 1801 "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", 1266 1802 "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", 1267 - "dev": true, 1268 1803 "license": "MIT", 1269 1804 "dependencies": { 1270 1805 "@types/estree": "1.0.8" ··· 1322 1857 "version": "1.2.1", 1323 1858 "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1324 1859 "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1325 - "dev": true, 1326 1860 "license": "BSD-3-Clause", 1327 1861 "engines": { 1328 1862 "node": ">=0.10.0" ··· 1380 1914 "typescript": ">=5.0.0" 1381 1915 } 1382 1916 }, 1917 + "node_modules/tailwindcss": { 1918 + "version": "4.2.1", 1919 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", 1920 + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", 1921 + "license": "MIT" 1922 + }, 1923 + "node_modules/tapable": { 1924 + "version": "2.3.0", 1925 + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", 1926 + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", 1927 + "license": "MIT", 1928 + "engines": { 1929 + "node": ">=6" 1930 + }, 1931 + "funding": { 1932 + "type": "opencollective", 1933 + "url": "https://opencollective.com/webpack" 1934 + } 1935 + }, 1383 1936 "node_modules/tinyglobby": { 1384 1937 "version": "0.2.15", 1385 1938 "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", 1386 1939 "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", 1387 - "dev": true, 1388 1940 "license": "MIT", 1389 1941 "dependencies": { 1390 1942 "fdir": "^6.5.0", ··· 1401 1953 "version": "2.8.1", 1402 1954 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 1403 1955 "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 1404 - "dev": true, 1956 + "devOptional": true, 1405 1957 "license": "0BSD" 1406 1958 }, 1407 1959 "node_modules/typescript": { ··· 1422 1974 "version": "7.3.1", 1423 1975 "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", 1424 1976 "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", 1425 - "dev": true, 1426 1977 "license": "MIT", 1427 1978 "dependencies": { 1428 1979 "esbuild": "^0.27.0",
+7
frontend/package.json
··· 16 16 "tslib": "^2.8.1", 17 17 "typescript": "^5.9.3", 18 18 "vite": "^7.2.6" 19 + }, 20 + "dependencies": { 21 + "@fontsource-variable/geist": "^5.2.8", 22 + "@fontsource-variable/jetbrains-mono": "^5.2.8", 23 + "@fontsource-variable/lora": "^5.2.8", 24 + "@tailwindcss/vite": "^4.2.1", 25 + "tailwindcss": "^4.2.1" 19 26 } 20 27 }
+8 -78
frontend/src/App.svelte
··· 1 1 <script lang="ts"> 2 - import logo from "./assets/images/logo-universal.png"; 3 - import { Greet } from "../wailsjs/go/main/App.js"; 4 - 5 - let resultText: string = "Please enter your name below 👇"; 6 - let name: string; 7 - 8 - function greet(): void { 9 - Greet(name).then((result) => (resultText = result)); 10 - } 2 + import "@fontsource-variable/jetbrains-mono"; 3 + import "@fontsource-variable/geist"; 4 + import "@fontsource-variable/lora"; 11 5 </script> 12 6 13 - <main> 14 - <img alt="Wails logo" id="logo" src={logo} /> 15 - <div class="result" id="result">{resultText}</div> 16 - <div class="input-box" id="input"> 17 - <input 18 - autocomplete="off" 19 - bind:value={name} 20 - class="input" 21 - id="name" 22 - type="text" 23 - /> 24 - <button class="btn" onclick={greet}>Greet</button> 7 + <main class="min-h-screen bg-black text-[#e5e5e5] flex items-center justify-center"> 8 + <div class="text-center"> 9 + <h1 class="font-serif text-4xl mb-4">Hello World</h1> 10 + <p class="font-mono text-[#737373]">bsky-browser-desktop</p> 11 + <p class="font-sans text-sm text-[#737373] mt-2">Built with Wails v3 + Tailwind v4</p> 25 12 </div> 26 13 </main> 27 - 28 - <style> 29 - #logo { 30 - display: block; 31 - width: 50%; 32 - height: 50%; 33 - margin: auto; 34 - padding: 10% 0 0; 35 - background-position: center; 36 - background-repeat: no-repeat; 37 - background-size: 100% 100%; 38 - background-origin: content-box; 39 - } 40 - 41 - .result { 42 - height: 20px; 43 - line-height: 20px; 44 - margin: 1.5rem auto; 45 - } 46 - 47 - .input-box .btn { 48 - width: 60px; 49 - height: 30px; 50 - line-height: 30px; 51 - border-radius: 3px; 52 - border: none; 53 - margin: 0 0 0 20px; 54 - padding: 0 8px; 55 - cursor: pointer; 56 - } 57 - 58 - .input-box .btn:hover { 59 - background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); 60 - color: #333333; 61 - } 62 - 63 - .input-box .input { 64 - border: none; 65 - border-radius: 3px; 66 - outline: none; 67 - height: 30px; 68 - line-height: 30px; 69 - padding: 0 10px; 70 - background-color: rgba(240, 240, 240, 1); 71 - -webkit-font-smoothing: antialiased; 72 - } 73 - 74 - .input-box .input:hover { 75 - border: none; 76 - background-color: rgba(255, 255, 255, 1); 77 - } 78 - 79 - .input-box .input:focus { 80 - border: none; 81 - background-color: rgba(255, 255, 255, 1); 82 - } 83 - </style>
+21
frontend/src/index.css
··· 1 + @import "tailwindcss"; 2 + 3 + @theme { 4 + --font-mono: "JetBrains Mono Variable", monospace; 5 + --font-sans: "Geist Variable", sans-serif; 6 + --font-serif: "Lora Variable", serif; 7 + --color-surface: #0a0a0a; 8 + --color-border: #1a1a1a; 9 + } 10 + 11 + html { 12 + background-color: #000000; 13 + color: #e5e5e5; 14 + } 15 + 16 + body { 17 + margin: 0; 18 + font-family: var(--font-sans); 19 + -webkit-font-smoothing: antialiased; 20 + -moz-osx-font-smoothing: grayscale; 21 + }
+1 -1
frontend/src/main.ts
··· 1 - import './style.css' 1 + import './index.css' 2 2 import App from './App.svelte' 3 3 import { mount } from 'svelte' 4 4
+2 -1
frontend/vite.config.ts
··· 1 1 import {defineConfig} from 'vite' 2 2 import {svelte} from '@sveltejs/vite-plugin-svelte' 3 + import tailwindcss from '@tailwindcss/vite' 3 4 4 5 // https://vitejs.dev/config/ 5 6 export default defineConfig({ 6 - plugins: [svelte()] 7 + plugins: [tailwindcss(), svelte()] 7 8 })
+12 -2
go.mod
··· 1 1 module bsky-browser-gui 2 2 3 - go 1.23 3 + go 1.24.0 4 + 5 + toolchain go1.24.11 4 6 5 7 require github.com/wailsapp/wails/v2 v2.11.0 6 8 7 9 require ( 8 10 github.com/bep/debounce v1.2.1 // indirect 11 + github.com/dustin/go-humanize v1.0.1 // indirect 9 12 github.com/go-ole/go-ole v1.3.0 // indirect 10 13 github.com/godbus/dbus/v5 v5.1.0 // indirect 11 14 github.com/google/uuid v1.6.0 // indirect ··· 19 22 github.com/leaanthony/u v1.1.1 // indirect 20 23 github.com/mattn/go-colorable v0.1.13 // indirect 21 24 github.com/mattn/go-isatty v0.0.20 // indirect 25 + github.com/ncruces/go-strftime v1.0.0 // indirect 22 26 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect 23 27 github.com/pkg/errors v0.9.1 // indirect 28 + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 24 29 github.com/rivo/uniseg v0.4.7 // indirect 25 30 github.com/samber/lo v1.49.1 // indirect 26 31 github.com/tkrajina/go-reflector v0.5.8 // indirect ··· 29 34 github.com/wailsapp/go-webview2 v1.0.22 // indirect 30 35 github.com/wailsapp/mimetype v1.4.1 // indirect 31 36 golang.org/x/crypto v0.33.0 // indirect 37 + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect 32 38 golang.org/x/net v0.35.0 // indirect 33 - golang.org/x/sys v0.30.0 // indirect 39 + golang.org/x/sys v0.37.0 // indirect 34 40 golang.org/x/text v0.22.0 // indirect 41 + modernc.org/libc v1.67.6 // indirect 42 + modernc.org/mathutil v1.7.1 // indirect 43 + modernc.org/memory v1.11.0 // indirect 44 + modernc.org/sqlite v1.46.1 // indirect 35 45 ) 36 46 37 47 // replace github.com/wailsapp/wails/v2 v2.11.0 => /Users/owais/go/pkg/mod
+18
go.sum
··· 2 2 github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= 3 3 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 4 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 + github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 6 + github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 5 7 github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 6 8 github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 7 9 github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= ··· 34 36 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 35 37 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 36 38 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 39 + github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= 40 + github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= 37 41 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= 38 42 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 39 43 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 40 44 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 41 45 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 42 46 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 47 + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 48 + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 43 49 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 44 50 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 45 51 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= ··· 61 67 github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k= 62 68 golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 63 69 golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 70 + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= 71 + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= 64 72 golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 65 73 golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= 66 74 golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= ··· 72 80 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 73 81 golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 74 82 golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 83 + golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= 84 + golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 75 85 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 76 86 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 77 87 golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= ··· 79 89 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 80 90 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 81 91 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 92 + modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= 93 + modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= 94 + modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= 95 + modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= 96 + modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= 97 + modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= 98 + modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= 99 + modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
+6 -12
main.go
··· 12 12 var assets embed.FS 13 13 14 14 func main() { 15 - // Create an instance of the app structure 16 15 app := NewApp() 17 - 18 - // Create application with options 19 16 err := wails.Run(&options.App{ 20 - Title: "bsky-browser-gui", 21 - Width: 1024, 22 - Height: 768, 23 - AssetServer: &assetserver.Options{ 24 - Assets: assets, 25 - }, 17 + Title: "bsky-browser-gui", 18 + Width: 1024, 19 + Height: 768, 20 + AssetServer: &assetserver.Options{Assets: assets}, 26 21 BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, 27 22 OnStartup: app.startup, 28 - Bind: []interface{}{ 29 - app, 30 - }, 23 + OnShutdown: app.shutdown, 24 + Bind: []any{app}, 31 25 }) 32 26 33 27 if err != nil {
+62
migrations/000_initial_schema.sql
··· 1 + -- Combined initial schema for bsky-browser-desktop 2 + -- Includes all migrations: auth, posts, FTS5, OAuth fields, and facets 3 + 4 + -- Auth table with all OAuth fields 5 + CREATE TABLE IF NOT EXISTS auth ( 6 + did TEXT PRIMARY KEY, 7 + handle TEXT NOT NULL, 8 + access_jwt TEXT NOT NULL, 9 + refresh_jwt TEXT NOT NULL, 10 + pds_url TEXT NOT NULL, 11 + session_id TEXT, 12 + auth_server_url TEXT, 13 + auth_server_token_endpoint TEXT, 14 + auth_server_revocation_endpoint TEXT, 15 + dpop_auth_nonce TEXT, 16 + dpop_host_nonce TEXT, 17 + dpop_private_key TEXT, 18 + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP 19 + ); 20 + 21 + -- Posts table with facets support 22 + CREATE TABLE IF NOT EXISTS posts ( 23 + uri TEXT PRIMARY KEY, 24 + cid TEXT NOT NULL, 25 + author_did TEXT NOT NULL, 26 + author_handle TEXT NOT NULL, 27 + text TEXT NOT NULL DEFAULT '', 28 + created_at DATETIME NOT NULL, 29 + like_count INTEGER DEFAULT 0, 30 + repost_count INTEGER DEFAULT 0, 31 + reply_count INTEGER DEFAULT 0, 32 + source TEXT NOT NULL CHECK(source IN ('saved', 'liked')), 33 + facets TEXT, 34 + indexed_at DATETIME DEFAULT CURRENT_TIMESTAMP 35 + ); 36 + 37 + -- FTS5 virtual table for full-text search 38 + CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts USING fts5( 39 + text, 40 + author_handle, 41 + content='posts', 42 + content_rowid='rowid', 43 + tokenize='unicode61' 44 + ); 45 + 46 + -- Triggers to keep FTS5 index in sync with posts table 47 + CREATE TRIGGER IF NOT EXISTS posts_ai AFTER INSERT ON posts BEGIN 48 + INSERT INTO posts_fts(rowid, text, author_handle) 49 + VALUES (new.rowid, new.text, new.author_handle); 50 + END; 51 + 52 + CREATE TRIGGER IF NOT EXISTS posts_ad AFTER DELETE ON posts BEGIN 53 + INSERT INTO posts_fts(posts_fts, rowid, text, author_handle) 54 + VALUES ('delete', old.rowid, old.text, old.author_handle); 55 + END; 56 + 57 + CREATE TRIGGER IF NOT EXISTS posts_au AFTER UPDATE ON posts BEGIN 58 + INSERT INTO posts_fts(posts_fts, rowid, text, author_handle) 59 + VALUES ('delete', old.rowid, old.text, old.author_handle); 60 + INSERT INTO posts_fts(rowid, text, author_handle) 61 + VALUES (new.rowid, new.text, new.author_handle); 62 + END;
+44
models.go
··· 1 + package main 2 + 3 + import ( 4 + "time" 5 + ) 6 + 7 + // Post represents a Bluesky post in the database 8 + type Post struct { 9 + URI string `json:"uri"` 10 + CID string `json:"cid"` 11 + AuthorDID string `json:"author_did"` 12 + AuthorHandle string `json:"author_handle"` 13 + Text string `json:"text"` 14 + CreatedAt time.Time `json:"created_at"` 15 + LikeCount int `json:"like_count"` 16 + RepostCount int `json:"repost_count"` 17 + ReplyCount int `json:"reply_count"` 18 + Source string `json:"source"` // 'saved' or 'liked' 19 + Facets string `json:"facets"` // JSON-encoded facets 20 + IndexedAt time.Time `json:"indexed_at"` 21 + } 22 + 23 + // Auth represents OAuth authentication information 24 + type Auth struct { 25 + DID string `json:"did"` 26 + Handle string `json:"handle"` 27 + AccessJWT string `json:"access_jwt"` 28 + RefreshJWT string `json:"refresh_jwt"` 29 + PDSURL string `json:"pds_url"` 30 + SessionID string `json:"session_id"` 31 + AuthServerURL string `json:"auth_server_url"` 32 + AuthServerTokenEndpoint string `json:"auth_server_token_endpoint"` 33 + AuthServerRevocationEndpoint string `json:"auth_server_revocation_endpoint"` 34 + DPoPAuthNonce string `json:"dpop_auth_nonce"` 35 + DPoPHostNonce string `json:"dpop_host_nonce"` 36 + DPoPPrivateKey string `json:"dpop_private_key"` 37 + UpdatedAt time.Time `json:"updated_at"` 38 + } 39 + 40 + // SearchResult represents a search result with BM25 ranking 41 + type SearchResult struct { 42 + Post 43 + Rank float64 `json:"rank"` 44 + }