create-sia-app 0.1.13 → 0.1.15

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.13",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "create-sia-app": "./dist/index.js"
@@ -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>
@@ -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
  </>
@@ -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>
@@ -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
  }
@@ -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,6 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react'
2
- import { PinnedObject, type ShardProgress } from 'sia-storage'
3
- import { APP_KEY } from '../../lib/constants'
2
+ import { encodedSize, PinnedObject, type ShardProgress } from 'sia-storage'
3
+ import { APP_KEY, DATA_SHARDS, PARITY_SHARDS } from '../../lib/constants'
4
4
  import { useAuthStore } from '../../stores/auth'
5
5
  import { DevNote } from '../DevNote'
6
6
 
@@ -37,9 +37,10 @@ type UploadedFile = {
37
37
 
38
38
  type UploadProgress = {
39
39
  fileName: string
40
+ fileSize: number
40
41
  shardsDone: number
41
42
  bytesUploaded: number
42
- totalBytes: number
43
+ encodedTotal: number
43
44
  }
44
45
 
45
46
  type DownloadProgress = {
@@ -88,11 +89,13 @@ export function UploadZone() {
88
89
  if (!sdk) return
89
90
  setUploading(true)
90
91
  setError(null)
92
+ const encodedTotal = encodedSize(file.size, DATA_SHARDS, PARITY_SHARDS)
91
93
  setActiveUpload({
92
94
  fileName: file.name,
95
+ fileSize: file.size,
93
96
  shardsDone: 0,
94
97
  bytesUploaded: 0,
95
- totalBytes: file.size,
98
+ encodedTotal,
96
99
  })
97
100
 
98
101
  try {
@@ -107,14 +110,17 @@ export function UploadZone() {
107
110
  let bytesUploaded = 0
108
111
  const pinnedObject = await sdk.upload(object, file.stream(), {
109
112
  maxInflight: 10,
113
+ dataShards: DATA_SHARDS,
114
+ parityShards: PARITY_SHARDS,
110
115
  onShardUploaded: (progress: ShardProgress) => {
111
116
  shardsDone++
112
117
  bytesUploaded += progress.shardSize
113
118
  setActiveUpload({
114
119
  fileName: file.name,
120
+ fileSize: file.size,
115
121
  shardsDone,
116
122
  bytesUploaded,
117
- totalBytes: file.size,
123
+ encodedTotal,
118
124
  })
119
125
  },
120
126
  })
@@ -215,7 +221,7 @@ export function UploadZone() {
215
221
  ? Math.min(
216
222
  100,
217
223
  Math.round(
218
- (activeUpload.bytesUploaded / activeUpload.totalBytes) * 100,
224
+ (activeUpload.bytesUploaded / activeUpload.encodedTotal) * 100,
219
225
  ),
220
226
  )
221
227
  : 0
@@ -227,34 +233,34 @@ export function UploadZone() {
227
233
  <DevNote title="Replace Your App Key">
228
234
  <p>
229
235
  You&apos;re using the template placeholder. Set your own key in{' '}
230
- <code className="text-amber-300">src/lib/constants.ts</code> or
236
+ <code className="text-amber-700">src/lib/constants.ts</code> or
231
237
  scaffold a fresh project with{' '}
232
- <code className="text-amber-300">bunx create-sia-app</code>.
238
+ <code className="text-amber-700">bunx create-sia-app</code>.
233
239
  </p>
234
240
  </DevNote>
235
241
  )}
236
242
 
237
243
  <DevNote title="Upload & Download">
238
244
  <p>
239
- <code className="text-amber-300">
245
+ <code className="text-amber-700">
240
246
  sdk.upload(object, file.stream(), opts)
241
247
  </code>{' '}
242
248
  encrypts, erasure-codes, and streams shards directly to Sia hosts.{' '}
243
- <code className="text-amber-300">sdk.download(object, opts)</code>{' '}
244
- 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
245
251
  decrypted bytes. Per-shard progress is reported via{' '}
246
- <code className="text-amber-300">onShardUploaded</code> /{' '}
247
- <code className="text-amber-300">onShardDownloaded</code>.
252
+ <code className="text-amber-700">onShardUploaded</code> /{' '}
253
+ <code className="text-amber-700">onShardDownloaded</code>.
248
254
  </p>
249
255
  </DevNote>
250
256
 
251
257
  {error && (
252
- <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">
253
259
  <span>{error}</span>
254
260
  <button
255
261
  type="button"
256
262
  onClick={() => setError(null)}
257
- 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"
258
264
  >
259
265
  Dismiss
260
266
  </button>
@@ -274,10 +280,10 @@ export function UploadZone() {
274
280
  }}
275
281
  className={`relative block border-2 border-dashed rounded-xl p-16 text-center transition-all duration-150 ${
276
282
  uploading
277
- ? 'border-neutral-800 cursor-default'
283
+ ? 'border-neutral-300 cursor-default'
278
284
  : dragOver
279
- ? 'border-green-500 bg-green-500/5 cursor-pointer'
280
- : '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'
281
287
  }`}
282
288
  >
283
289
  <input
@@ -294,30 +300,36 @@ export function UploadZone() {
294
300
 
295
301
  {activeUpload ? (
296
302
  <div className="space-y-4">
297
- <p className="text-neutral-300 text-sm">
303
+ <p className="text-neutral-700 text-sm">
298
304
  Uploading{' '}
299
- <span className="text-white">{activeUpload.fileName}</span>
305
+ <span className="text-neutral-900">{activeUpload.fileName}</span>{' '}
306
+ <span className="text-neutral-500">
307
+ ({formatBytes(activeUpload.fileSize)})
308
+ </span>
300
309
  </p>
301
- <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">
302
311
  {activeUpload.shardsDone === 0 ? (
303
- <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" />
304
313
  ) : (
305
314
  <div
306
- 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"
307
316
  style={{ width: `${uploadPercent}%` }}
308
317
  />
309
318
  )}
310
319
  </div>
311
- <p className="text-neutral-600 text-xs font-mono">
320
+ <p className="text-neutral-500 text-xs font-mono">
312
321
  {activeUpload.shardsDone} shards &middot;{' '}
313
- {formatBytes(activeUpload.bytesUploaded)} /{' '}
314
- {formatBytes(activeUpload.totalBytes)}
322
+ {formatBytes(
323
+ (activeUpload.bytesUploaded / activeUpload.encodedTotal) *
324
+ activeUpload.fileSize,
325
+ )}{' '}
326
+ / {formatBytes(activeUpload.fileSize)}
315
327
  </p>
316
328
  </div>
317
329
  ) : (
318
330
  <div className="space-y-2">
319
331
  <svg
320
- className="w-8 h-8 mx-auto text-neutral-700"
332
+ className="w-8 h-8 mx-auto text-neutral-400"
321
333
  viewBox="0 0 24 24"
322
334
  fill="none"
323
335
  stroke="currentColor"
@@ -327,10 +339,10 @@ export function UploadZone() {
327
339
  <path d="M12 16V4m0 0l-4 4m4-4l4 4" />
328
340
  <path d="M20 16v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2" />
329
341
  </svg>
330
- <p className="text-neutral-400 text-sm">
342
+ <p className="text-neutral-600 text-sm">
331
343
  Drop files here or click to browse
332
344
  </p>
333
- <p className="text-neutral-600 text-xs">
345
+ <p className="text-neutral-500 text-xs">
334
346
  Encrypted end-to-end and stored on the Sia network
335
347
  </p>
336
348
  </div>
@@ -343,7 +355,7 @@ export function UploadZone() {
343
355
  <h2 className="text-xs font-medium text-neutral-500 uppercase tracking-wider">
344
356
  {files.length} file{files.length !== 1 ? 's' : ''}
345
357
  </h2>
346
- <div className="divide-y divide-neutral-800/60">
358
+ <div className="divide-y divide-neutral-200/80">
347
359
  {files.map((file) => {
348
360
  const isDownloading = downloading === file.id
349
361
  return (
@@ -352,10 +364,10 @@ export function UploadZone() {
352
364
  className="flex items-center justify-between py-3 group"
353
365
  >
354
366
  <div className="flex-1 min-w-0 mr-4">
355
- <p className="text-sm text-neutral-200 truncate">
367
+ <p className="text-sm text-neutral-900 truncate">
356
368
  {file.metadata.name}
357
369
  </p>
358
- <p className="text-xs text-neutral-600 mt-0.5">
370
+ <p className="text-xs text-neutral-500 mt-0.5">
359
371
  {formatBytes(file.metadata.size)}
360
372
  {file.metadata.type !== 'application/octet-stream' && (
361
373
  <span> &middot; {file.metadata.type}</span>
@@ -376,7 +388,7 @@ export function UploadZone() {
376
388
  type="button"
377
389
  onClick={() => downloadFile(file)}
378
390
  disabled={downloading !== null}
379
- 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"
380
392
  title="Download"
381
393
  >
382
394
  {isDownloading ? (
@@ -406,7 +418,7 @@ export function UploadZone() {
406
418
  )}
407
419
  </button>
408
420
  <span
409
- 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"
410
422
  title={file.metadata.hash}
411
423
  >
412
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
1
  import type { AppMetadata } from '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}}'
@@ -12,3 +12,7 @@ export const APP_META: AppMetadata = {
12
12
  logoUrl: undefined,
13
13
  callbackUrl: undefined,
14
14
  }
15
+
16
+ // Erasure coding parameters — passed to sdk.upload() and encodedSize().
17
+ export const DATA_SHARDS = 10
18
+ export const PARITY_SHARDS = 20
@@ -1,6 +1,7 @@
1
1
  import type { Sdk } from '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,