most-box 0.3.5 → 0.3.7

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.
Files changed (57) hide show
  1. package/README.md +23 -16
  2. package/out/about/index.html +2 -2
  3. package/out/admin/index.html +0 -0
  4. package/out/app/index.html +0 -0
  5. package/out/assets/AccountMenu-D_rcXDZr.js +1 -0
  6. package/out/assets/AppShell-CKP_dglR.js +1 -0
  7. package/out/assets/{AppTop-BE5kKBYH.js → AppTop-C03PsDr_.js} +1 -1
  8. package/out/assets/ChatRestoringIndicator-CLV78gNW.js +1 -0
  9. package/out/assets/{CopyButton-DnFqfCN9.js → CopyButton-ClwBN6Iy.js} +1 -1
  10. package/out/assets/{MarketingHeader-8gNzHawY.js → MarketingHeader-ChTIIzXG.js} +1 -1
  11. package/out/assets/{MarketingLayout-BPdjpEfc.js → MarketingLayout-hkPAtieL.js} +1 -1
  12. package/out/assets/{MilkdownEditor-C388llV6.js → MilkdownEditor-X9MUH_0q.js} +1 -1
  13. package/out/assets/{OpenSidebarButton-OP8zl7ad.js → OpenSidebarButton-CXBAjjCE.js} +1 -1
  14. package/out/assets/{chatJoinInvite-CeC6H6t8.js → chatJoinInvite-7tuD-STm.js} +1 -1
  15. package/out/assets/i18n-DKsg67mH.js +1 -0
  16. package/out/assets/{index-D1mozno-.js → index-DJzLHA0V.js} +3 -3
  17. package/out/assets/index-FEwag7ej.css +1 -0
  18. package/out/assets/index.lazy-B8ud1tpC.js +1 -0
  19. package/out/assets/{index.lazy-CX2l1_Gi.js → index.lazy-BEDWJZPP.js} +1 -1
  20. package/out/assets/{index.lazy-CsTaY5WS.js → index.lazy-BQ_Dyw5C.js} +1 -1
  21. package/out/assets/{index.lazy-D7E1pb1S.js → index.lazy-C0L9TDqK.js} +1 -1
  22. package/out/assets/{index.lazy-ClrSkRx5.js → index.lazy-C8luNCxY.js} +1 -1
  23. package/out/assets/{index.lazy-B1iJsXQ8.js → index.lazy-CT5TGyGl.js} +1 -1
  24. package/out/assets/{index.lazy-59kmyNMW.js → index.lazy-CTLlLPjy.js} +1 -1
  25. package/out/assets/{index.lazy-m9XAzMt1.js → index.lazy-Cgrv1NmQ.js} +1 -1
  26. package/out/assets/{index.lazy-Bz77Jlzu.js → index.lazy-CnpeKB4C.js} +1 -1
  27. package/out/assets/{index.lazy-DzaN9qwA.js → index.lazy-CwAN1t6l.js} +2 -2
  28. package/out/assets/{index.lazy-FvXAYdEO.js → index.lazy-Cy249kq1.js} +1 -1
  29. package/out/assets/index.lazy-DA98XQ0Q.js +1 -0
  30. package/out/assets/index.lazy-DYhpVW5B.js +3 -0
  31. package/out/assets/{index.lazy-CVFcovqX.js → index.lazy-b811JgFg.js} +1 -1
  32. package/out/assets/{index.lazy-CflWbocP.js → index.lazy-psYKmxdW.js} +1 -1
  33. package/out/assets/{saveLocalFile-2CNt4X0T.js → saveLocalFile-DTgdBgO9.js} +1 -1
  34. package/out/assets/useChannelMessages-BrqZa2SG.js +4 -0
  35. package/out/assets/{useGameRoom-asr2gyY6.js → useGameRoom-B3wvUICl.js} +1 -1
  36. package/out/chat/index.html +0 -0
  37. package/out/chat/join/demo/index.html +0 -0
  38. package/out/chat/join/index.html +0 -0
  39. package/out/download/index.html +2 -2
  40. package/out/game/gandengyan/index.html +0 -0
  41. package/out/game/index.html +0 -0
  42. package/out/game/zhajinhua/index.html +0 -0
  43. package/out/index.html +2 -2
  44. package/out/note/index.html +0 -0
  45. package/out/ping/index.html +2 -2
  46. package/out/profile/index.html +0 -0
  47. package/out/web3/index.html +0 -0
  48. package/package.json +1 -1
  49. package/server/src/index.js +212 -77
  50. package/out/assets/AccountMenu-D-wa5tux.js +0 -1
  51. package/out/assets/AppShell-BdRKZIZ9.js +0 -1
  52. package/out/assets/i18n-C6yaqRst.js +0 -1
  53. package/out/assets/index-C6IjeTBh.css +0 -1
  54. package/out/assets/index.lazy-C_J9afOY.js +0 -1
  55. package/out/assets/index.lazy-CazlK1-Y.js +0 -3
  56. package/out/assets/index.lazy-JwrS2vDI.js +0 -1
  57. package/out/assets/useChannelMessages-DDBqKBXU.js +0 -4
