browse the protocol like its 2008 ibex.desertthunder.dev
ubuntu atproto svelte
7

Configure Feed

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

feat: document viewer

Owais Jamil (Jun 9, 2026, 2:14 PM -0500) bab2c1c0 0b31680e

+713 -80
+1 -39
CHANGELOG.md
··· 1 - # Changelog 2 - 3 - ## [Unreleased] 4 - 5 - Target release: `v1.0.0`. 6 - 7 - ### 2026-06-09 8 - 9 - #### Added 10 - 11 - - PGlite-backed local cache/search foundation with WASM build support. 12 - - Transactional schema migrations for accounts, cached records, collection 13 - sync state, and record search. 14 - - Ubuntu-purple boot splash that initializes PGlite, runs migrations, and offers 15 - recovery actions. 16 - - Cache-aware AT Protocol browsing: live record fetches populate PGlite and offline 17 - failures can fall back to cached collections/records. 18 - - Full-text cache search in the Nautilus-style collection browser. 19 - - Atmosphere app logo mapping for known AT Protocol collection namespaces. 20 - - Cursor-based record pagination with a generator-backed “Load more records” flow. 21 - 22 - #### Changed 23 - 24 - - Grouped Nautilus collections by top-level namespace folders. 25 - 26 - ### 2026-06-08 27 - 28 - #### Added 29 - 30 - - Ubuntu 8.10 / GNOME 2-inspired desktop shell, panel, wallpaper, windows, and Humanity 31 - icon styling. 32 - - First-run AT Protocol account setup flow with handle resolution and persisted account 33 - identity. 34 - - Nautilus-style collection browser backed by live AT Protocol repository collection and 35 - record loading. 36 - - gedit-style JSON record viewer opened from selected records. 37 - - Window management basics including movable, resizable, minimizable, maximizable, and 38 - closable windows. 39 - - About Computer dialog, desktop icons, sticky note, and Tangled project links. 1 + src/routes/docs/CHANGELOG.md
+1
package.json
··· 28 28 "eslint-config-prettier": "^10.1.8", 29 29 "eslint-plugin-svelte": "^3.17.0", 30 30 "globals": "^17.4.0", 31 + "mdsvex": "^0.12.7", 31 32 "playwright": "^1.60.0", 32 33 "prettier": "^3.8.1", 33 34 "prettier-plugin-svelte": "^3.5.1",
+71
pnpm-lock.yaml
··· 72 72 globals: 73 73 specifier: ^17.4.0 74 74 version: 17.6.0 75 + mdsvex: 76 + specifier: ^0.12.7 77 + version: 0.12.7(svelte@5.56.3(@typescript-eslint/types@8.61.0)) 75 78 playwright: 76 79 specifier: ^1.60.0 77 80 version: 1.60.0 ··· 449 452 450 453 '@types/trusted-types@2.0.7': 451 454 resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} 455 + 456 + '@types/unist@2.0.11': 457 + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} 452 458 453 459 '@types/unist@3.0.3': 454 460 resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} ··· 957 963 mdast-util-to-hast@13.2.1: 958 964 resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} 959 965 966 + mdsvex@0.12.7: 967 + resolution: {integrity: sha512-gx4bReLCUvq+MPErHXYeyX+TEq1hsS2KfiZtEOMNTcbibSouFy8AHc5h04KbGCl+g5tLuo4/lbgRVYRnc7bJZw==} 968 + peerDependencies: 969 + svelte: ^3.56.0 || ^4.0.0 || ^5.0.0-next.120 970 + 960 971 micromark-util-character@2.1.1: 961 972 resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} 962 973 ··· 1095 1106 resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} 1096 1107 engines: {node: '>=14'} 1097 1108 hasBin: true 1109 + 1110 + prism-svelte@0.4.7: 1111 + resolution: {integrity: sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ==} 1112 + 1113 + prismjs@1.30.0: 1114 + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} 1115 + engines: {node: '>=6'} 1098 1116 1099 1117 property-information@7.2.0: 1100 1118 resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} ··· 1242 1260 unicode-segmenter@0.14.5: 1243 1261 resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==} 1244 1262 1263 + unist-util-is@4.1.0: 1264 + resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} 1265 + 1245 1266 unist-util-is@6.0.1: 1246 1267 resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} 1247 1268 1248 1269 unist-util-position@5.0.0: 1249 1270 resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} 1250 1271 1272 + unist-util-stringify-position@2.0.3: 1273 + resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} 1274 + 1251 1275 unist-util-stringify-position@4.0.0: 1252 1276 resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} 1253 1277 1278 + unist-util-visit-parents@3.1.1: 1279 + resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} 1280 + 1254 1281 unist-util-visit-parents@6.0.2: 1255 1282 resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} 1283 + 1284 + unist-util-visit@2.0.3: 1285 + resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} 1256 1286 1257 1287 unist-util-visit@5.1.0: 1258 1288 resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} ··· 1270 1300 peerDependenciesMeta: 1271 1301 typescript: 1272 1302 optional: true 1303 + 1304 + vfile-message@2.0.4: 1305 + resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} 1273 1306 1274 1307 vfile-message@4.0.3: 1275 1308 resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} ··· 1726 1759 undici-types: 7.18.2 1727 1760 1728 1761 '@types/trusted-types@2.0.7': {} 1762 + 1763 + '@types/unist@2.0.11': {} 1729 1764 1730 1765 '@types/unist@3.0.3': {} 1731 1766 ··· 2251 2286 unist-util-visit: 5.1.0 2252 2287 vfile: 6.0.3 2253 2288 2289 + mdsvex@0.12.7(svelte@5.56.3(@typescript-eslint/types@8.61.0)): 2290 + dependencies: 2291 + '@types/mdast': 4.0.4 2292 + '@types/unist': 2.0.11 2293 + prism-svelte: 0.4.7 2294 + prismjs: 1.30.0 2295 + svelte: 5.56.3(@typescript-eslint/types@8.61.0) 2296 + unist-util-visit: 2.0.3 2297 + vfile-message: 2.0.4 2298 + 2254 2299 micromark-util-character@2.1.1: 2255 2300 dependencies: 2256 2301 micromark-util-symbol: 2.0.1 ··· 2363 2408 svelte: 5.56.3(@typescript-eslint/types@8.61.0) 2364 2409 2365 2410 prettier@3.8.3: {} 2411 + 2412 + prism-svelte@0.4.7: {} 2413 + 2414 + prismjs@1.30.0: {} 2366 2415 2367 2416 property-information@7.2.0: {} 2368 2417 ··· 2536 2585 2537 2586 unicode-segmenter@0.14.5: {} 2538 2587 2588 + unist-util-is@4.1.0: {} 2589 + 2539 2590 unist-util-is@6.0.1: 2540 2591 dependencies: 2541 2592 '@types/unist': 3.0.3 ··· 2544 2595 dependencies: 2545 2596 '@types/unist': 3.0.3 2546 2597 2598 + unist-util-stringify-position@2.0.3: 2599 + dependencies: 2600 + '@types/unist': 2.0.11 2601 + 2547 2602 unist-util-stringify-position@4.0.0: 2548 2603 dependencies: 2549 2604 '@types/unist': 3.0.3 2550 2605 2606 + unist-util-visit-parents@3.1.1: 2607 + dependencies: 2608 + '@types/unist': 2.0.11 2609 + unist-util-is: 4.1.0 2610 + 2551 2611 unist-util-visit-parents@6.0.2: 2552 2612 dependencies: 2553 2613 '@types/unist': 3.0.3 2554 2614 unist-util-is: 6.0.1 2555 2615 2616 + unist-util-visit@2.0.3: 2617 + dependencies: 2618 + '@types/unist': 2.0.11 2619 + unist-util-is: 4.1.0 2620 + unist-util-visit-parents: 3.1.1 2621 + 2556 2622 unist-util-visit@5.1.0: 2557 2623 dependencies: 2558 2624 '@types/unist': 3.0.3 ··· 2568 2634 valibot@1.4.1(typescript@6.0.3): 2569 2635 optionalDependencies: 2570 2636 typescript: 6.0.3 2637 + 2638 + vfile-message@2.0.4: 2639 + dependencies: 2640 + '@types/unist': 2.0.11 2641 + unist-util-stringify-position: 2.0.3 2571 2642 2572 2643 vfile-message@4.0.3: 2573 2644 dependencies:
+11 -19
src/lib/atproto/repo.svelte.ts
··· 1 1 import { Client, ok, simpleFetchHandler } from '@atcute/client'; 2 2 import type { ActorIdentifier } from '@atcute/lexicons/syntax'; 3 3 import type {} from '@atcute/atproto'; 4 - import { 5 - cacheFetchedRecords, 6 - getDatabase, 7 - listCachedCollections, 8 - listCachedRecords, 9 - searchCachedRecords, 10 - updateCollectionSyncState, 11 - type CachedRecord, 12 - type CachedRecordInput 13 - } from '$lib/db'; 4 + import type { CachedRecord, CachedRecordInput } from '$lib/db'; 14 5 import { errorMessage } from '$lib/utils/errors'; 15 6 import { appLabelForCollection, collectionIconMatch } from './collection-icons'; 16 7 import { listRecordPages, type RecordPage } from './pagination'; 17 - import { 18 - isRecordValue, 19 - type AccountIdentity, 20 - type CollectionSummary, 21 - type RepoRecordSummary, 22 - type UnknownRecord 23 - } from './types'; 8 + import { isRecordValue } from './types'; 9 + import type { AccountIdentity, CollectionSummary, RepoRecordSummary, UnknownRecord } from './types'; 24 10 25 11 export type { CollectionSummary, RepoRecordSummary } from './types'; 26 12 ··· 125 111 126 112 async loadCollectionsFromCache(identity: AccountIdentity) { 127 113 try { 114 + const { getDatabase, listCachedCollections } = await import('$lib/db'); 128 115 const db = await getDatabase(); 129 116 const collections = await listCachedCollections(db, identity.did); 130 117 131 118 if (collections.length === 0) return false; 132 119 133 - this.collections = collections.map((collection) => collectionSummaryForName(collection.name, collection.loadedCount)); 120 + this.collections = collections.map((collection) => 121 + collectionSummaryForName(collection.name, collection.loadedCount) 122 + ); 134 123 this.selectedCollection = preferredCollection(this.collections.map((collection) => collection.name)); 135 124 136 125 if (this.selectedCollection) { ··· 160 149 this.error = null; 161 150 162 151 try { 152 + const { getDatabase, searchCachedRecords } = await import('$lib/db'); 163 153 const db = await getDatabase(); 164 154 const results = await searchCachedRecords(db, normalizedQuery, { 165 155 repoDid: identity.did, ··· 168 158 this.records = results.map((record) => summarizeCachedRecord(record, identity.handle)); 169 159 } catch (searchError) { 170 160 this.records = []; 171 - this.error = errorMessage(searchError, 'Could not search cached records.'); 161 + this.error = errorMessage(searchError, 'Could not search records.'); 172 162 } finally { 173 163 this.isSearching = false; 174 164 } ··· 176 166 177 167 async loadRecordsFromCache(identity: AccountIdentity, collectionName: string) { 178 168 try { 169 + const { getDatabase, listCachedRecords } = await import('$lib/db'); 179 170 const db = await getDatabase(); 180 171 const records = await listCachedRecords(db, { repoDid: identity.did, collection: collectionName, limit: 25 }); 181 172 ··· 269 260 cursor: string | null 270 261 ) { 271 262 try { 263 + const { cacheFetchedRecords, getDatabase, updateCollectionSyncState } = await import('$lib/db'); 272 264 const db = await getDatabase(); 273 265 await cacheFetchedRecords( 274 266 db,
+2 -2
src/lib/components/CollectionBrowser.svelte
··· 149 149 event.preventDefault(); 150 150 searchRecords(); 151 151 }}> 152 - <label for="record-search">Search cache</label> 152 + <label for="record-search">Search</label> 153 153 <div> 154 154 <input 155 155 id="record-search" 156 156 bind:value={searchQuery} 157 - placeholder="Search cached records" 157 + placeholder="Search records" 158 158 disabled={!repoBrowser.selectedCollection || repoBrowser.isLoadingRecords || repoBrowser.isSearching} /> 159 159 <button type="submit" disabled={!searchQuery.trim() || repoBrowser.isSearching}>Search</button> 160 160 {#if repoBrowser.searchQuery}
+140
src/lib/components/DocumentViewer.svelte
··· 1 + <script lang="ts"> 2 + import type { Component } from 'svelte'; 3 + 4 + type DocumentModule = { default: Component; metadata?: { title?: string } }; 5 + 6 + type Props = { slug?: string }; 7 + 8 + let { slug = 'changelog' }: Props = $props(); 9 + 10 + const documents = import.meta.glob('../../routes/docs/*.md', { eager: true }) as Record<string, DocumentModule>; 11 + 12 + const document = $derived(Object.entries(documents).find(([path]) => slugFromPath(path) === slug)?.[1] ?? null); 13 + const Document = $derived(document?.default); 14 + const title = $derived(document?.metadata?.title ?? titleFromSlug(slug)); 15 + 16 + function slugFromPath(path: string) { 17 + return path 18 + .split('/') 19 + .at(-1) 20 + ?.replace(/\.md$/, '') 21 + .toLowerCase() 22 + .replace(/[^a-z0-9]+/g, '-') 23 + .replace(/^-|-$/g, ''); 24 + } 25 + 26 + function titleFromSlug(value: string) { 27 + return value 28 + .split('-') 29 + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 30 + .join(' '); 31 + } 32 + </script> 33 + 34 + <div class="document-viewer"> 35 + {#if Document} 36 + <article class="document-page" aria-label={title}> 37 + <Document /> 38 + </article> 39 + {:else} 40 + <div class="document-missing" role="status"> 41 + <img src="/icons/humanity/mimes/gnome-mime-application-pdf.svg" alt="" width="48" height="48" /> 42 + <h2>Document not found</h2> 43 + <p>Document Viewer could not open “{slug}”.</p> 44 + </div> 45 + {/if} 46 + </div> 47 + 48 + <style> 49 + .document-viewer { 50 + height: 100%; 51 + overflow: auto; 52 + padding: var(--space-5); 53 + color: #24170f; 54 + background: 55 + radial-gradient(circle at 20% 0%, rgb(233 84 32 / 0.08), transparent 18rem), linear-gradient(#d8c2a0, #9b7a51); 56 + } 57 + 58 + .document-page, 59 + .document-missing { 60 + width: min(48rem, 100%); 61 + margin: 0 auto; 62 + padding: clamp(var(--space-5), 5vw, var(--space-8)); 63 + background: #fff8ed; 64 + border: 1px solid #8e6d43; 65 + border-radius: var(--radius-2); 66 + box-shadow: 67 + 0 1px 0 rgb(255 255 255 / 0.8) inset, 68 + 0 1.25rem 2.5rem rgb(0 0 0 / 0.28); 69 + } 70 + 71 + .document-missing { 72 + display: grid; 73 + justify-items: center; 74 + gap: var(--space-2); 75 + text-align: center; 76 + } 77 + 78 + .document-missing h2 { 79 + font-size: var(--text-5); 80 + } 81 + 82 + .document-missing p { 83 + color: var(--text-muted); 84 + } 85 + 86 + .document-page :global(h1) { 87 + font-size: clamp(2rem, 6vw, 3.2rem); 88 + line-height: var(--leading-tight); 89 + } 90 + 91 + .document-page :global(h2) { 92 + margin-top: var(--space-6); 93 + font-size: var(--text-5); 94 + line-height: var(--leading-tight); 95 + } 96 + 97 + .document-page :global(h3) { 98 + margin-top: var(--space-5); 99 + font-size: var(--text-4); 100 + } 101 + 102 + .document-page :global(h4) { 103 + margin-top: var(--space-4); 104 + font-size: var(--text-3); 105 + } 106 + 107 + .document-page :global(p), 108 + .document-page :global(li) { 109 + font-size: var(--text-2); 110 + line-height: var(--leading-roomy); 111 + } 112 + 113 + .document-page :global(p), 114 + .document-page :global(ul), 115 + .document-page :global(ol) { 116 + margin-top: var(--space-3); 117 + } 118 + 119 + .document-page :global(ul), 120 + .document-page :global(ol) { 121 + padding-left: var(--space-5); 122 + } 123 + 124 + .document-page :global(ul) { 125 + list-style: disc; 126 + } 127 + 128 + .document-page :global(ol) { 129 + list-style: decimal; 130 + } 131 + 132 + .document-page :global(code) { 133 + padding: 0.08rem 0.3rem; 134 + font-family: var(--font-mono); 135 + font-size: 0.9em; 136 + background: #ead7b8; 137 + border: 1px solid #d2b991; 138 + border-radius: var(--radius-1); 139 + } 140 + </style>
+11 -2
src/lib/window-manager.svelte.ts
··· 1 - export type WindowId = 'main' | 'about-computer' | 'gedit'; 1 + export type WindowId = 'main' | 'about-computer' | 'gedit' | 'document-viewer'; 2 2 3 3 export type ManagedWindow = { 4 4 id: WindowId; ··· 38 38 isMinimized: false, 39 39 isMaximized: false, 40 40 zIndex: 3 41 + }, 42 + { 43 + id: 'document-viewer', 44 + title: 'CHANGELOG.md - Document Viewer', 45 + icon: '/icons/humanity/mimes/gnome-mime-application-pdf.svg', 46 + isOpen: false, 47 + isMinimized: false, 48 + isMaximized: false, 49 + zIndex: 4 41 50 } 42 51 ]); 43 52 44 - private nextZIndex = 3; 53 + private nextZIndex = 4; 45 54 46 55 get openWindows() { 47 56 return this.windows.filter((window) => window.isOpen);
+65 -18
src/routes/+layout.svelte
··· 5 5 import { accountSetup } from '$lib/atproto/setup.svelte'; 6 6 import favicon from '$lib/assets/favicon.svg'; 7 7 import { desktopSession } from '$lib/desktop-session.svelte'; 8 - import { getDatabase, resetLocalDatabase, runMigrations } from '$lib/db'; 9 8 import { errorMessage } from '$lib/utils/errors'; 10 9 import { windowManager } from '$lib/window-manager.svelte'; 11 10 import AboutComputer from '$lib/components/AboutComputer.svelte'; 12 11 import AppWindow from '$lib/components/AppWindow.svelte'; 13 12 import BootSplash from '$lib/components/BootSplash.svelte'; 14 13 import DesktopIcon from '$lib/components/DesktopIcon.svelte'; 14 + import DocumentViewer from '$lib/components/DocumentViewer.svelte'; 15 15 import Gedit from '$lib/components/Gedit.svelte'; 16 16 import GnomePanel from '$lib/components/GnomePanel.svelte'; 17 17 import LockScreen from '$lib/components/LockScreen.svelte'; ··· 27 27 let bootError = $state<string | null>(null); 28 28 let cacheDisabled = $state(false); 29 29 30 + const windowTitle = $derived( 31 + accountSetup.isConfigured ? 'AT Protocol Collections - Intrepid Ibex' : 'AT Protocol Account Setup' 32 + ); 33 + const windowIcon = $derived( 34 + accountSetup.isConfigured ? '/icons/humanity/apps/internet-feed-reader.svg' : '/icons/humanity/places/user-home.svg' 35 + ); 36 + const mainWindow = $derived(windowManager.getWindow('main')); 37 + const aboutWindow = $derived(windowManager.getWindow('about-computer')); 38 + const geditWindow = $derived(windowManager.getWindow('gedit')); 39 + const documentViewerWindow = $derived(windowManager.getWindow('document-viewer')); 40 + 30 41 const shortcuts = $derived([ 31 42 { 32 43 label: 'ibex Home', ··· 49 60 } 50 61 }, 51 62 { 63 + label: 'Document Viewer', 64 + icon: '/icons/humanity/mimes/gnome-mime-application-pdf.svg', 65 + selected: documentViewerWindow?.isOpen && !documentViewerWindow.isMinimized, 66 + onactivate: () => windowManager.open('document-viewer') 67 + }, 68 + { 52 69 label: 'Trash', 53 70 icon: '/icons/humanity/places/user-trash.svg', 54 71 onactivate: () => window.open('https://tangled.org/desertthunder.dev/ibex', '_blank', 'noopener,noreferrer') 55 72 } 56 73 ]); 57 74 58 - const windowTitle = $derived( 59 - accountSetup.isConfigured ? 'AT Protocol Collections - Intrepid Ibex' : 'AT Protocol Account Setup' 60 - ); 61 - const windowIcon = $derived( 62 - accountSetup.isConfigured ? '/icons/humanity/apps/internet-feed-reader.svg' : '/icons/humanity/places/user-home.svg' 63 - ); 64 - const mainWindow = $derived(windowManager.getWindow('main')); 65 - const aboutWindow = $derived(windowManager.getWindow('about-computer')); 66 - const geditWindow = $derived(windowManager.getWindow('gedit')); 67 - 68 75 $effect(() => { 69 76 windowManager.setTitle('main', windowTitle, windowIcon); 70 77 showAboutComputer = windowManager.getWindow('about-computer')?.isOpen ?? false; ··· 88 95 bootError = null; 89 96 90 97 try { 98 + const { getDatabase, getMigrationStatus, runMigrations } = await import('$lib/db'); 99 + 91 100 bootStep = 'Loading database'; 92 101 const db = await getDatabase(); 93 - bootStep = 'Applying migrations'; 94 - await runMigrations(db); 95 - bootStep = 'Starting desktop'; 96 - await waitForMinimumBootTime(bootStartedAt); 102 + bootStep = 'Checking migrations'; 103 + const migrationStatus = await getMigrationStatus(db); 104 + 105 + if (migrationStatus.pending.length > 0) { 106 + bootStep = 'Applying migrations'; 107 + await runMigrations(db); 108 + bootStep = 'Starting desktop'; 109 + await waitForMinimumBootTime(bootStartedAt); 110 + } else { 111 + bootStep = 'Starting desktop'; 112 + } 113 + 97 114 accountSetup.load(); 98 115 bootStatus = 'ready'; 99 116 } catch (error) { ··· 108 125 bootError = null; 109 126 110 127 try { 128 + const { resetLocalDatabase } = await import('$lib/db'); 111 129 await resetLocalDatabase(); 112 130 await bootDesktop(); 113 131 } catch (error) { ··· 123 141 } 124 142 125 143 async function waitForMinimumBootTime(startedAt: number) { 126 - // TODO: this should be configurable/off after migrations are done 127 - // We probably need to track app version 128 - const remaining = 2500 - (performance.now() - startedAt); 144 + const minimumBootTimeMs = 2500; 145 + const remaining = minimumBootTimeMs - (performance.now() - startedAt); 129 146 130 147 if (remaining > 0) { 131 148 await new Promise((resolve) => setTimeout(resolve, remaining)); ··· 195 212 </div> 196 213 {/if} 197 214 215 + {#if documentViewerWindow?.isOpen && !documentViewerWindow.isMinimized} 216 + <div 217 + class="document-viewer-window" 218 + class:maximized={documentViewerWindow.isMaximized} 219 + style:z-index={documentViewerWindow.zIndex}> 220 + <AppWindow 221 + windowId="document-viewer" 222 + title="CHANGELOG.md - Document Viewer" 223 + icon="/icons/humanity/mimes/gnome-mime-application-pdf.svg" 224 + address="/docs/changelog" 225 + maximized={documentViewerWindow.isMaximized} 226 + onfocus={() => windowManager.focus('document-viewer')} 227 + onminimize={() => windowManager.minimize('document-viewer')} 228 + onmaximize={() => windowManager.toggleMaximize('document-viewer')} 229 + onclose={() => windowManager.close('document-viewer')}> 230 + <DocumentViewer slug="changelog" /> 231 + </AppWindow> 232 + </div> 233 + {/if} 234 + 198 235 {#if repoBrowser.selectedRecord && geditWindow?.isOpen && !geditWindow.isMinimized} 199 236 <div class="gedit-window" class:maximized={geditWindow.isMaximized} style:z-index={geditWindow.zIndex}> 200 237 <NativeWindow ··· 269 306 270 307 .primary-window.maximized, 271 308 .about-window.maximized, 309 + .document-viewer-window.maximized, 272 310 .gedit-window.maximized { 273 311 position: fixed; 274 312 top: 1.75rem; ··· 281 319 } 282 320 283 321 .about-window, 322 + .document-viewer-window, 284 323 .gedit-window { 285 324 position: absolute; 286 325 z-index: 3; ··· 293 332 height: min(28rem, calc(100vh - 5rem)); 294 333 } 295 334 335 + .document-viewer-window { 336 + top: min(5rem, 10vh); 337 + left: min(16rem, 18vw); 338 + width: min(48rem, calc(100vw - 2rem)); 339 + height: min(38rem, calc(100vh - 5rem)); 340 + } 341 + 296 342 .gedit-window { 297 343 top: min(6rem, 12vh); 298 344 left: min(20rem, 24vw); ··· 301 347 } 302 348 303 349 .about-window :global(.app-window), 350 + .document-viewer-window :global(.app-window), 304 351 .gedit-window :global(.app-window) { 305 352 height: 100%; 306 353 }
+39
src/routes/docs/CHANGELOG.md
··· 1 + # Changelog 2 + 3 + ## [Unreleased] 4 + 5 + Target release: `v1.0.0`. 6 + 7 + ### 2026-06-09 8 + 9 + #### Added 10 + 11 + - PGlite-backed local cache/search foundation with WASM build support. 12 + - Transactional schema migrations for accounts, cached records, collection 13 + sync state, and record search. 14 + - Ubuntu-purple boot splash that initializes PGlite, runs migrations, and offers 15 + recovery actions. 16 + - Cache-aware AT Protocol browsing: live record fetches populate PGlite and offline 17 + failures can fall back to cached collections/records. 18 + - Full-text cache search in the Nautilus-style collection browser. 19 + - Atmosphere app logo mapping for known AT Protocol collection namespaces. 20 + - Cursor-based record pagination with a generator-backed “Load more records” flow. 21 + 22 + #### Changed 23 + 24 + - Grouped Nautilus collections by top-level namespace folders. 25 + 26 + ### 2026-06-08 27 + 28 + #### Added 29 + 30 + - Ubuntu 8.10 / GNOME 2-inspired desktop shell, panel, wallpaper, windows, and Humanity 31 + icon styling. 32 + - First-run AT Protocol account setup flow with handle resolution and persisted account 33 + identity. 34 + - Nautilus-style collection browser backed by live AT Protocol repository collection and 35 + record loading. 36 + - gedit-style JSON record viewer opened from selected records. 37 + - Window management basics including movable, resizable, minimizable, maximizable, and 38 + closable windows. 39 + - About Computer dialog, desktop icons, sticky note, and Tangled project links.
+28
src/routes/docs/[slug]/+page.svelte
··· 1 + <script lang="ts"> 2 + import DocumentViewer from '$lib/components/DocumentViewer.svelte'; 3 + 4 + let { data }: { data: { slug: string } } = $props(); 5 + 6 + const title = $derived(titleFromSlug(data.slug)); 7 + 8 + function titleFromSlug(slug: string) { 9 + return slug 10 + .split('-') 11 + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 12 + .join(' '); 13 + } 14 + </script> 15 + 16 + <svelte:head> 17 + <title>{title} - Intrepid Ibex</title> 18 + </svelte:head> 19 + 20 + <div class="docs-route"> 21 + <DocumentViewer slug={data.slug} /> 22 + </div> 23 + 24 + <style> 25 + .docs-route { 26 + height: 100vh; 27 + } 28 + </style>
+28
src/routes/docs/[slug]/+page.ts
··· 1 + import { error } from '@sveltejs/kit'; 2 + import type { EntryGenerator, PageLoad } from './$types'; 3 + 4 + const documents = import.meta.glob('../*.md'); 5 + 6 + function slugFromPath(path: string) { 7 + return path 8 + .split('/') 9 + .at(-1) 10 + ?.replace(/\.md$/, '') 11 + .toLowerCase() 12 + .replace(/[^a-z0-9]+/g, '-') 13 + .replace(/^-|-$/g, ''); 14 + } 15 + 16 + const documentSlugs = Object.keys(documents).map((path) => slugFromPath(path)).filter(Boolean) as string[]; 17 + 18 + export const entries: EntryGenerator = () => { 19 + return documentSlugs.map((slug) => ({ slug })); 20 + }; 21 + 22 + export const load: PageLoad = ({ params }) => { 23 + if (!documentSlugs.includes(params.slug)) { 24 + error(404, 'Document not found'); 25 + } 26 + 27 + return { slug: params.slug }; 28 + };
+290
static/icons/humanity/mimes/gnome-mime-application-pdf.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + <svg 4 + xmlns:svg="http://www.w3.org/2000/svg" 5 + xmlns="http://www.w3.org/2000/svg" 6 + xmlns:xlink="http://www.w3.org/1999/xlink" 7 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 8 + version="1.0" 9 + width="48" 10 + height="48" 11 + id="svg2470"> 12 + <defs 13 + id="defs2472"> 14 + <linearGradient 15 + id="linearGradient5048"> 16 + <stop 17 + id="stop5050" 18 + style="stop-color:#000000;stop-opacity:0" 19 + offset="0" /> 20 + <stop 21 + id="stop5056" 22 + style="stop-color:#000000;stop-opacity:1" 23 + offset="0.5" /> 24 + <stop 25 + id="stop5052" 26 + style="stop-color:#000000;stop-opacity:0" 27 + offset="1" /> 28 + </linearGradient> 29 + <linearGradient 30 + x1="302.85715" 31 + y1="366.64789" 32 + x2="302.85715" 33 + y2="609.50507" 34 + id="linearGradient2467" 35 + xlink:href="#linearGradient5048" 36 + gradientUnits="userSpaceOnUse" 37 + gradientTransform="matrix(6.7324884e-2,0,0,1.4700216e-2,-0.3411391,37.040146)" /> 38 + <radialGradient 39 + cx="605.71429" 40 + cy="486.64789" 41 + r="117.14286" 42 + fx="605.71429" 43 + fy="486.64789" 44 + id="radialGradient2464" 45 + xlink:href="#linearGradient5060" 46 + gradientUnits="userSpaceOnUse" 47 + gradientTransform="matrix(-2.3039943e-2,0,0,1.4700216e-2,21.62311,37.040176)" /> 48 + <linearGradient 49 + id="linearGradient5060"> 50 + <stop 51 + id="stop5062" 52 + style="stop-color:#000000;stop-opacity:1" 53 + offset="0" /> 54 + <stop 55 + id="stop5064" 56 + style="stop-color:#000000;stop-opacity:0" 57 + offset="1" /> 58 + </linearGradient> 59 + <radialGradient 60 + cx="605.71429" 61 + cy="486.64789" 62 + r="117.14286" 63 + fx="605.71429" 64 + fy="486.64789" 65 + id="radialGradient2461" 66 + xlink:href="#linearGradient5060" 67 + gradientUnits="userSpaceOnUse" 68 + gradientTransform="matrix(2.3039948e-2,0,0,1.4700216e-2,26.360882,37.040176)" /> 69 + <linearGradient 70 + id="linearGradient2490"> 71 + <stop 72 + id="stop2492" 73 + style="stop-color:#791235;stop-opacity:1" 74 + offset="0" /> 75 + <stop 76 + id="stop2494" 77 + style="stop-color:#dd3b27;stop-opacity:1" 78 + offset="1" /> 79 + </linearGradient> 80 + <linearGradient 81 + x1="-51.786404" 82 + y1="53.623615" 83 + x2="-51.786404" 84 + y2="2.9062471" 85 + id="linearGradient2458" 86 + xlink:href="#linearGradient2490" 87 + gradientUnits="userSpaceOnUse" 88 + gradientTransform="matrix(0.8074968,0,0,0.8948322,59.410232,-2.9805533)" /> 89 + <linearGradient 90 + id="linearGradient3242"> 91 + <stop 92 + id="stop3244" 93 + style="stop-color:#f89b7e;stop-opacity:1" 94 + offset="0" /> 95 + <stop 96 + id="stop3246" 97 + style="stop-color:#e35d4f;stop-opacity:1" 98 + offset="0.26238" /> 99 + <stop 100 + id="stop3248" 101 + style="stop-color:#c6262e;stop-opacity:1" 102 + offset="0.66093999" /> 103 + <stop 104 + id="stop3250" 105 + style="stop-color:#690b2c;stop-opacity:1" 106 + offset="1" /> 107 + </linearGradient> 108 + <radialGradient 109 + cx="14.76559" 110 + cy="10.898237" 111 + r="18.000002" 112 + fx="14.76559" 113 + fy="10.898237" 114 + id="radialGradient2456" 115 + xlink:href="#linearGradient3242" 116 + gradientUnits="userSpaceOnUse" 117 + gradientTransform="matrix(-2.2703142e-8,2.1357283,-3.5601359,-3.7844828e-8,54.620625,-24.438287)" /> 118 + <radialGradient 119 + cx="102" 120 + cy="112.3047" 121 + r="139.55859" 122 + id="XMLID_8_" 123 + gradientUnits="userSpaceOnUse"> 124 + <stop 125 + id="stop41" 126 + style="stop-color:#000000;stop-opacity:1" 127 + offset="0" /> 128 + <stop 129 + id="stop47" 130 + style="stop-color:#000000;stop-opacity:1" 131 + offset="0.18851049" /> 132 + <stop 133 + id="stop49" 134 + style="stop-color:#000000;stop-opacity:0" 135 + offset="0.25718147" /> 136 + <stop 137 + id="stop51" 138 + style="stop-color:#000000;stop-opacity:0" 139 + offset="1" /> 140 + </radialGradient> 141 + <radialGradient 142 + cx="102" 143 + cy="112.3047" 144 + r="139.55859" 145 + id="radialGradient2453" 146 + xlink:href="#XMLID_8_" 147 + gradientUnits="userSpaceOnUse" 148 + gradientTransform="matrix(0.3617022,0,0,-0.3907784,0.8510637,47.517004)" /> 149 + <linearGradient 150 + id="linearGradient3211"> 151 + <stop 152 + id="stop3213" 153 + style="stop-color:#ffffff;stop-opacity:1" 154 + offset="0" /> 155 + <stop 156 + id="stop3215" 157 + style="stop-color:#ffffff;stop-opacity:0" 158 + offset="1" /> 159 + </linearGradient> 160 + <linearGradient 161 + x1="24" 162 + y1="1.9999999" 163 + x2="24" 164 + y2="46.01725" 165 + id="linearGradient2450" 166 + xlink:href="#linearGradient3211" 167 + gradientUnits="userSpaceOnUse" 168 + gradientTransform="matrix(1,0,0,0.9777778,0,-0.9666693)" /> 169 + <linearGradient 170 + id="linearGradient4246"> 171 + <stop 172 + id="stop4248" 173 + style="stop-color:#ffffff;stop-opacity:1" 174 + offset="0" /> 175 + <stop 176 + id="stop4250" 177 + style="stop-color:#dcdcdc;stop-opacity:1" 178 + offset="1" /> 179 + </linearGradient> 180 + <linearGradient 181 + x1="23.969797" 182 + y1="3.9810441" 183 + x2="23.969797" 184 + y2="44.024017" 185 + id="linearGradient2447" 186 + xlink:href="#linearGradient4246" 187 + gradientUnits="userSpaceOnUse" 188 + gradientTransform="matrix(0.8947369,0,0,1.131579,2.5263157,-4.6578946)" /> 189 + <filter 190 + height="1.3286875" 191 + y="-0.16434373" 192 + width="1.2969251" 193 + x="-0.14846256" 194 + id="filter3212"> 195 + <feGaussianBlur 196 + id="feGaussianBlur3214" 197 + stdDeviation="0.77391625" 198 + inkscape:collect="always" /> 199 + </filter> 200 + <linearGradient 201 + id="linearGradient8589"> 202 + <stop 203 + id="stop8591" 204 + style="stop-color:#fefefe;stop-opacity:1" 205 + offset="0" /> 206 + <stop 207 + id="stop8593" 208 + style="stop-color:#cbcbcb;stop-opacity:1" 209 + offset="1" /> 210 + </linearGradient> 211 + <linearGradient 212 + x1="32.892288" 213 + y1="8.0590115" 214 + x2="36.358372" 215 + y2="5.4565363" 216 + id="linearGradient2441" 217 + xlink:href="#linearGradient8589" 218 + gradientUnits="userSpaceOnUse" 219 + gradientTransform="matrix(0.9976985,0,0,1.0406866,0.1638029,-1.1057939)" /> 220 + <linearGradient 221 + id="linearGradient4267"> 222 + <stop 223 + id="stop4269" 224 + style="stop-color:#c4c4c4;stop-opacity:1" 225 + offset="0" /> 226 + <stop 227 + id="stop4271" 228 + style="stop-color:#c4c4c4;stop-opacity:0" 229 + offset="1" /> 230 + </linearGradient> 231 + <linearGradient 232 + x1="35.516502" 233 + y1="4.8577352" 234 + x2="34.102982" 235 + y2="6.5436172" 236 + id="linearGradient2438" 237 + xlink:href="#linearGradient4267" 238 + gradientUnits="userSpaceOnUse" /> 239 + </defs> 240 + <g 241 + id="layer1"> 242 + <rect 243 + width="32.508301" 244 + height="3.5700529" 245 + x="7.7378473" 246 + y="42.429947" 247 + id="rect2879" 248 + style="opacity:0.3;fill:url(#linearGradient2467);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" /> 249 + <path 250 + d="M 7.7378475,42.430102 C 7.7378475,42.430102 7.7378475,45.999958 7.7378475,45.999958 C 6.5513473,46.006679 4.869468,45.200135 4.869468,44.2148 C 4.869468,43.229467 6.1935126,42.430103 7.7378475,42.430102 z" 251 + id="path2881" 252 + style="opacity:0.3;fill:url(#radialGradient2464);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" /> 253 + <path 254 + d="M 40.246148,42.430102 C 40.246148,42.430102 40.246148,45.999958 40.246148,45.999958 C 41.432648,46.006679 43.114528,45.200135 43.114528,44.2148 C 43.114528,43.229467 41.790483,42.430103 40.246148,42.430102 z" 255 + id="path2883" 256 + style="opacity:0.3;fill:url(#radialGradient2461);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" /> 257 + <path 258 + d="M 6.4999609,0.49719878 C 14.520256,0.49719878 22.540551,0.49719878 30.560847,0.49719878 C 31.086081,2.4573984 36.693941,7.3488015 41.500042,10.123605 C 41.500042,21.583338 41.500042,33.04307 41.500042,44.502803 C 29.833348,44.502803 18.166655,44.502803 6.4999609,44.502803 C 6.4999609,29.834268 6.4999609,15.165733 6.4999609,0.49719878 z" 259 + id="path4160" 260 + style="fill:url(#radialGradient2456);fill-opacity:1;stroke:url(#linearGradient2458);stroke-width:0.99992186;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" /> 261 + <path 262 + d="M 7.3617017,44 C 7.1624047,44 6.9999997,43.824541 6.9999997,43.60922 L 6.9999997,1.4051701 C 6.9999997,1.1894613 7.1624047,1.0143926 7.3617017,1.0143926 C 14.844793,1.1224073 23.137385,0.85281757 30.611384,1.0412872 L 40.894021,9.9073127 L 41,43.60922 C 41,43.824541 40.837957,44 40.638297,44 L 7.3617017,44 z" 263 + id="path4191" 264 + style="opacity:0.2;fill:url(#radialGradient2453);fill-opacity:1" /> 265 + <path 266 + d="M 40.500039,10.258811 C 40.500039,21.207848 40.500039,32.573221 40.500039,43.522258 C 29.500013,43.522258 18.499987,43.522258 7.4999609,43.522258 C 7.4999609,29.507417 7.4999609,15.492577 7.4999609,1.4777367 C 15.061954,1.4777367 22.804295,1.4777367 30.366289,1.4777367" 267 + id="path2435" 268 + style="opacity:0.4;fill:none;fill-opacity:1;stroke:url(#linearGradient2450);stroke-width:0.9999218;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" /> 269 + <path 270 + d="M 27.40625,1 C 15.817408,13.595176 9.2020539,15.366459 7,15.5625 L 7,20.09375 C 8.6621097,19.799799 12.831793,18.779912 17.28125,15.25 C 22.986676,10.723686 29.8125,2.125 29.8125,2.125 C 27.941818,6.7728299 27.411144,22.806478 27.375,44 L 41,44 C 41,42.822323 41,43.059096 41,41.875 C 38.783112,36.083676 37.337644,27.727504 36.25,20.25 C 35.607297,14.072494 35.83378,9.6262807 36.345901,5.9066265 C 34.424556,4.2752562 30.72491,1.719687 29.517212,1 L 27.40625,1 z M 7,22.9375 L 7,27.96875 C 11.811154,27.968749 19.203198,35.271179 22.78125,44 L 25.78125,44 C 24.100012,36.064648 10.670523,22.9375 7,22.9375 z" 271 + id="path6711" 272 + style="fill:url(#linearGradient2447);fill-opacity:1;fill-rule:evenodd;stroke:none" /> 273 + <path 274 + d="M 34.495321,5 C 28.651337,9.0398384 16.712418,16.013787 7,14.90277 L 7,23.88573 C 7.0000002,23.885729 33.153847,28.595843 41,18.648208 L 41,10.753088 C 39.645237,8.9191284 36.641936,6.6267909 34.495321,5 z M 41,27.893078 C 37.879395,31.345529 34.049097,38.166385 31.362737,44 L 28,43.971488 C 31.137915,35.769399 36.211951,27.712687 41,22 L 41,27.893078 z" 275 + id="path6713" 276 + style="opacity:0.15;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" /> 277 + <path 278 + d="M 28.617256,0.92125832 C 32.899778,0.92125832 30.770506,9.4044296 30.770506,9.4044296 C 30.770506,9.4044296 41.128148,7.6020829 41.128148,12.223174 C 41.128148,9.6134507 29.825844,1.4945784 28.617256,0.92125832 z" 279 + id="path12038" 280 + style="opacity:0.4;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline;filter:url(#filter3212)" /> 281 + <path 282 + d="M 28.617256,0.92125832 C 31.803749,0.92125832 30.953016,8.5831368 30.953016,8.5831368 C 30.953016,8.5831368 41.128148,7.6020829 41.128148,12.223174 C 41.128148,11.097235 41.214061,10.290929 40.994371,9.9395812 C 39.416058,7.4154025 32.881194,1.7511811 30.41941,1.008793 C 30.235188,0.95323837 29.544367,0.92125832 28.617256,0.92125832 z" 283 + id="path4474" 284 + style="fill:url(#linearGradient2441);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" /> 285 + <path 286 + d="M 30.560847,0.5 C 31.96778,0.9746531 39.526301,6.3794034 41.500042,10.123605 C 41.500042,21.583338 41.500042,10.949143 41.500042,22.408876 C 1.0542159,38.137806 6.2643971,0.5 30.560847,0.5 z" 287 + id="path4238" 288 + style="fill:none;fill-opacity:1;stroke:url(#linearGradient2438);stroke-width:0.99992186;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline" /> 289 + </g> 290 + </svg>
+3
svelte.config.js
··· 1 1 import adapter from '@sveltejs/adapter-static'; 2 + import { mdsvex } from 'mdsvex'; 2 3 3 4 /** @type {import('@sveltejs/kit').Config} */ 4 5 const config = { 6 + extensions: ['.svelte', '.svx', '.md'], 7 + preprocess: [mdsvex({ extensions: ['.svx', '.md'] })], 5 8 compilerOptions: { runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true) }, 6 9 kit: { adapter: adapter() } 7 10 };
+23
vite.config.ts
··· 6 6 plugins: [sveltekit()], 7 7 assetsInclude: ['**/*.wasm'], 8 8 optimizeDeps: { exclude: ['@electric-sql/pglite'] }, 9 + build: { 10 + rolldownOptions: { 11 + onLog(level, log, defaultHandler) { 12 + const message = typeof log === 'string' ? log : log.message; 13 + 14 + /** 15 + * PGlite currently emits Rolldown direct-eval warnings from its own 16 + * packaged code. The app lazy-loads PGlite behind DB operations, and 17 + * this filter is intentionally scoped to that third-party package so 18 + * eval warnings from app code still surface. 19 + */ 20 + if (level === 'warn' && message.includes('Use of direct `eval`') && message.includes('@electric-sql/pglite')) { 21 + return; 22 + } 23 + 24 + if (level === 'warn' && message.includes('[PLUGIN_TIMINGS]')) { 25 + return; 26 + } 27 + 28 + defaultHandler(level, log); 29 + } 30 + } 31 + }, 9 32 test: { 10 33 expect: { requireAssertions: true }, 11 34 ui: false,