create-sia-app 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sia-app",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "create-sia-app": "./dist/index.js"
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- A starter template for building decentralized storage apps on the Sia network. Uses [`sia-storage`](https://www.npmjs.com/package/sia-storage) — a TypeScript SDK that ships a pre-compiled WASM binary for encryption, uploads, downloads, and key management. WASM runs on the main thread (Rust async — no workers required).
5
+ A starter template for building decentralized storage apps on the Sia network. Uses [`@siafoundation/sia-storage`](https://www.npmjs.com/package/@siafoundation/sia-storage) — a TypeScript SDK that ships a pre-compiled WASM binary for encryption, uploads, downloads, and key management. WASM runs on the main thread (Rust async — no workers required).
6
6
 
7
- **Tech stack:** React 19, TypeScript, Vite, Tailwind CSS 4, Zustand, `sia-storage`
7
+ **Tech stack:** React 19, TypeScript, Vite, Tailwind CSS 4, Zustand, `@siafoundation/sia-storage`
8
8
 
9
9
  ## Architecture
10
10
 
@@ -28,7 +28,7 @@ Returning users skip `requestConnection`/`waitForApproval`/`register` entirely.
28
28
 
29
29
  ### SDK
30
30
 
31
- `sia-storage` handles:
31
+ `@siafoundation/sia-storage` handles:
32
32
  - Encrypted file uploads/downloads (erasure coding + encryption)
33
33
  - Key derivation from recovery phrases (BIP-39)
34
34
  - Object pinning and metadata management
@@ -62,7 +62,7 @@ Auth state persists to localStorage via Zustand's `persist` middleware. The stor
62
62
  ### Upload a file
63
63
 
64
64
  ```ts
65
- import { PinnedObject } from 'sia-storage'
65
+ import { PinnedObject } from '@siafoundation/sia-storage'
66
66
 
67
67
  const object = new PinnedObject()
68
68
  const pinnedObject = await sdk.upload(object, file.stream(), {
@@ -76,7 +76,7 @@ const sdk = useAuthStore((s) => s.sdk)
76
76
  - [Tailwind CSS](https://tailwindcss.com) 4
77
77
  - [Zustand](https://zustand.docs.pmnd.rs) (state management)
78
78
  - [Biome](https://biomejs.dev) (linting & formatting)
79
- - [sia-storage](https://www.npmjs.com/package/sia-storage) (Sia SDK — encryption, erasure coding, direct host transfers via WASM)
79
+ - [@siafoundation/sia-storage](https://www.npmjs.com/package/@siafoundation/sia-storage) (Sia SDK — encryption, erasure coding, direct host transfers via WASM)
80
80
 
81
81
  ## Project Structure
82
82
 
@@ -94,4 +94,4 @@ src/
94
94
  ## Learn More
95
95
 
96
96
  - [Sia Documentation](https://docs.sia.tech)
97
- - [sia-storage](https://www.npmjs.com/package/sia-storage)
97
+ - [@siafoundation/sia-storage](https://www.npmjs.com/package/@siafoundation/sia-storage)
@@ -12,7 +12,7 @@
12
12
  "dependencies": {
13
13
  "react": "^19.2.0",
14
14
  "react-dom": "^19.2.0",
15
- "sia-storage": "^0.0.8",
15
+ "@siafoundation/sia-storage": "^0.0.8",
16
16
  "zustand": "^5.0.11"
17
17
  },
18
18
  "devDependencies": {
@@ -16,7 +16,7 @@ export function CopyButton({
16
16
  navigator.clipboard.writeText(value)
17
17
  addToast(label)
18
18
  }}
19
- className="p-1 text-neutral-500 hover:text-neutral-300 transition-colors"
19
+ className="p-1 text-neutral-400 hover:text-neutral-700 transition-colors"
20
20
  title="Copy"
21
21
  >
22
22
  <svg
@@ -8,12 +8,12 @@ export function DevNote({
8
8
  children: ReactNode
9
9
  }) {
10
10
  return (
11
- <div className="border-l-4 border-amber-500 bg-amber-950/50 rounded-r-lg p-4 space-y-1">
12
- <p className="text-amber-400 text-xs font-semibold uppercase tracking-wider">
11
+ <div className="border-l-4 border-amber-500 bg-amber-50 rounded-r-lg p-4 space-y-1">
12
+ <p className="text-amber-700 text-xs font-semibold uppercase tracking-wider">
13
13
  Developer Note
14
14
  </p>
15
- <p className="text-amber-200 text-sm font-medium">{title}</p>
16
- <div className="text-amber-200/80 text-xs leading-relaxed">
15
+ <p className="text-amber-900 text-sm font-medium">{title}</p>
16
+ <div className="text-amber-900/80 text-xs leading-relaxed">
17
17
  {children}
18
18
  </div>
19
19
  </div>
@@ -23,16 +23,16 @@ export function Navbar() {
23
23
  }
24
24
 
25
25
  return (
26
- <header className="border-b border-neutral-800/60">
26
+ <header className="border-b border-neutral-200/80">
27
27
  <div className="flex items-center justify-between px-6 py-3 max-w-5xl mx-auto">
28
- <h1 className="text-sm font-semibold text-white tracking-tight">
28
+ <h1 className="text-sm font-semibold text-neutral-900 tracking-tight">
29
29
  {APP_NAME}
30
30
  </h1>
31
31
  {isConnected && publicKey && (
32
32
  <div className="flex items-center gap-3">
33
33
  <span className="relative flex h-2 w-2">
34
- <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
35
- <span className="relative inline-flex rounded-full h-2 w-2 bg-green-500" />
34
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-500 opacity-75" />
35
+ <span className="relative inline-flex rounded-full h-2 w-2 bg-green-600" />
36
36
  </span>
37
37
  <span
38
38
  className="text-[11px] font-mono text-neutral-500"
@@ -44,7 +44,7 @@ export function Navbar() {
44
44
  <button
45
45
  type="button"
46
46
  onClick={handleSignOut}
47
- className="text-xs text-neutral-500 hover:text-neutral-300 transition-colors ml-1"
47
+ className="text-xs text-neutral-500 hover:text-neutral-900 transition-colors ml-1"
48
48
  >
49
49
  Sign Out
50
50
  </button>
@@ -10,7 +10,7 @@ export function Toasts() {
10
10
  {toasts.map((toast) => (
11
11
  <div
12
12
  key={toast.id}
13
- className="animate-fade-in px-4 py-2 bg-neutral-800 border border-neutral-700 rounded-lg text-sm text-neutral-200 shadow-lg"
13
+ className="animate-fade-in px-4 py-2 bg-white border border-neutral-200 rounded-lg text-sm text-neutral-700 shadow-lg"
14
14
  >
15
15
  {toast.message}
16
16
  </div>
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef, useState } from 'react'
2
- import type { Builder } from 'sia-storage'
2
+ import type { Builder } from '@siafoundation/sia-storage'
3
3
  import { useAuthStore } from '../../stores/auth'
4
4
  import { CopyButton } from '../CopyButton'
5
5
  import { DevNote } from '../DevNote'
@@ -63,10 +63,10 @@ export function ApproveScreen({
63
63
  <div className="flex flex-col items-center justify-center flex-1 px-4">
64
64
  <div className="w-full max-w-md space-y-6">
65
65
  <div className="text-center space-y-2">
66
- <h1 className="text-2xl font-semibold text-white">
66
+ <h1 className="text-2xl font-semibold text-neutral-900">
67
67
  Approve Connection
68
68
  </h1>
69
- <p className="text-neutral-400 text-sm">
69
+ <p className="text-neutral-600 text-sm">
70
70
  Open the link below to approve this app, then return here.
71
71
  </p>
72
72
  </div>
@@ -76,15 +76,15 @@ export function ApproveScreen({
76
76
  The user must visit the approval URL in another tab (or on the
77
77
  indexer&apos;s dashboard) to authorize your app. This is an
78
78
  out-of-band step — your app polls for approval via{' '}
79
- <code className="text-amber-300">builder.waitForApproval()</code>.
79
+ <code className="text-amber-700">builder.waitForApproval()</code>.
80
80
  Once approved, the flow continues to recovery phrase setup.
81
81
  </p>
82
82
  </DevNote>
83
83
 
84
84
  {approvalUrl && (
85
85
  <div className="space-y-3">
86
- <div className="flex items-center gap-2 p-3 bg-neutral-900 border border-neutral-700 rounded-lg">
87
- <span className="flex-1 text-sm font-mono text-neutral-400 truncate">
86
+ <div className="flex items-center gap-2 p-3 bg-white border border-neutral-300 rounded-lg">
87
+ <span className="flex-1 text-sm font-mono text-neutral-600 truncate">
88
88
  {approvalUrl}
89
89
  </span>
90
90
  <CopyButton value={approvalUrl} label="URL copied" />
@@ -104,11 +104,11 @@ export function ApproveScreen({
104
104
  type="button"
105
105
  onClick={handleManualCheck}
106
106
  disabled={manualChecking}
107
- className="w-full py-3 bg-neutral-800 hover:bg-neutral-700 disabled:bg-neutral-700 disabled:text-neutral-500 text-white font-medium rounded-lg transition-colors"
107
+ className="w-full py-3 bg-neutral-100 hover:bg-neutral-200 disabled:bg-neutral-200 disabled:text-neutral-400 text-neutral-900 font-medium rounded-lg transition-colors"
108
108
  >
109
109
  {manualChecking ? (
110
110
  <span className="flex items-center justify-center gap-2">
111
- <span className="w-4 h-4 border-2 border-neutral-400 border-t-white rounded-full animate-spin" />
111
+ <span className="w-4 h-4 border-2 border-neutral-300 border-t-neutral-900 rounded-full animate-spin" />
112
112
  Checking...
113
113
  </span>
114
114
  ) : (
@@ -116,12 +116,12 @@ export function ApproveScreen({
116
116
  )}
117
117
  </button>
118
118
 
119
- <div className="flex items-center justify-center gap-2 text-xs text-neutral-600">
119
+ <div className="flex items-center justify-center gap-2 text-xs text-neutral-500">
120
120
  {polling ? (
121
121
  <>
122
122
  <span className="relative flex h-1.5 w-1.5">
123
- <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
124
- <span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-green-500" />
123
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-500 opacity-75" />
124
+ <span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-green-600" />
125
125
  </span>
126
126
  Polling for approval...
127
127
  </>
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef } from 'react'
2
- import { AppKey, Builder, initSia } from 'sia-storage'
2
+ import { AppKey, Builder, initSia } from '@siafoundation/sia-storage'
3
3
  import { APP_META } from '../../lib/constants'
4
4
  import { useAuthStore } from '../../stores/auth'
5
5
  import { ApproveScreen } from './ApproveScreen'
@@ -54,12 +54,12 @@ export function AuthFlow() {
54
54
  return (
55
55
  <div className="flex-1 flex flex-col">
56
56
  {error && (
57
- <div className="fixed top-4 left-1/2 -translate-x-1/2 z-50 px-4 py-2 bg-red-900/90 border border-red-700 rounded-lg text-red-200 text-sm max-w-md text-center">
57
+ <div className="fixed top-4 left-1/2 -translate-x-1/2 z-50 px-4 py-2 bg-red-50 border border-red-200 rounded-lg text-red-800 text-sm max-w-md text-center shadow-sm">
58
58
  {error}
59
59
  <button
60
60
  type="button"
61
61
  onClick={() => setError(null)}
62
- className="ml-2 text-red-400 hover:text-red-300"
62
+ className="ml-2 text-red-600 hover:text-red-900"
63
63
  >
64
64
  Dismiss
65
65
  </button>
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'react'
2
- import { Builder } from 'sia-storage'
2
+ import { Builder } from '@siafoundation/sia-storage'
3
3
  import { APP_META, DEFAULT_INDEXER_URL } from '../../lib/constants'
4
4
  import { useAuthStore } from '../../stores/auth'
5
5
  import { DevNote } from '../DevNote'
@@ -45,10 +45,10 @@ export function ConnectScreen({
45
45
  <div className="flex flex-col items-center justify-center flex-1 px-4">
46
46
  <div className="w-full max-w-md space-y-6">
47
47
  <div className="text-center space-y-2">
48
- <h1 className="text-2xl font-semibold text-white">
48
+ <h1 className="text-2xl font-semibold text-neutral-900">
49
49
  Connect to Indexer
50
50
  </h1>
51
- <p className="text-neutral-400 text-sm">
51
+ <p className="text-neutral-600 text-sm">
52
52
  Enter your Sia indexer URL to get started
53
53
  </p>
54
54
  </div>
@@ -56,9 +56,9 @@ export function ConnectScreen({
56
56
  <DevNote title="Indexer URL & App Key">
57
57
  <p>
58
58
  The indexer URL points to your Sia storage provider. The default is{' '}
59
- <code className="text-amber-300">https://sia.storage</code>. Your
59
+ <code className="text-amber-700">https://sia.storage</code>. Your
60
60
  app key (set in{' '}
61
- <code className="text-amber-300">src/lib/constants.ts</code>)
61
+ <code className="text-amber-700">src/lib/constants.ts</code>)
62
62
  uniquely identifies your app to the indexer.
63
63
  </p>
64
64
  <p className="mt-1">
@@ -73,14 +73,14 @@ export function ConnectScreen({
73
73
  value={url}
74
74
  onChange={(e) => setUrl(e.target.value)}
75
75
  placeholder="https://sia.storage"
76
- className="w-full px-4 py-3 bg-neutral-900 border border-neutral-700 rounded-lg text-white placeholder-neutral-500 focus:outline-none focus:border-green-500"
76
+ className="w-full px-4 py-3 bg-white border border-neutral-300 rounded-lg text-neutral-900 placeholder-neutral-400 focus:outline-none focus:border-green-600"
77
77
  />
78
78
 
79
79
  <button
80
80
  type="button"
81
81
  onClick={handleConnect}
82
82
  disabled={loading || !url}
83
- className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-neutral-700 disabled:text-neutral-500 text-white font-medium rounded-lg transition-colors"
83
+ className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-neutral-200 disabled:text-neutral-400 text-white font-medium rounded-lg transition-colors"
84
84
  >
85
85
  {loading ? 'Connecting...' : 'Connect'}
86
86
  </button>
@@ -1,8 +1,8 @@
1
1
  export function LoadingScreen({ message }: { message?: string }) {
2
2
  return (
3
3
  <div className="flex flex-col items-center justify-center flex-1 gap-4">
4
- <div className="w-8 h-8 border-2 border-neutral-600 border-t-green-500 rounded-full animate-spin" />
5
- <p className="text-neutral-400 text-sm">{message || 'Initializing...'}</p>
4
+ <div className="w-8 h-8 border-2 border-neutral-300 border-t-green-600 rounded-full animate-spin" />
5
+ <p className="text-neutral-500 text-sm">{message || 'Initializing...'}</p>
6
6
  </div>
7
7
  )
8
8
  }
@@ -3,7 +3,7 @@ import {
3
3
  type Builder,
4
4
  generateRecoveryPhrase,
5
5
  validateRecoveryPhrase,
6
- } from 'sia-storage'
6
+ } from '@siafoundation/sia-storage'
7
7
  import { useAuthStore } from '../../stores/auth'
8
8
  import { CopyButton } from '../CopyButton'
9
9
  import { DevNote } from '../DevNote'
@@ -71,10 +71,10 @@ export function RecoveryScreen({
71
71
  <div className="flex flex-col items-center justify-center flex-1 px-4">
72
72
  <div className="w-full max-w-md space-y-6">
73
73
  <div className="text-center space-y-2">
74
- <h1 className="text-2xl font-semibold text-white">
74
+ <h1 className="text-2xl font-semibold text-neutral-900">
75
75
  Recovery Phrase
76
76
  </h1>
77
- <p className="text-neutral-400 text-sm">
77
+ <p className="text-neutral-600 text-sm">
78
78
  Generate a new recovery phrase or enter an existing one.
79
79
  </p>
80
80
  </div>
@@ -100,7 +100,7 @@ export function RecoveryScreen({
100
100
  <button
101
101
  type="button"
102
102
  onClick={() => setMode('import')}
103
- className="w-full py-3 bg-neutral-800 hover:bg-neutral-700 text-white font-medium rounded-lg transition-colors"
103
+ className="w-full py-3 bg-neutral-100 hover:bg-neutral-200 text-neutral-900 font-medium rounded-lg transition-colors"
104
104
  >
105
105
  Enter Existing Phrase
106
106
  </button>
@@ -114,12 +114,12 @@ export function RecoveryScreen({
114
114
  <div className="flex flex-col items-center justify-center flex-1 px-4">
115
115
  <div className="w-full max-w-md space-y-6">
116
116
  <div className="text-center space-y-2">
117
- <h1 className="text-2xl font-semibold text-white">
117
+ <h1 className="text-2xl font-semibold text-neutral-900">
118
118
  {mode === 'generate'
119
119
  ? 'Save Your Recovery Phrase'
120
120
  : 'Enter Recovery Phrase'}
121
121
  </h1>
122
- <p className="text-neutral-400 text-sm">
122
+ <p className="text-neutral-600 text-sm">
123
123
  {mode === 'generate'
124
124
  ? 'Write down these 12 words in order. You will need them to recover your account.'
125
125
  : 'Enter your 12-word recovery phrase.'}
@@ -128,14 +128,14 @@ export function RecoveryScreen({
128
128
 
129
129
  {mode === 'generate' ? (
130
130
  <div className="space-y-2">
131
- <div className="grid grid-cols-3 gap-2 p-4 bg-neutral-900 rounded-lg border border-neutral-700">
131
+ <div className="grid grid-cols-3 gap-2 p-4 bg-white rounded-lg border border-neutral-300">
132
132
  {generatedPhrase.split(' ').map((word, i) => (
133
133
  <div
134
134
  key={`${word}-${i}`}
135
- className="text-center py-2 bg-neutral-800 rounded text-sm"
135
+ className="text-center py-2 bg-neutral-100 rounded text-sm"
136
136
  >
137
- <span className="text-neutral-500 mr-1">{i + 1}.</span>
138
- <span className="text-white">{word}</span>
137
+ <span className="text-neutral-400 mr-1">{i + 1}.</span>
138
+ <span className="text-neutral-900">{word}</span>
139
139
  </div>
140
140
  ))}
141
141
  </div>
@@ -153,10 +153,10 @@ export function RecoveryScreen({
153
153
  onChange={(e) => handleValidatePhrase(e.target.value)}
154
154
  placeholder="Enter your 12-word recovery phrase..."
155
155
  rows={3}
156
- className="w-full px-4 py-3 bg-neutral-900 border border-neutral-700 rounded-lg text-white placeholder-neutral-500 focus:outline-none focus:border-green-500"
156
+ className="w-full px-4 py-3 bg-white border border-neutral-300 rounded-lg text-neutral-900 placeholder-neutral-400 focus:outline-none focus:border-green-600"
157
157
  />
158
158
  {phraseError && (
159
- <p className="text-red-400 text-sm">{phraseError}</p>
159
+ <p className="text-red-600 text-sm">{phraseError}</p>
160
160
  )}
161
161
  </div>
162
162
  )}
@@ -165,7 +165,7 @@ export function RecoveryScreen({
165
165
  type="button"
166
166
  onClick={handleRegister}
167
167
  disabled={loading || !phrase.trim()}
168
- className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-neutral-700 disabled:text-neutral-500 text-white font-medium rounded-lg transition-colors"
168
+ className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-neutral-200 disabled:text-neutral-400 text-white font-medium rounded-lg transition-colors"
169
169
  >
170
170
  {loading ? 'Registering...' : 'Complete Setup'}
171
171
  </button>
@@ -178,7 +178,7 @@ export function RecoveryScreen({
178
178
  setGeneratedPhrase('')
179
179
  setPhraseError(null)
180
180
  }}
181
- className="w-full py-2 text-neutral-400 hover:text-neutral-300 text-sm transition-colors"
181
+ className="w-full py-2 text-neutral-500 hover:text-neutral-900 text-sm transition-colors"
182
182
  >
183
183
  Back
184
184
  </button>
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react'
2
- import { encodedSize, PinnedObject, type ShardProgress } from 'sia-storage'
2
+ import { encodedSize, PinnedObject, type ShardProgress } from '@siafoundation/sia-storage'
3
3
  import { APP_KEY, DATA_SHARDS, PARITY_SHARDS } from '../../lib/constants'
4
4
  import { useAuthStore } from '../../stores/auth'
5
5
  import { DevNote } from '../DevNote'
@@ -233,34 +233,34 @@ export function UploadZone() {
233
233
  <DevNote title="Replace Your App Key">
234
234
  <p>
235
235
  You&apos;re using the template placeholder. Set your own key in{' '}
236
- <code className="text-amber-300">src/lib/constants.ts</code> or
236
+ <code className="text-amber-700">src/lib/constants.ts</code> or
237
237
  scaffold a fresh project with{' '}
238
- <code className="text-amber-300">bunx create-sia-app</code>.
238
+ <code className="text-amber-700">bunx create-sia-app</code>.
239
239
  </p>
240
240
  </DevNote>
241
241
  )}
242
242
 
243
243
  <DevNote title="Upload & Download">
244
244
  <p>
245
- <code className="text-amber-300">
245
+ <code className="text-amber-700">
246
246
  sdk.upload(object, file.stream(), opts)
247
247
  </code>{' '}
248
248
  encrypts, erasure-codes, and streams shards directly to Sia hosts.{' '}
249
- <code className="text-amber-300">sdk.download(object, opts)</code>{' '}
250
- returns a <code className="text-amber-300">ReadableStream</code> of
249
+ <code className="text-amber-700">sdk.download(object, opts)</code>{' '}
250
+ returns a <code className="text-amber-700">ReadableStream</code> of
251
251
  decrypted bytes. Per-shard progress is reported via{' '}
252
- <code className="text-amber-300">onShardUploaded</code> /{' '}
253
- <code className="text-amber-300">onShardDownloaded</code>.
252
+ <code className="text-amber-700">onShardUploaded</code> /{' '}
253
+ <code className="text-amber-700">onShardDownloaded</code>.
254
254
  </p>
255
255
  </DevNote>
256
256
 
257
257
  {error && (
258
- <div className="flex items-center justify-between px-4 py-2.5 bg-red-950/80 border border-red-900 rounded-lg text-red-300 text-sm">
258
+ <div className="flex items-center justify-between px-4 py-2.5 bg-red-50 border border-red-200 rounded-lg text-red-800 text-sm">
259
259
  <span>{error}</span>
260
260
  <button
261
261
  type="button"
262
262
  onClick={() => setError(null)}
263
- className="text-red-500 hover:text-red-300 text-xs ml-4 shrink-0"
263
+ className="text-red-600 hover:text-red-900 text-xs ml-4 shrink-0"
264
264
  >
265
265
  Dismiss
266
266
  </button>
@@ -280,10 +280,10 @@ export function UploadZone() {
280
280
  }}
281
281
  className={`relative block border-2 border-dashed rounded-xl p-16 text-center transition-all duration-150 ${
282
282
  uploading
283
- ? 'border-neutral-800 cursor-default'
283
+ ? 'border-neutral-300 cursor-default'
284
284
  : dragOver
285
- ? 'border-green-500 bg-green-500/5 cursor-pointer'
286
- : 'border-neutral-800 hover:border-neutral-600 cursor-pointer'
285
+ ? 'border-green-600 bg-green-600/5 cursor-pointer'
286
+ : 'border-neutral-300 hover:border-neutral-400 cursor-pointer'
287
287
  }`}
288
288
  >
289
289
  <input
@@ -300,24 +300,24 @@ export function UploadZone() {
300
300
 
301
301
  {activeUpload ? (
302
302
  <div className="space-y-4">
303
- <p className="text-neutral-300 text-sm">
303
+ <p className="text-neutral-700 text-sm">
304
304
  Uploading{' '}
305
- <span className="text-white">{activeUpload.fileName}</span>{' '}
305
+ <span className="text-neutral-900">{activeUpload.fileName}</span>{' '}
306
306
  <span className="text-neutral-500">
307
307
  ({formatBytes(activeUpload.fileSize)})
308
308
  </span>
309
309
  </p>
310
- <div className="w-full max-w-xs mx-auto bg-neutral-800 rounded-full h-1.5 overflow-hidden">
310
+ <div className="w-full max-w-xs mx-auto bg-neutral-200 rounded-full h-1.5 overflow-hidden">
311
311
  {activeUpload.shardsDone === 0 ? (
312
- <div className="bg-green-500 h-full rounded-full w-1/4 animate-indeterminate" />
312
+ <div className="bg-green-600 h-full rounded-full w-1/4 animate-indeterminate" />
313
313
  ) : (
314
314
  <div
315
- className="bg-green-500 h-full rounded-full transition-all duration-300"
315
+ className="bg-green-600 h-full rounded-full transition-all duration-300"
316
316
  style={{ width: `${uploadPercent}%` }}
317
317
  />
318
318
  )}
319
319
  </div>
320
- <p className="text-neutral-600 text-xs font-mono">
320
+ <p className="text-neutral-500 text-xs font-mono">
321
321
  {activeUpload.shardsDone} shards &middot;{' '}
322
322
  {formatBytes(
323
323
  (activeUpload.bytesUploaded / activeUpload.encodedTotal) *
@@ -329,7 +329,7 @@ export function UploadZone() {
329
329
  ) : (
330
330
  <div className="space-y-2">
331
331
  <svg
332
- className="w-8 h-8 mx-auto text-neutral-700"
332
+ className="w-8 h-8 mx-auto text-neutral-400"
333
333
  viewBox="0 0 24 24"
334
334
  fill="none"
335
335
  stroke="currentColor"
@@ -339,10 +339,10 @@ export function UploadZone() {
339
339
  <path d="M12 16V4m0 0l-4 4m4-4l4 4" />
340
340
  <path d="M20 16v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2" />
341
341
  </svg>
342
- <p className="text-neutral-400 text-sm">
342
+ <p className="text-neutral-600 text-sm">
343
343
  Drop files here or click to browse
344
344
  </p>
345
- <p className="text-neutral-600 text-xs">
345
+ <p className="text-neutral-500 text-xs">
346
346
  Encrypted end-to-end and stored on the Sia network
347
347
  </p>
348
348
  </div>
@@ -355,7 +355,7 @@ export function UploadZone() {
355
355
  <h2 className="text-xs font-medium text-neutral-500 uppercase tracking-wider">
356
356
  {files.length} file{files.length !== 1 ? 's' : ''}
357
357
  </h2>
358
- <div className="divide-y divide-neutral-800/60">
358
+ <div className="divide-y divide-neutral-200/80">
359
359
  {files.map((file) => {
360
360
  const isDownloading = downloading === file.id
361
361
  return (
@@ -364,10 +364,10 @@ export function UploadZone() {
364
364
  className="flex items-center justify-between py-3 group"
365
365
  >
366
366
  <div className="flex-1 min-w-0 mr-4">
367
- <p className="text-sm text-neutral-200 truncate">
367
+ <p className="text-sm text-neutral-900 truncate">
368
368
  {file.metadata.name}
369
369
  </p>
370
- <p className="text-xs text-neutral-600 mt-0.5">
370
+ <p className="text-xs text-neutral-500 mt-0.5">
371
371
  {formatBytes(file.metadata.size)}
372
372
  {file.metadata.type !== 'application/octet-stream' && (
373
373
  <span> &middot; {file.metadata.type}</span>
@@ -388,7 +388,7 @@ export function UploadZone() {
388
388
  type="button"
389
389
  onClick={() => downloadFile(file)}
390
390
  disabled={downloading !== null}
391
- className="text-xs text-neutral-600 hover:text-neutral-300 disabled:opacity-30 disabled:cursor-default transition-colors"
391
+ className="text-xs text-neutral-500 hover:text-neutral-900 disabled:opacity-30 disabled:cursor-default transition-colors"
392
392
  title="Download"
393
393
  >
394
394
  {isDownloading ? (
@@ -418,7 +418,7 @@ export function UploadZone() {
418
418
  )}
419
419
  </button>
420
420
  <span
421
- className="text-[11px] text-neutral-700 font-mono group-hover:text-neutral-500 transition-colors"
421
+ className="text-[11px] text-neutral-400 font-mono group-hover:text-neutral-700 transition-colors"
422
422
  title={file.metadata.hash}
423
423
  >
424
424
  {file.metadata.hash.slice(0, 8)}...
@@ -2,8 +2,8 @@
2
2
 
3
3
  body {
4
4
  margin: 0;
5
- background-color: #0a0a0a;
6
- color: #e5e5e5;
5
+ background-color: #fafafa;
6
+ color: #171717;
7
7
  font-family:
8
8
  -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
9
9
  }
@@ -1,6 +1,6 @@
1
- import type { AppMetadata } from 'sia-storage'
1
+ import type { AppMetadata } from '@siafoundation/sia-storage'
2
2
 
3
- // biome-ignore format: scaffolder substitutes a 64-char hex string here
3
+ // biome-ignore format: long hex literal
4
4
  export const APP_KEY = '{{APP_KEY}}'
5
5
  export const APP_NAME = '{{APP_NAME}}'
6
6
  export const DEFAULT_INDEXER_URL = '{{INDEXER_URL}}'
@@ -1,6 +1,7 @@
1
- import type { Sdk } from 'sia-storage'
1
+ import type { Sdk } from '@siafoundation/sia-storage'
2
2
  import { create } from 'zustand'
3
3
  import { persist } from 'zustand/middleware'
4
+ import { APP_KEY } from '../lib/constants'
4
5
 
5
6
  export type AuthStep =
6
7
  | 'loading'
@@ -50,7 +51,7 @@ export const useAuthStore = create<AuthState>()(
50
51
  }),
51
52
  }),
52
53
  {
53
- name: '{{APP_NAME}}-auth',
54
+ name: `sia-auth-${APP_KEY.slice(0, 16)}`,
54
55
  partialize: (state) => ({
55
56
  storedKeyHex: state.storedKeyHex,
56
57
  indexerUrl: state.indexerUrl,
@@ -6,5 +6,5 @@ export default defineConfig({
6
6
  plugins: [react(), tailwindcss()],
7
7
  // sia-storage loads its WASM via `new URL(..., import.meta.url)`; excluding
8
8
  // it from the deps pre-bundler keeps that URL pointing at the real file.
9
- optimizeDeps: { exclude: ['sia-storage'] },
9
+ optimizeDeps: { exclude: ['@siafoundation/sia-storage'] },
10
10
  })