@@ -101,6 +101,26 @@ function isChannelHistoryEntry(entry) {
101
101
  return entry?.type === 'message' || entry?.type === 'system'
102
102
  }
103
103
 
104
+ function getChannelHistoryDedupeKey(message) {
105
+ const type = String(message?.type || '')
106
+ const event = String(message?.event || '')
107
+ const author = normalizeOwnerAddress(message?.author)
108
+ const content = String(message?.content || '').trim()
109
+
110
+ if (type === 'system' && event === CHANNEL_MEMBER_JOINED_EVENT && author) {
111
+ return `${type}:${event}:${author}:${content}`
112
+ }
113
+
114
+ return [
115
+ message?._coreKey || '',
116
+ type,
117
+ event,
118
+ message?.author || '',
119
+ message?.timestamp || '',
120
+ content,
121
+ ].join(':')
122
+ }
123
+
104
124
  function createMemoryDuplexPair() {
105
125
  let left
106
126
  let right
@@ -293,6 +313,42 @@ export class MostBoxEngine extends EventEmitter {
293
313
  return this.#pruneStaleChannelPresence()
294
314
  }
295
315
 
316
+ #isClosedSessionError(err) {
317
+ if (!err || typeof err !== 'object') return false
318
+ const code = String(err.code || '')
319
+ const message = String(err.message || '')
320
+ return (
321
+ code === 'SESSION_CLOSED' ||
322
+ message.includes('SESSION_CLOSED') ||
323
+ message.includes('closed session')
324
+ )
325
+ }
326
+
327
+ async #reopenChannelLocalWriter(channel) {
328
+ const channelKey = channel.channelKey
329
+ const localKeyHex =
330
+ this.#channelLocalCoreKey.get(channelKey) || channel.localWriterCoreKey
331
+ const coresMap = this.#channelCores.get(channelKey)
332
+ const staleCore = localKeyHex && coresMap ? coresMap.get(localKeyHex) : null
333
+
334
+ if (staleCore) {
335
+ coresMap.delete(localKeyHex)
336
+ await staleCore.close().catch(() => {})
337
+ }
338
+ this.#channelLocalCoreKey.delete(channelKey)
339
+
340
+ await this.#openChannelRuntime(channel)
341
+
342
+ const reopenedKeyHex = this.#channelLocalCoreKey.get(channelKey)
343
+ const reopenedCore = reopenedKeyHex
344
+ ? this.#channelCores.get(channelKey)?.get(reopenedKeyHex)
345
+ : null
346
+ if (!reopenedCore) {
347
+ throw new Error('频道未初始化或无可写 core')
348
+ }
349
+ return reopenedCore
350
+ }
351
+
296
352
  /**
297
353
  * 初始化引擎 — 必须在调用其他方法之前调用
298
354
  */
@@ -624,29 +680,38 @@ export class MostBoxEngine extends EventEmitter {
624
680
  const publishedBucket = this.#getPublishedBucket(ownerAddress, true)
625
681
  // 检查相同内容是否已存在
626
682
  const existingIndex = publishedBucket.findIndex(f => f.cid === cidString)
683
+ const repairingMissingContent = existingIndex !== -1
627
684
  if (existingIndex !== -1) {
628
685
  const existing = publishedBucket[existingIndex]
629
- await this.#joinCidTopicInternal(cidString, {
630
- server: true,
631
- client: false,
632
- })
633
- this.#upsertHolding({
634
- cid: cidString,
635
- fileName: existing.fileName,
636
- size: fileSize,
637
- driveName: name,
638
- source: 'published',
686
+ const existingContent = await this.#getLocalCidContent(cidString, {
687
+ ownerAddress,
688
+ public: true,
689
+ allowHoldingFallback: true,
639
690
  })
640
- return {
641
- cid: cidString,
642
- link: buildMostLink(cidString, existing.fileName),
643
- fileName: existing.fileName,
644
- alreadyExists: true,
691
+ if (existingContent) {
692
+ await this.#joinCidTopicInternal(cidString, {
693
+ server: true,
694
+ client: false,
695
+ })
696
+ this.#upsertHolding({
697
+ cid: cidString,
698
+ fileName: existing.fileName,
699
+ size: fileSize,
700
+ driveName: name,
701
+ source: 'published',
702
+ })
703
+ return {
704
+ cid: cidString,
705
+ link: buildMostLink(cidString, existing.fileName),
706
+ fileName: existing.fileName,
707
+ alreadyExists: true,
708
+ }
645
709
  }
646
710
  }
647
711
 
648
712
  this.#assertDisplayNameAvailable(safeFileName, {
649
713
  ownerAddress,
714
+ excludeCid: repairingMissingContent ? cidString : undefined,
650
715
  })
651
716
 
652
717
  // 获取或创建该 CID 对应的 drive
@@ -667,40 +732,17 @@ export class MostBoxEngine extends EventEmitter {
667
732
 
668
733
  // Hyperdrive 中用 CID 作为 key 存储(解耦目录结构)
669
734
  const driveKey = '/' + cidString
670
-
671
- const ws = drive.createWriteStream(driveKey)
672
-
673
- if (Buffer.isBuffer(content)) {
674
- let offset = 0
675
- const waitForDrain = () =>
676
- new Promise(resolve => ws.once('drain', resolve))
677
-
678
- try {
679
- while (offset < content.length) {
680
- const chunk = content.slice(offset, offset + FILE_WRITE_CHUNK_SIZE)
681
- const canContinue = ws.write(chunk)
682
- offset += chunk.length
683
- if (!canContinue && offset < content.length) {
684
- await waitForDrain()
685
- }
686
- }
687
- ws.end()
688
- await new Promise((resolve, reject) => {
689
- ws.on('finish', resolve)
690
- ws.on('error', reject)
691
- })
692
- } catch (err) {
693
- ws.destroy()
735
+ try {
736
+ await this.#writeDriveFile(drive, driveKey, content, cleanPath)
737
+ } catch (err) {
738
+ if (!this.#isClosedSessionError(err)) {
694
739
  throw err
695
740
  }
696
- } else {
697
- const rs = fs.createReadStream(cleanPath)
698
- await new Promise((resolve, reject) => {
699
- rs.pipe(ws)
700
- ws.on('finish', resolve)
701
- ws.on('error', reject)
702
- rs.on('error', reject)
741
+ drive = await this.#reopenDrive(name, {
742
+ server: true,
743
+ client: false,
703
744
  })
745
+ await this.#writeDriveFile(drive, driveKey, content, cleanPath)
704
746
  }
705
747
 
706
748
  // 存储 displayName(用户看到的文件夹路径),不存储 drivePath
@@ -715,7 +757,11 @@ export class MostBoxEngine extends EventEmitter {
715
757
  starred: false,
716
758
  syncUpdatedAt: now,
717
759
  }
718
- publishedBucket.push(fileRecord)
760
+ if (repairingMissingContent) {
761
+ publishedBucket[existingIndex] = fileRecord
762
+ } else {
763
+ publishedBucket.push(fileRecord)
764
+ }
719
765
  this.#savePublishedMetadata()
720
766
  this.#upsertHolding({
721
767
  cid: cidString,
@@ -2214,7 +2260,10 @@ export class MostBoxEngine extends EventEmitter {
2214
2260
  async #hasLocalDriveContent(drive, key) {
2215
2261
  try {
2216
2262
  return await drive.has(key)
2217
- } catch {
2263
+ } catch (err) {
2264
+ if (this.#isClosedSessionError(err)) {
2265
+ throw err
2266
+ }
2218
2267
  return false
2219
2268
  }
2220
2269
  }
@@ -2234,42 +2283,53 @@ export class MostBoxEngine extends EventEmitter {
2234
2283
  }
2235
2284
  const holding = this.#holdings.find(item => item.cid === cid)
2236
2285
  const { driveName } = this.#getCidInfo(cid)
2237
- const drive = await this.#getOrCreateDrive(
2286
+ let drive = await this.#getOrCreateDrive(
2238
2287
  fileRecord?.driveName || holding?.driveName || driveName,
2239
2288
  { server: true, client: false }
2240
2289
  )
2241
2290
  const driveKey = '/' + cid
2242
2291
 
2243
- try {
2244
- const entry = await drive.entry(driveKey, { wait: false })
2245
- if (!entry?.value?.blob) {
2246
- return null
2247
- }
2248
- const hasContent = await this.#hasLocalDriveContent(drive, driveKey)
2249
- if (!hasContent) {
2250
- return null
2251
- }
2292
+ for (let attempt = 0; attempt < 2; attempt += 1) {
2293
+ try {
2294
+ const entry = await drive.entry(driveKey, { wait: false })
2295
+ if (!entry?.value?.blob) {
2296
+ return null
2297
+ }
2298
+ const hasContent = await this.#hasLocalDriveContent(drive, driveKey)
2299
+ if (!hasContent) {
2300
+ return null
2301
+ }
2252
2302
 
2253
- const size =
2254
- Number(entry.value.blob.byteLength) ||
2255
- Number(fileRecord?.size) ||
2256
- Number(holding?.size) ||
2257
- 0
2258
- return {
2259
- drive,
2260
- entry,
2261
- size,
2262
- fileRecord: fileRecord || {
2263
- cid,
2264
- fileName: holding?.fileName || cid,
2265
- driveName: holding?.driveName || driveName,
2303
+ const size =
2304
+ Number(entry.value.blob.byteLength) ||
2305
+ Number(fileRecord?.size) ||
2306
+ Number(holding?.size) ||
2307
+ 0
2308
+ return {
2309
+ drive,
2310
+ entry,
2266
2311
  size,
2267
- ownerAddress,
2268
- },
2312
+ fileRecord: fileRecord || {
2313
+ cid,
2314
+ fileName: holding?.fileName || cid,
2315
+ driveName: holding?.driveName || driveName,
2316
+ size,
2317
+ ownerAddress,
2318
+ },
2319
+ }
2320
+ } catch (err) {
2321
+ if (attempt === 0 && this.#isClosedSessionError(err)) {
2322
+ drive = await this.#reopenDrive(
2323
+ fileRecord?.driveName || holding?.driveName || driveName,
2324
+ { server: true, client: false }
2325
+ )
2326
+ continue
2327
+ }
2328
+ return null
2269
2329
  }
2270
- } catch {
2271
- return null
2272
2330
  }
2331
+
2332
+ return null
2273
2333
  }
2274
2334
 
2275
2335
  // --- 频道管理 ---
@@ -2632,7 +2692,7 @@ export class MostBoxEngine extends EventEmitter {
2632
2692
 
2633
2693
  const seen = new Set()
2634
2694
  const unique = allMessages.filter(m => {
2635
- const key = `${m._coreKey}:${m.type}:${m.event || ''}:${m.author}:${m.timestamp}:${m.content}`
2695
+ const key = getChannelHistoryDedupeKey(m)
2636
2696
  if (seen.has(key)) return false
2637
2697
  seen.add(key)
2638
2698
  return true
@@ -2721,7 +2781,15 @@ export class MostBoxEngine extends EventEmitter {
2721
2781
  if (event) message.event = event
2722
2782
  }
2723
2783
 
2724
- await core.append(message)
2784
+ try {
2785
+ await core.append(message)
2786
+ } catch (err) {
2787
+ if (!this.#isClosedSessionError(err)) {
2788
+ throw err
2789
+ }
2790
+ const reopenedCore = await this.#reopenChannelLocalWriter(channel)
2791
+ await reopenedCore.append(message)
2792
+ }
2725
2793
  if (channel) {
2726
2794
  channel.lastMessageAt = new Date(message.timestamp).toISOString()
2727
2795
  this.#saveChannelsMetadata()
@@ -4085,6 +4153,19 @@ export class MostBoxEngine extends EventEmitter {
4085
4153
  if (!this.#holdings.some(current => current.cid === holding.cid)) {
4086
4154
  return
4087
4155
  }
4156
+ const localContent = await this.#getLocalCidContent(holding.cid, {
4157
+ public: true,
4158
+ allowHoldingFallback: true,
4159
+ })
4160
+ if (!localContent) {
4161
+ this.#setSeedState(holding.cid, {
4162
+ status: 'error',
4163
+ topic: holding.topic,
4164
+ driveName: holding.driveName,
4165
+ error: 'Local CID content missing',
4166
+ })
4167
+ return
4168
+ }
4088
4169
  await this.#joinCidTopicInternal(holding.cid, {
4089
4170
  server: true,
4090
4171
  client: false,
@@ -4312,6 +4393,60 @@ export class MostBoxEngine extends EventEmitter {
4312
4393
  return drive
4313
4394
  }
4314
4395
 
4396
+ async #reopenDrive(name, options = { server: true, client: false }) {
4397
+ const staleDrive = this.#drives.get(name)
4398
+ if (staleDrive) {
4399
+ this.#drives.delete(name)
4400
+ await staleDrive.close().catch(() => {})
4401
+ }
4402
+ this.#drivePromises.delete(name)
4403
+ return this.#getOrCreateDrive(name, options)
4404
+ }
4405
+
4406
+ async #writeDriveFile(drive, driveKey, content, cleanPath) {
4407
+ const ws = drive.createWriteStream(driveKey)
4408
+
4409
+ if (Buffer.isBuffer(content)) {
4410
+ let offset = 0
4411
+ const waitForDrain = () =>
4412
+ new Promise(resolve => ws.once('drain', resolve))
4413
+
4414
+ try {
4415
+ while (offset < content.length) {
4416
+ const chunk = content.slice(offset, offset + FILE_WRITE_CHUNK_SIZE)
4417
+ const canContinue = ws.write(chunk)
4418
+ offset += chunk.length
4419
+ if (!canContinue && offset < content.length) {
4420
+ await waitForDrain()
4421
+ }
4422
+ }
4423
+ ws.end()
4424
+ await new Promise((resolve, reject) => {
4425
+ ws.on('finish', resolve)
4426
+ ws.on('error', reject)
4427
+ })
4428
+ } catch (err) {
4429
+ ws.destroy()
4430
+ throw err
4431
+ }
4432
+ return
4433
+ }
4434
+
4435
+ const rs = fs.createReadStream(cleanPath)
4436
+ try {
4437
+ await new Promise((resolve, reject) => {
4438
+ rs.pipe(ws)
4439
+ ws.on('finish', resolve)
4440
+ ws.on('error', reject)
4441
+ rs.on('error', reject)
4442
+ })
4443
+ } catch (err) {
4444
+ ws.destroy()
4445
+ rs.destroy()
4446
+ throw err
4447
+ }
4448
+ }
4449
+
4315
4450
  #getOwnerKey(ownerAddress) {
4316
4451
  return getOwnerBucketKey(ownerAddress)
4317
4452
  }
@@ -1 +0,0 @@
1
- import{t as e}from"./jsx-runtime-Bt-cYkS5.js";import{B as t,E as n,W as r,t as i}from"./useAppStore-DNhyhD20.js";import{a,c as o,o as s}from"./i18n-C6yaqRst.js";import{S as c,g as l}from"./index-D1mozno-.js";var u=t(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),d=t(`languages`,[[`path`,{d:`m5 8 6 6`,key:`1wu5hv`}],[`path`,{d:`m4 14 6-6 2-3`,key:`1k1g8d`}],[`path`,{d:`M2 5h12`,key:`or177f`}],[`path`,{d:`M7 2h1`,key:`1t2jsx`}],[`path`,{d:`m22 22-5-10-5 10`,key:`don7ne`}],[`path`,{d:`M14 18h6`,key:`1m8k6r`}]]),f=t(`moon`,[[`path`,{d:`M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401`,key:`kfwtm`}]]),p=t(`sun`,[[`circle`,{cx:`12`,cy:`12`,r:`4`,key:`4exip2`}],[`path`,{d:`M12 2v2`,key:`tus03m`}],[`path`,{d:`M12 20v2`,key:`1lh1kg`}],[`path`,{d:`m4.93 4.93 1.41 1.41`,key:`149t6j`}],[`path`,{d:`m17.66 17.66 1.41 1.41`,key:`ptbguv`}],[`path`,{d:`M2 12h2`,key:`1t8f8n`}],[`path`,{d:`M20 12h2`,key:`1q8mjw`}],[`path`,{d:`m6.34 17.66-1.41 1.41`,key:`1m8zz5`}],[`path`,{d:`m19.07 4.93-1.41 1.41`,key:`1shlcs`}]]),m=t(`user`,[[`path`,{d:`M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2`,key:`975kel`}],[`circle`,{cx:`12`,cy:`7`,r:`4`,key:`17ys0d`}]]),h=e();function g(){let{t:e}=a(),t=i(e=>e.isDarkMode),n=i(e=>e.setIsDarkMode),r=e(`common.appearance.toggle`);return(0,h.jsx)(`button`,{type:`button`,className:`header-tool-btn`,title:r,"aria-label":r,"aria-pressed":t,onClick:()=>n(!t),children:t?(0,h.jsx)(p,{size:16}):(0,h.jsx)(f,{size:16})})}function _(){let{locale:e,setLocale:t,t:n}=a(),r=n(`common.locale.choose`);return(0,h.jsx)(c,{ariaLabel:r,className:`language-toggle`,items:s.map(n=>({key:n,label:o[n],icon:n===e?(0,h.jsx)(u,{size:16}):(0,h.jsx)(`span`,{className:`language-toggle-placeholder`,"aria-hidden":`true`}),onSelect:()=>{n!==e&&t(n)}})),renderTrigger:e=>(0,h.jsx)(`button`,{...e,className:`header-tool-btn`,title:r,"aria-label":r,children:(0,h.jsx)(d,{size:16})})})}function v(){let{t:e}=a(),t=n(e=>e.identity),i=l(t?.address,t?.avatar),o=e(`nav.profile`);return(0,h.jsx)(r,{to:`/profile/`,className:`account-profile-link`,title:o,"aria-label":o,children:t?(0,h.jsx)(`img`,{className:`account-profile-link-avatar`,src:i,alt:``,"aria-hidden":`true`}):(0,h.jsx)(m,{size:18})})}export{u as a,m as i,_ as n,g as r,v as t};
@@ -1 +0,0 @@
1
- import{n as e,o as t,t as n}from"./jsx-runtime-Bt-cYkS5.js";import{B as r}from"./useAppStore-DNhyhD20.js";import{a as i}from"./i18n-C6yaqRst.js";import{n as a,r as o,t as s}from"./AccountMenu-D-wa5tux.js";import"./index-D1mozno-.js";var c=r(`menu`,[[`path`,{d:`M4 5h16`,key:`1tepv9`}],[`path`,{d:`M4 12h16`,key:`1lakjw`}],[`path`,{d:`M4 19h16`,key:`1djgab`}]]),l=t(e(),1);function u(e,t){return typeof t==`boolean`?t:typeof window<`u`&&`matchMedia`in window?window.matchMedia(e).matches:!1}function d(e,t,{getInitialValueInEffect:n}={getInitialValueInEffect:!0}){let[r,i]=(0,l.useState)(n?t:u(e));return(0,l.useEffect)(()=>{try{if(`matchMedia`in window){let t=window.matchMedia(e);i(t.matches);let n=e=>i(e.matches);return t.addEventListener(`change`,n),()=>{t.removeEventListener(`change`,n)}}}catch{return}},[e]),r||!1}function f(e=!1,t={}){let[n,r]=(0,l.useState)(e),i=(0,l.useCallback)(()=>{r(e=>e||(t.onOpen?.(),!0))},[t.onOpen]),a=(0,l.useCallback)(()=>{r(e=>e&&(t.onClose?.(),!1))},[t.onClose]);return[n,{open:i,close:a,toggle:(0,l.useCallback)(()=>{n?a():i()},[a,i,n]),set:r}]}var p=n(),m=(0,l.createContext)(null);function h(){let e=(0,l.useContext)(m);if(!e)throw Error(`useAppShell must be used within AppShell`);return e}function g({sidebar:e,className:t=``,headerTitle:n,headerRight:r,sidebarToggleReplacement:u,defaultHide:h=!1,hideAccountMenu:g=!1,children:_}){let[v,y]=f(!1),[b,x]=(0,l.useState)(h),S=(0,l.useRef)(h),C=d(`(max-width: 768px)`),{t:w}=i(),T=(0,l.useCallback)((e={})=>{y.close(),e.collapse&&x(!0)},[y]);(0,l.useEffect)(()=>{h!==S.current&&(h?T({collapse:!0}):x(!1),S.current=h)},[h,T]);let E=()=>{C?y.toggle():x(!b)},D=()=>{C?y.open():x(!1)},O=C?v:!b;return(0,p.jsx)(m.Provider,{value:{closeSidebar:T,openSidebar:D,isSidebarVisible:O},children:(0,p.jsxs)(`div`,{className:[`app-layout`,t].filter(Boolean).join(` `),children:[(0,p.jsx)(`div`,{className:`sidebar-overlay ${v?`visible`:``}`,onClick:()=>T()}),(0,p.jsx)(`div`,{className:`sidebar ${v?`open`:``} ${b?`collapsed`:``}`,children:e({closeSidebar:T,openSidebar:D})}),(0,p.jsxs)(`div`,{className:`main-content`,children:[(0,p.jsxs)(`header`,{className:`app-header`,children:[(0,p.jsxs)(`div`,{className:`header-left`,children:[u??(0,p.jsx)(`button`,{onClick:E,className:`btn btn-icon sidebar-toggle-btn`,"aria-label":w(C?`appShell.openMenu`:b?`appShell.expandSidebar`:`appShell.collapseSidebar`),children:(0,p.jsx)(c,{size:16})}),n]}),(0,p.jsxs)(`div`,{className:`header-right`,children:[r,(0,p.jsx)(o,{}),(0,p.jsx)(a,{}),!g&&(0,p.jsx)(s,{})]})]}),_]})]})})}export{h as n,f as r,g as t};