@allbluecn/web-app 0.22.0 → 0.22.3

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 (93) hide show
  1. package/package.json +6 -8
  2. package/public/logo/allblue-logo-app-icon.svg +1 -1
  3. package/src/components/changelog/changelog-page.tsx +24 -5
  4. package/src/components/desktop/cloud-login.tsx +91 -0
  5. package/src/components/desktop/conflict-center.tsx +148 -0
  6. package/src/components/desktop/renamed-toast.tsx +23 -0
  7. package/src/components/desktop/shell-widgets.tsx +19 -0
  8. package/src/components/desktop/sync-indicator.tsx +60 -0
  9. package/src/components/desktop/sync-status-card.tsx +60 -0
  10. package/src/components/layout/sidebar-03/app-sidebar.tsx +38 -3
  11. package/src/components/shared-kit/avatars/__tests__/avatar-shape.test.ts +17 -1
  12. package/src/components/shared-kit/avatars/avatar-shape.ts +10 -0
  13. package/src/components/shared-kit/avatars/user-avatar.tsx +2 -2
  14. package/src/lib/auth/auth.config.ts +4 -1
  15. package/src/lib/changelog/sdk-versions.ts +33 -2
  16. package/src/lib/desktop-bridge-types.ts +30 -0
  17. package/src/lib/desktop-detect.ts +2 -0
  18. package/src/lib/vector-store/__tests__/factory.test.ts +0 -7
  19. package/src/lib/vector-store/index.ts +2 -4
  20. package/src/paraglide/messages/_index.d.ts +1 -1
  21. package/src/paraglide/messages/_index.js +32 -16
  22. package/src/paraglide/messages/common_sidebar_usermenu_loginsaas3.d.ts +1 -1
  23. package/src/paraglide/messages/common_sidebar_usermenu_loginsaas3.js +3 -3
  24. package/src/paraglide/messages/en.js +11 -6
  25. package/src/paraglide/messages/{tasks_ai_desktop_notice.d.ts → settings_tabs_sync.d.ts} +5 -5
  26. package/src/paraglide/messages/settings_tabs_sync.js +29 -0
  27. package/src/paraglide/messages/zh.js +11 -6
  28. package/src/providers/index.tsx +6 -0
  29. package/src/routeTree.gen.ts +107 -49
  30. package/src/routes/_nav/route.lazy.tsx +12 -0
  31. package/src/routes/_nav/settings/-settings-tabs.tsx +1 -1
  32. package/src/routes/_nav/settings/sync/route.lazy.tsx +40 -0
  33. package/src/routes/_nav/settings/sync/route.tsx +15 -0
  34. package/src/routes/_nav/tasks/index.lazy.tsx +1 -11
  35. package/src/routes/api/billing-webhook.ts +6 -2
  36. package/src/routes/api/sync/bootstrap.ts +38 -0
  37. package/src/routes/api/sync/pull.ts +96 -0
  38. package/src/routes/api/sync/push.ts +169 -0
  39. package/src/routes/api/sync/snapshot.ts +104 -0
  40. package/src/server/__tests__/fetch-guard.test.ts +2 -2
  41. package/src/server/__tests__/resource-registry.test.ts +4 -2
  42. package/src/server/__tests__/sandbox-entitlement.test.ts +5 -0
  43. package/src/server/__tests__/security-headers.test.ts +25 -0
  44. package/src/server/__tests__/task-assigned-query.test.ts +0 -11
  45. package/src/server/__tests__/task-serverfn-crud.test.ts +1 -22
  46. package/src/server/__tests__/task-serverfn-queries.test.ts +1 -9
  47. package/src/server/ai-gateway/__tests__/chat-stream-builder-memory.test.ts +0 -11
  48. package/src/server/ai-gateway/__tests__/embedding-request.test.ts +1 -1
  49. package/src/server/ai-gateway/__tests__/provider-adapter.test.ts +2 -2
  50. package/src/server/ai-gateway/__tests__/run-store-durable.test.ts +1 -1
  51. package/src/server/ai-gateway/durability/__tests__/pg-stream.test.ts +1 -1
  52. package/src/server/ai-gateway/durability/__tests__/reaper-real-db.test.ts +1 -1
  53. package/src/server/ai-gateway/memory/index.ts +2 -2
  54. package/src/server/ai-gateway/sandbox/__tests__/instance-store.test.ts +1 -1
  55. package/src/server/ai-task/assigned-query.ts +4 -15
  56. package/src/server/desktop-guest.ts +12 -0
  57. package/src/server/fetch-guard.ts +3 -3
  58. package/src/server/middlewares/desktop-token.ts +23 -0
  59. package/src/server/remote-auth.ts +6 -0
  60. package/src/server/serverFns/__tests__/billing.test.ts +58 -1
  61. package/src/server/serverFns/__tests__/update-seats.test.ts +8 -8
  62. package/src/server/serverFns/agents/agent-crud.ts +3 -1
  63. package/src/server/serverFns/auth/session.ts +9 -0
  64. package/src/server/serverFns/billing.ts +10 -4
  65. package/src/server/serverFns/bug/bug-crud.ts +7 -2
  66. package/src/server/serverFns/story/__tests__/labels.test.ts +37 -15
  67. package/src/server/serverFns/story/story-comments.ts +2 -1
  68. package/src/server/serverFns/story/story-crud/activities.ts +1 -1
  69. package/src/server/serverFns/story/story-crud/user-brief.ts +1 -1
  70. package/src/server/serverFns/story/story-crud.ts +2 -1
  71. package/src/server/serverFns/story/story-transfer.ts +2 -1
  72. package/src/server/serverFns/tasks/task-crud.ts +0 -13
  73. package/src/server/serverFns/tasks/task-queries.ts +1 -12
  74. package/src/server/serverFns/testcase/plans.ts +1 -1
  75. package/src/server/serverFns/testcase/run-cases.ts +1 -1
  76. package/src/server/serverFns/testcase/runs.ts +1 -1
  77. package/src/server/serverFns/testcase/testcase-activities.ts +1 -1
  78. package/src/server/serverFns/testcase/testcase-board.ts +1 -1
  79. package/src/server/serverFns/testcase/testcase-crud.ts +1 -1
  80. package/src/server/serverFns/workspace.ts +1 -1
  81. package/src/server/story/types.ts +7 -22
  82. package/src/server/story/user-brief.ts +48 -0
  83. package/src/server/sync/scopes.ts +29 -0
  84. package/src/start.ts +10 -2
  85. package/src/styles/globals.css +10 -0
  86. package/src/__tests__/desktop-connection-validate.test.ts +0 -66
  87. package/src/lib/vector-store/__tests__/sqlite-vec-store.test.ts +0 -143
  88. package/src/lib/vector-store/sqlite-vec-store.ts +0 -224
  89. package/src/paraglide/messages/tasks_ai_desktop_notice.js +0 -29
  90. package/src/routes/__desktop/onboarding/route.lazy.tsx +0 -175
  91. package/src/routes/__desktop/onboarding/route.tsx +0 -11
  92. package/src/routes/_nav/settings/connection/route.lazy.tsx +0 -64
  93. package/src/routes/_nav/settings/connection/route.tsx +0 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allbluecn/web-app",
3
- "version": "0.22.0",
3
+ "version": "0.22.3",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "type": "module",
6
6
  "files": [
@@ -60,7 +60,7 @@
60
60
  "dependencies": {
61
61
  "@ag-ui/core": "1.0.0",
62
62
  "@allbluecn/collab-core": "0.2.0",
63
- "@allbluecn/database": "0.11.0",
63
+ "@allbluecn/database": "0.12.1",
64
64
  "@allbluecn/kernel": "0.12.0",
65
65
  "@ariakit/react": "^0.4.28",
66
66
  "@auth/core": "^0.41.2",
@@ -134,11 +134,11 @@
134
134
  "@tanstack/ai-sandbox-vercel": "1.0.0",
135
135
  "@tanstack/ai-skills": "1.0.0",
136
136
  "@tanstack/ai-utils": "1.0.0",
137
- "@tanstack/db": "0.15.2",
137
+ "@tanstack/db": "1.0.0-rc.1",
138
138
  "@tanstack/hotkeys": "^0.18.0",
139
139
  "@tanstack/markdown": "^1.0.0",
140
140
  "@tanstack/pacer": "^0.23.1",
141
- "@tanstack/query-db-collection": "1.4.8",
141
+ "@tanstack/query-db-collection": "1.5.0",
142
142
  "@tanstack/react-form": "2.0.0-alpha.3",
143
143
  "@tanstack/react-hotkeys": "^0.17.0",
144
144
  "@tanstack/react-pacer": "^0.24.1",
@@ -187,7 +187,6 @@
187
187
  "@vercel/speed-insights": "^2.0.0",
188
188
  "@zumer/snapdom": "^2.12.9",
189
189
  "bcryptjs": "^3.0.3",
190
- "better-sqlite3": "^13.0.1",
191
190
  "class-variance-authority": "^0.7.1",
192
191
  "clsx": "^2.1.1",
193
192
  "cmdk": "^1.1.1",
@@ -235,12 +234,12 @@
235
234
  "remark-gfm": "^4.0.1",
236
235
  "sanitize-html": "^2.18.0",
237
236
  "shiki": "^4.2.0",
238
- "sqlite-vec": "^0.1.9",
239
237
  "start-authjs": "^1.0.2",
240
238
  "tailwind-merge": "^3.5.0",
241
239
  "tw-animate-css": "^1.4.0",
242
240
  "yjs": "^13.6.33",
243
- "zod": "^4.6.7"
241
+ "zod": "^4.6.7",
242
+ "@allbluecn/sync-core": "0.5.4"
244
243
  },
245
244
  "devDependencies": {
246
245
  "@babel/parser": "^8.0.4",
@@ -261,7 +260,6 @@
261
260
  "@testing-library/react": "^16.3.2",
262
261
  "@testing-library/user-event": "^14.6.1",
263
262
  "@types/bcryptjs": "^2.4.6",
264
- "@types/better-sqlite3": "^7.6.13",
265
263
  "@types/file-saver": "^2.0.7",
266
264
  "@types/is-hotkey": "^0.1.10",
267
265
  "@types/jsdom": "^28.0.3",
@@ -1,6 +1,6 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
2
2
  <rect width="1024" height="1024" rx="229" fill="#ffffff"/>
3
- <g transform="translate(165.96 186.65) scale(0.75692)">
3
+ <g transform="translate(123.41 146.54) scale(0.85)">
4
4
  <path d="M912.82 421.43C912.92 423.04 913.03 424.64 913.13 426.24C913.41 436.02 913.68 445.8 913.96 455.57C913.82 457.16 913.67 458.75 913.53 460.34C913.39 464.09 913.25 467.85 913.11 471.61C913.01 475.69 912.9 479.76 912.8 483.84C905.03 614.78 836.48 739.86 721.87 806.58C713.75 811.31 705.22 814.88 696.81 818.82C681.84 825.84 666.92 832.57 651.26 838.16C619.75 849.42 586.6 854.7 553.54 858.44C552.2 858.57 550.85 858.7 549.51 858.83C545.18 858.97 540.85 859.11 536.52 859.25C533.01 859.29 529.5 859.33 525.98 859.38C520.6 859.19 515.22 859 509.83 858.81C507.65 858.71 505.47 858.6 503.29 858.49C501.39 858.34 499.48 858.18 497.58 858.02C496.24 857.91 494.9 857.8 493.57 857.69C461.02 854.33 429.45 847.25 399.28 834.59C396.56 833.45 393.57 832.92 390.9 831.7C389.25 830.95 388.09 829.59 386.42 828.83C375.89 824.08 364.9 820.07 354.95 814.1C348.49 810.22 340.67 802.9 334.06 800.22C327.67 797.62 320.09 797.29 313.41 795.58C302.7 792.84 291.97 788.87 281.95 784.16C218.81 754.5 165.31 704.82 137.47 640.13C125.41 612.09 117.59 582.91 112.22 552.9C105.08 513.03 104.34 471.16 86.98 433.78C80.1 418.98 71.48 405.92 59 395.26C43.31 381.86 27.91 375.15 13.87 358.51C8.99 352.73 4.81 346.43 2.3 339.24C1.43 336.74 1.16 333.94 0.14 331.58C0.14 326.94 0.14 322.31 0.14 317.67C1.35 314.89 1.52 311.38 2.36 308.37C4.36 301.27 6.96 294.29 10.08 287.6C22.74 260.44 38.53 234.64 55.66 210.06C139.44 89.85 287.99 10.93 433.59 2.96C435.46 2.86 437.32 2.77 439.19 2.68C470.99 -0.23 503.07 3.72 534.54 7.84C556.37 10.7 577.66 15.99 598.74 22.39C618.17 28.3 637.25 34.9 655.82 43.23C745.71 83.53 819.5 157.02 863.49 244.8C879.78 277.29 893.81 312.84 901.66 348.38C907 372.56 910.26 396.87 912.82 421.43ZM493.57 182.93C494.93 182.82 496.3 182.72 497.66 182.61C505.74 182.61 513.82 182.61 521.9 182.61C524.07 182.72 526.24 182.82 528.41 182.92C561.81 185.44 594.93 192.49 625.58 206.35C635.18 210.69 644.79 215.62 653.81 221.07C658.57 223.94 663.03 228.33 668.22 230.26C673.63 226.68 683.25 209.59 686.8 203.36C703.34 174.33 713.07 141.46 709.77 107.79C709.63 106.48 709.5 105.18 709.37 103.87C709.14 100.94 709.21 96.23 707.38 93.76C704.78 90.25 698.39 87.71 694.72 85.35C685.43 79.4 676.09 73.48 666.3 68.38C624.09 46.36 577.59 31.74 530.61 24.81C522.06 23.54 513.55 22.01 504.89 21.63C503.54 21.51 502.2 21.38 500.86 21.25C493.3 20.83 485.75 20.42 478.2 20C476.56 19.89 474.93 19.78 473.3 19.68C450.82 17.58 427.93 20.22 405.63 22.9C371.88 26.95 341.56 33.48 309.14 43.67C296.56 47.62 283.89 52.16 271.81 57.45C268.64 58.83 261.75 60.48 260.51 63.62C264.92 66.44 271.7 66.57 276.75 68.08C286.8 71.09 296.71 75.83 305.56 81.45C336.54 101.12 361.81 126.41 378.02 159.73C382.31 168.53 386.57 177.57 389.71 186.86C391.66 192.64 392.43 199.06 394.6 204.64C398.04 204.62 401.67 202.32 404.95 201.13C413.17 198.15 421.55 195.3 430 193.03C450.85 187.43 472.07 184.53 493.57 182.93ZM187.04 542.19C186.91 540.33 186.78 538.46 186.65 536.59C186.84 532.9 186.2 529.26 186.27 525.58C186.54 511.2 187.22 496.43 189.24 482.19C199.47 410.09 228.1 344.53 276.74 290.11C281.56 284.72 286.16 278.95 291.42 273.99C309.99 256.47 329.78 240.3 351.93 227.51C359.31 223.25 366.61 218.8 374.29 215.08C378.39 213.1 384.07 211.94 386.99 208.33C387.19 198.94 377.68 178.18 373.32 169.26C355.91 133.67 330.18 103.03 294.94 83.94C284.52 78.3 273.61 73.83 262.29 70.33C258.28 69.09 252.68 66.98 248.55 68.23C244.02 69.6 239.34 72.89 235.11 75.04C221.9 81.75 209.46 89.66 197.15 97.86C189.2 103.16 180.88 107.88 173.15 113.55C122.61 150.58 78.65 197.8 47.79 252.38C40.47 265.32 33.02 277.98 27.12 291.68C22.72 301.89 16.43 318.01 18.24 329.1C21.06 346.46 41.16 360.45 54.63 369.63C72.08 381.53 88.02 396.33 98.55 414.87C122.95 457.88 122.14 510.27 131.72 557.48C135.8 577.54 139.77 597.71 146.98 616.95C164.32 663.26 195.1 705.41 234.93 734.76C247.38 743.94 259.84 752.27 273.72 759.2C277.82 761.24 284.79 766.61 289.13 765.47C287.16 761.37 278.25 754.14 274.66 750.17C263.65 737.97 252.77 725.18 243.61 711.49C207.97 658.28 192.06 605.7 187.04 542.19ZM718.15 117.52C718.18 120.49 718.2 123.46 718.22 126.43C717.46 136.86 716.88 147.28 714.61 157.54C710.82 174.7 702.85 191 694.8 206.49C691.82 212.22 688.37 217.72 684.34 222.76C681.34 226.51 676.68 230.85 675.33 235.44C680.4 240.92 687.28 245.04 692.79 250.16C704.7 261.25 715.61 273.36 725.75 286.09C749.66 316.1 767.03 353.54 774.82 391.04C777.72 404.95 778.9 418.75 780.67 432.78C780.72 438.18 780.77 443.59 780.83 448.99C780.62 451.46 780.42 453.92 780.22 456.39C780.1 458.52 779.98 460.64 779.87 462.77C779.99 464.5 779.95 464.53 781.34 465.43C784.02 465.54 786.7 465.66 789.38 465.77C801.15 466.66 813.28 466.98 825.04 465.79C826.96 465.68 828.88 465.57 830.79 465.47C850.7 465.45 874.53 460.05 892.99 452.64C895.33 445.14 893.36 435.28 893.03 427.51C892.24 408.85 890.32 389.91 886.51 371.61C875.06 316.67 855.45 263.3 825.31 215.75C812.61 195.72 799.1 175.14 782.34 158.19C767.47 143.15 752.77 128.48 736.46 114.95C732.34 111.54 721.34 101.22 716.84 100.67C717.28 106.29 717.72 111.9 718.15 117.52ZM514.64 364.49C519.5 364.49 524.36 364.48 529.22 364.48C563.64 368.65 596.33 384.31 611.49 417.37C616.99 429.35 619.78 441.71 621.78 454.66C621.9 456.05 622.03 457.44 622.16 458.83C622.17 460.94 622.18 463.05 622.18 465.17C622.04 466.8 621.9 468.43 621.76 470.06C618.66 503.22 595.23 538.24 560.43 543.86C552.87 545.08 544.33 545.22 536.87 543.15C509.15 535.43 492.16 506.74 509.02 480.29C512.66 474.57 519.84 467.6 527.17 471.71C528.62 472.52 530.07 473.94 530.56 475.58C531.59 479.06 528.57 481.57 526.76 484.19C523.83 488.43 521.98 492.45 521.68 497.65C521.68 499.83 521.67 502.01 521.67 504.19C523.6 527.88 550.02 530.06 567.56 522.56C587.94 513.84 600.08 489.72 601.61 468.48C601.62 464.39 601.62 460.29 601.62 456.2C601.57 450.19 600.01 444.32 598.49 438.54C594.42 423.05 585.53 407.69 572.6 397.9C550.9 381.46 518.84 377.57 493.2 385.06C450.3 397.59 419.32 435.92 407.95 477.96C403.48 494.49 401.61 512.55 402.5 529.63C403.14 541.91 406.11 554.6 409.85 566.28C432.39 636.77 500.5 676.66 572.99 670.86C574.32 670.73 575.66 670.61 576.99 670.48C585.6 670.28 594.5 667.86 602.79 665.69C619.35 661.34 634.41 655.75 649.35 647.3C718.36 608.26 757.1 538.55 759.19 459.93C759.4 452.24 760.69 444.53 759.98 436.83C759.86 433.88 759.75 430.92 759.64 427.96C759.49 425.25 759.34 422.53 759.2 419.81C759.08 418.45 758.96 417.09 758.84 415.73C757.72 395.27 751.43 375.82 744.23 356.82C719.23 290.85 664.95 240.51 599.81 215.16C592.66 212.38 585.11 210.77 577.73 208.75C562.15 204.48 545.42 201.03 529.18 200.74C527.3 200.61 525.43 200.47 523.55 200.34C508.74 198.48 493.06 200 478.27 201.41C432.35 205.78 387.73 222.74 349.49 248.57C337.91 256.39 326.64 265.14 316.44 274.71C313.21 277.74 308.64 280.6 306.1 284.06C309.51 288.45 323.4 292.81 329.01 296.26C346.82 307.23 364.64 318.41 380.18 332.61C395 346.15 408.61 361.07 421.19 376.68C424.81 381.17 427.82 386.38 430.99 391.21C432.51 393.54 433.34 396.53 435.62 397.97C438.8 396.11 441.43 392.85 444.37 390.49C448.2 387.42 452.75 384.46 457.07 382.13C476.8 371.52 492.29 366.73 514.64 364.49ZM206.07 543.81C206.18 545.13 206.3 546.45 206.42 547.77C206.56 549.42 206.7 551.08 206.84 552.73C206.97 554.09 207.1 555.46 207.22 556.82C208.75 587.42 217.32 618.74 229.86 646.59C233.67 655.06 237.48 668.35 244.3 674.7C248.5 674.4 253.61 670.6 257.46 668.8C267.12 664.28 277.07 660.52 286.69 655.88C304.27 647.41 321.86 638.41 338.62 628.38C346.33 623.76 353.75 618.59 361.45 613.92C373.55 606.58 386.27 599.78 397.53 591.17C396.7 584.09 392.15 576.86 390.09 569.96C386.6 558.27 385.08 546.09 383.12 534.09C383.01 532.7 382.91 531.32 382.8 529.93C382.8 525.39 382.8 520.85 382.8 516.31C382.92 514.13 383.03 511.95 383.15 509.77C383.3 507.85 383.44 505.92 383.59 503.99C383.72 502.68 383.86 501.36 383.99 500.05C387.62 476.41 393.83 452.71 406.6 432.14C410.26 426.24 414.34 420.51 418.96 415.31C422.09 411.79 426.6 408.12 428.75 404.01C423.15 392.59 414.55 382.49 406.58 372.65C386.28 347.58 361.41 325.17 333.7 308.56C325.97 303.92 307.5 291.43 299.46 290.31C293.08 294.95 288.07 302.54 283.01 308.55C272.5 321.03 262.6 333.83 254.46 348.01C227.76 394.48 212.55 439.71 207.31 493.16C205.65 510.12 204.84 526.78 206.07 543.81ZM816.94 474.28C810.72 474.33 804.51 474.38 798.29 474.42C794.01 474.23 789.72 474.04 785.44 473.85C783.65 473.78 781.85 473.71 780.06 473.65C779.98 473.8 779.9 473.96 779.82 474.11C778.27 480.1 778.41 486.95 777.39 493.16C775.21 506.55 771.82 519.48 767.88 532.43C764.89 542.25 760.29 551.72 755.84 560.94C739.05 595.71 716.36 625.15 684.87 647.99C672.14 657.21 658.79 665.87 644.45 672.36C640.55 674.12 636.43 675.95 632.35 677.25C629.55 678.14 626.58 678.11 624.02 679.56C624.13 683.59 626.94 686.68 628.68 690.3C631.87 696.92 634.91 703.67 637.71 710.45C646.43 731.52 653.6 753.15 658.57 775.43C660.45 783.85 661.15 792.48 662.45 800.99C662.98 804.48 662.8 808.75 664.98 811.45C672.92 809.52 679.75 804.41 687.05 800.92C704.15 792.76 720.13 783.37 735.63 772.49C806.65 722.63 859.72 644.07 880.43 559.79C886.19 536.35 891.08 512.51 891.99 488.3C892.24 481.78 894.47 467.13 892.62 461.55C888.62 461.6 884.42 464.05 880.53 465.2C875.2 466.79 869.75 467.99 864.32 469.2C848.64 472.68 832.81 472.65 816.94 474.28ZM517.98 839.83C521.68 839.84 525.39 839.86 529.1 839.87C570.52 840.69 615.92 830.57 654.44 815.48C656.78 805.45 652.44 794.04 650.83 784.11C646.7 758.8 638.19 733.8 628.28 710.2C625.69 704.03 622.06 698.44 619.35 692.36C617.98 689.3 617.09 685.12 614.73 682.76C605.25 683.65 595.84 686.76 586.36 688.03C574.03 689.68 561.94 689.08 549.6 689.43C547.67 689.29 545.74 689.15 543.81 689.01C501.53 683.39 462.28 669.63 431.63 638.56C422.63 629.44 415.77 619.45 408.57 608.93C406.16 605.41 404.3 601.27 401.58 598.04C394.71 599.37 378.41 613 371.47 617.46C347.08 633.15 322.05 647.46 296.12 660.41C286.68 665.13 276.85 669.6 267.03 673.45C261.1 675.77 253.05 677.55 248.4 682.08C249.64 688.81 255.93 696.23 259.72 701.88C270.21 717.54 282.56 731.83 295.93 745.1C345.45 794.22 410.48 826.76 479.4 837C492.01 838.87 505.24 839.34 517.98 839.83Z" fill="#1a1a1a" fill-rule="evenodd" stroke="#1a1a1a" stroke-width="60" stroke-linejoin="round"/>
5
5
  <path d="M439.19 2.68C437.32 2.77 435.46 2.86 433.59 2.96C433.73 2.78 433.86 2.61 434 2.43C435.73 2.51 437.46 2.6 439.19 2.68ZM473.3 19.68C474.93 19.78 476.56 19.89 478.2 20C476.7 20.09 475.2 20.17 473.71 20.26C473.57 20.07 473.44 19.87 473.3 19.68ZM500.86 21.25C502.2 21.38 503.54 21.51 504.89 21.63C503.68 21.72 502.47 21.8 501.26 21.88C501.13 21.67 500.99 21.46 500.86 21.25ZM709.37 103.87C709.5 105.18 709.63 106.48 709.77 107.79C709.56 107.66 709.35 107.52 709.15 107.38C709.22 106.21 709.29 105.04 709.37 103.87ZM718.15 117.52C718.39 117.65 718.63 117.79 718.87 117.92C718.87 120.62 718.87 123.32 718.87 126.03C718.65 126.16 718.44 126.3 718.22 126.43C718.2 123.46 718.18 120.49 718.15 117.52ZM497.66 182.61C496.3 182.72 494.93 182.82 493.57 182.93C493.7 182.73 493.84 182.54 493.97 182.35C495.2 182.44 496.43 182.53 497.66 182.61ZM521.9 182.61C523.94 182.53 525.97 182.44 528.01 182.35C528.14 182.54 528.28 182.73 528.41 182.92C526.24 182.82 524.07 182.72 521.9 182.61ZM523.55 200.34C525.43 200.47 527.3 200.61 529.18 200.74C527.44 200.82 525.7 200.91 523.96 200.99C523.82 200.77 523.69 200.56 523.55 200.34ZM529.22 364.48C524.36 364.48 519.5 364.49 514.64 364.49C514.77 364.29 514.91 364.09 515.04 363.89C519.63 363.89 524.23 363.89 528.82 363.89C528.95 364.09 529.09 364.28 529.22 364.48ZM758.84 415.73C758.96 417.09 759.08 418.45 759.2 419.81C758.99 419.68 758.79 419.54 758.58 419.41C758.67 418.18 758.75 416.96 758.84 415.73ZM912.82 421.43C913.01 421.57 913.19 421.71 913.38 421.84C913.3 423.31 913.21 424.77 913.13 426.24C913.03 424.64 912.92 423.04 912.82 421.43ZM759.64 427.96C759.75 430.92 759.86 433.88 759.98 436.83C759.78 436.7 759.59 436.56 759.39 436.43C759.47 433.61 759.56 430.78 759.64 427.96ZM780.67 432.78C780.72 438.18 780.77 443.59 780.83 448.99C780.77 443.59 780.72 438.18 780.67 432.78ZM621.78 454.66C621.99 454.8 622.21 454.93 622.43 455.07C622.34 456.32 622.25 457.57 622.16 458.83C622.03 457.44 621.9 456.05 621.78 454.66ZM913.96 455.57C914.04 457.03 914.11 458.48 914.19 459.93C913.97 460.07 913.75 460.2 913.53 460.34C913.67 458.75 913.82 457.16 913.96 455.57ZM601.62 456.2C601.62 460.29 601.62 464.39 601.61 468.48C601.62 464.39 601.62 460.29 601.62 456.2ZM780.22 456.39C780.3 458.38 780.38 460.37 780.47 462.36C780.27 462.5 780.07 462.63 779.87 462.77C779.98 460.64 780.1 458.52 780.22 456.39ZM622.18 465.17C622.26 466.66 622.35 468.16 622.43 469.66C622.2 469.79 621.98 469.93 621.76 470.06C621.9 468.43 622.04 466.8 622.18 465.17ZM781.34 465.43C783.89 465.35 786.43 465.28 788.97 465.2C789.11 465.39 789.24 465.58 789.38 465.77C786.7 465.66 784.02 465.54 781.34 465.43ZM830.79 465.47C828.88 465.57 826.96 465.68 825.04 465.79C825.17 465.59 825.31 465.4 825.45 465.2C827.23 465.29 829.01 465.38 830.79 465.47ZM913.11 471.61C913.2 475.55 913.29 479.49 913.38 483.43C913.19 483.57 912.99 483.7 912.8 483.84C912.9 479.76 913.01 475.69 913.11 471.61ZM785.44 473.85C783.57 473.94 781.69 474.02 779.82 474.11C779.9 473.96 779.98 473.8 780.06 473.65C781.85 473.71 783.65 473.78 785.44 473.85ZM798.29 474.42C804.51 474.38 810.72 474.33 816.94 474.28C814.3 475.59 801.18 475.53 798.29 474.42ZM521.68 497.65C521.68 499.83 521.67 502.01 521.67 504.19C521.67 502.01 521.68 499.83 521.68 497.65ZM383.99 500.05C383.86 501.36 383.72 502.68 383.59 503.99C383.5 502.81 383.42 501.63 383.34 500.45C383.56 500.32 383.78 500.18 383.99 500.05ZM383.15 509.77C383.03 511.95 382.92 514.13 382.8 516.31C382.71 514.27 382.62 512.22 382.53 510.18C382.74 510.04 382.95 509.91 383.15 509.77ZM382.8 529.93C382.91 531.32 383.01 532.7 383.12 534.09C382.92 533.95 382.73 533.82 382.53 533.68C382.62 532.43 382.71 531.18 382.8 529.93ZM186.65 536.59C186.78 538.46 186.91 540.33 187.04 542.19C186.82 542.06 186.61 541.92 186.4 541.79C186.49 540.06 186.57 538.33 186.65 536.59ZM206.07 543.81C206.27 543.95 206.47 544.08 206.67 544.22C206.58 545.4 206.5 546.59 206.42 547.77C206.3 546.45 206.18 545.13 206.07 543.81ZM206.84 552.73C207.05 552.86 207.26 553 207.48 553.13C207.39 554.36 207.31 555.59 207.22 556.82C207.1 555.46 206.97 554.09 206.84 552.73ZM576.99 670.48C575.66 670.61 574.32 670.73 572.99 670.86C573.12 670.66 573.26 670.45 573.39 670.24C574.59 670.32 575.79 670.4 576.99 670.48ZM543.81 689.01C545.74 689.15 547.67 689.29 549.6 689.43C547.81 689.52 546.01 689.6 544.22 689.69C544.08 689.47 543.95 689.24 543.81 689.01ZM517.98 839.83C521.68 839.84 525.39 839.86 529.1 839.87C525.39 839.86 521.68 839.84 517.98 839.83ZM493.57 857.69C494.9 857.8 496.24 857.91 497.58 858.02C496.37 858.1 495.17 858.19 493.97 858.27C493.84 858.08 493.7 857.88 493.57 857.69ZM503.29 858.49C505.47 858.6 507.65 858.71 509.83 858.81C507.79 858.9 505.74 858.99 503.7 859.08C503.56 858.88 503.43 858.69 503.29 858.49ZM553.54 858.44C553.4 858.66 553.27 858.87 553.13 859.08C551.93 858.99 550.72 858.91 549.51 858.83C550.85 858.7 552.2 858.57 553.54 858.44ZM525.98 859.38C529.5 859.33 533.01 859.29 536.52 859.25C536.38 859.46 536.25 859.67 536.11 859.89C532.87 859.89 529.63 859.89 526.39 859.89C526.25 859.72 526.12 859.55 525.98 859.38Z" fill="#1a1a1a" fill-rule="evenodd" stroke="#1a1a1a" stroke-width="60" stroke-linejoin="round"/>
6
6
  </g>
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { cn } from '@/lib/utils'
19
- import { ScrollText, AlertTriangle, ArrowLeft, ChevronDown } from 'lucide-react'
19
+ import { ScrollText, AlertTriangle, ArrowLeft, ChevronDown, Cpu } from 'lucide-react'
20
20
  import ReactMarkdown from 'react-markdown'
21
21
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
22
  import { Route } from '@/routes/_nav/changelog/route'
@@ -426,11 +426,30 @@ function SdkGroupSection({
426
426
  />
427
427
  </span>
428
428
  ) : (
429
- <span className="text-base">{group.icon}</span>
429
+ // 其他核心组:lucide Cpu 图标(size-4 对齐前两组 h-4 图形字标,
430
+ // 用户裁决弃用 emoji)
431
+ <Cpu className="size-4 mt-0.5 shrink-0 text-foreground/80" strokeWidth={3} />
430
432
  )}
431
- <span className="font-brand text-xs mt-1 font-semibold text-foreground/80">
432
- {SDK_GROUP_LABELS[group.label]?.() ?? group.label}
433
- </span>
433
+ {/* 纯文本分组标题(其他核心)拆两段:「其他核心/Other Core」承当字标
434
+ 角色用 text-base,SDK 尾缀与其余两组(图形字标 + SDK)保持同号
435
+ text-sm;z/en 文案均以 " SDK" 结尾,正则拆分双语言通吃 */}
436
+ {(() => {
437
+ const label = SDK_GROUP_LABELS[group.label]?.() ?? group.label
438
+ const m = label.match(/^(.*\S)\s+(SDK)$/)
439
+ if (!m) {
440
+ return (
441
+ <span className="font-brand text-sm mt-0.5 font-semibold text-foreground/80">
442
+ {label}
443
+ </span>
444
+ )
445
+ }
446
+ return (
447
+ <span className="mt-0.5 flex items-baseline gap-1.5 font-semibold text-foreground/80">
448
+ <span className="font-brand text-lg">{m[1]}</span>
449
+ <span className="font-brand text-sm">{m[2]}</span>
450
+ </span>
451
+ )
452
+ })()}
434
453
  </span>
435
454
  {isAccordion && (
436
455
  <ChevronDown
@@ -0,0 +1,91 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+
5
+ // 桌面云端登录桥(迁移设计 §3.6):表单调用 desktopBridge.cloudLogin
6
+ // (main 侧 desktopLogin 走云端 credentials),成功后引擎自动拾取会话
7
+ // (makeFetchApi 每次同步重读 cloud-session.json)。
8
+ // 独立于 web 登录页——web 表单走本地 serverFn authjs,二者不混用。
9
+ import { useEffect, useState } from "react"
10
+ import { getDesktopBridge } from "@/lib/desktop-bridge-types"
11
+ import { Button } from "@/components/ui/button"
12
+ import { Input } from "@/components/ui/input"
13
+ import { Label } from "@/components/ui/label"
14
+
15
+ export function DesktopCloudLogin() {
16
+ const [email, setEmail] = useState("")
17
+ const [password, setPassword] = useState("")
18
+ const [busy, setBusy] = useState(false)
19
+ const [error, setError] = useState<string | null>(null)
20
+ const [ok, setOk] = useState(false)
21
+ const [mounted, setMounted] = useState(false)
22
+
23
+ // 环境自判(与 sync-indicator/conflict-center 同构,mounted 模式保证 SSR/客户端
24
+ // 首帧一致):仅桌面壳(存在 desktopBridge)时渲染。web/云端部署下本组件由
25
+ // shell-widgets 无条件挂载,缺此守卫会让登录表单以 fixed 容器悬浮在浏览器
26
+ // 每个页面右下角遮挡内容区交互(2026-10-30 实锤)。
27
+ useEffect(() => {
28
+ setMounted(true)
29
+ }, [])
30
+
31
+ const submit = async () => {
32
+ const bridge = getDesktopBridge()
33
+ if (!bridge || !email || !password || busy) return
34
+ setBusy(true)
35
+ setError(null)
36
+ try {
37
+ const res = await bridge.cloudLogin(email, password)
38
+ if (res.ok) {
39
+ setOk(true)
40
+ } else {
41
+ setError(res.error ?? "登录失败")
42
+ }
43
+ } catch (e) {
44
+ setError(e instanceof Error ? e.message : String(e))
45
+ } finally {
46
+ setBusy(false)
47
+ }
48
+ }
49
+
50
+ if (!mounted || !getDesktopBridge()) return null
51
+
52
+ if (ok) {
53
+ return (
54
+ <p className="text-xs text-emerald-600" data-testid="desktop-cloud-login-ok">
55
+ 云端登录成功,同步已启用
56
+ </p>
57
+ )
58
+ }
59
+
60
+ return (
61
+ <div className="space-y-2" data-testid="desktop-cloud-login">
62
+ <p className="text-xs text-muted-foreground">登录云端账号以启用多端同步</p>
63
+ <div className="space-y-1.5">
64
+ <Label htmlFor="desktop-cloud-email" className="text-xs">邮箱</Label>
65
+ <Input
66
+ id="desktop-cloud-email"
67
+ type="email"
68
+ value={email}
69
+ onChange={(e) => setEmail(e.target.value)}
70
+ placeholder="you@example.com"
71
+ className="h-8 text-xs"
72
+ />
73
+ </div>
74
+ <div className="space-y-1.5">
75
+ <Label htmlFor="desktop-cloud-password" className="text-xs">密码</Label>
76
+ <Input
77
+ id="desktop-cloud-password"
78
+ type="password"
79
+ value={password}
80
+ onChange={(e) => setPassword(e.target.value)}
81
+ className="h-8 text-xs"
82
+ onKeyDown={(e) => e.key === "Enter" && void submit()}
83
+ />
84
+ </div>
85
+ {error && <p className="text-xs text-destructive">{error}</p>}
86
+ <Button size="sm" className="w-full" disabled={busy || !email || !password} onClick={() => void submit()}>
87
+ {busy ? "登录中…" : "登录云端"}
88
+ </Button>
89
+ </div>
90
+ )
91
+ }
@@ -0,0 +1,148 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+
5
+ // 桌面冲突中心(迁移设计 §3.5):列出未决冲突(双方载荷),三选操作
6
+ // (采用我的 / 保留云端 / 都保留副本)。数据源 desktopBridge.conflictRpc。
7
+ // 仅 DEPLOY_TARGET=desktop 挂载;云端部署零成本。
8
+ import { useCallback, useEffect, useState } from "react"
9
+ import { getDesktopBridge } from "@/lib/desktop-bridge-types"
10
+ import { Button } from "@/components/ui/button"
11
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
12
+ import { Badge } from "@/components/ui/badge"
13
+
14
+ interface ConflictItem {
15
+ id: string
16
+ table: string
17
+ pk: string
18
+ reason: string
19
+ localPayload: Record<string, unknown> | null
20
+ remotePayload: Record<string, unknown> | null
21
+ detectedAt: string
22
+ }
23
+
24
+ const REASON_LABELS: Record<string, string> = {
25
+ versionDiverged: "双方均已修改",
26
+ localDirtyGuard: "本地有未同步修改",
27
+ uniqueViolation: "编号冲突",
28
+ forbidden: "权限不足",
29
+ }
30
+
31
+ function payloadTitle(p: Record<string, unknown> | null): string {
32
+ if (!p) return "(无)"
33
+ return String(p.name ?? p.title ?? p.identifier ?? p.pk ?? "未知")
34
+ }
35
+
36
+ export function DesktopConflictCenter() {
37
+ const [items, setItems] = useState<ConflictItem[]>([])
38
+ const [busyId, setBusyId] = useState<string | null>(null)
39
+ const [error, setError] = useState<string | null>(null)
40
+ const [loaded, setLoaded] = useState(false)
41
+
42
+ const refresh = useCallback(async () => {
43
+ const bridge = getDesktopBridge()
44
+ if (!bridge) return
45
+ try {
46
+ const list = (await bridge.conflictRpc("list")) as ConflictItem[]
47
+ setItems(list)
48
+ setLoaded(true)
49
+ } catch (e) {
50
+ setError(e instanceof Error ? e.message : String(e))
51
+ }
52
+ }, [])
53
+
54
+ useEffect(() => {
55
+ void refresh()
56
+ }, [refresh])
57
+
58
+ const resolve = useCallback(
59
+ async (action: "applyLocal" | "keepRemote", item: ConflictItem) => {
60
+ setBusyId(item.id)
61
+ try {
62
+ await getDesktopBridge()!.conflictRpc(action, item.id, item.table)
63
+ await refresh()
64
+ } catch (e) {
65
+ setError(e instanceof Error ? e.message : String(e))
66
+ } finally {
67
+ setBusyId(null)
68
+ }
69
+ },
70
+ [refresh],
71
+ )
72
+
73
+ const keepBoth = useCallback(
74
+ async (item: ConflictItem) => {
75
+ setBusyId(item.id)
76
+ try {
77
+ const newPk = `${item.pk}-copy-${Date.now()}`
78
+ await getDesktopBridge()!.conflictRpc("keepBoth", item.id, item.table, newPk)
79
+ await refresh()
80
+ } catch (e) {
81
+ setError(e instanceof Error ? e.message : String(e))
82
+ } finally {
83
+ setBusyId(null)
84
+ }
85
+ },
86
+ [refresh],
87
+ )
88
+
89
+ if (!loaded) return null
90
+ if (items.length === 0) return null
91
+
92
+ return (
93
+ <div className="space-y-3 p-4" data-testid="desktop-conflict-center">
94
+ <div className="flex items-center gap-2">
95
+ <h3 className="text-sm font-semibold">同步冲突</h3>
96
+ <Badge variant="destructive">{items.length}</Badge>
97
+ </div>
98
+ {error && <p className="text-xs text-destructive">{error}</p>}
99
+ {items.map((item) => (
100
+ <Card key={item.id}>
101
+ <CardHeader className="p-3 pb-0">
102
+ <CardTitle className="text-xs font-medium text-muted-foreground">
103
+ {REASON_LABELS[item.reason] ?? item.reason} · {item.table} · {item.pk}
104
+ </CardTitle>
105
+ </CardHeader>
106
+ <CardContent className="space-y-2 p-3 pt-2">
107
+ <div className="grid grid-cols-2 gap-2 text-xs">
108
+ <div className="rounded border p-2">
109
+ <p className="mb-1 font-medium">本地</p>
110
+ <p className="text-muted-foreground">{payloadTitle(item.localPayload)}</p>
111
+ </div>
112
+ <div className="rounded border p-2">
113
+ <p className="mb-1 font-medium">云端</p>
114
+ <p className="text-muted-foreground">{payloadTitle(item.remotePayload)}</p>
115
+ </div>
116
+ </div>
117
+ <div className="flex gap-2">
118
+ <Button
119
+ size="sm"
120
+ variant="outline"
121
+ disabled={busyId === item.id}
122
+ onClick={() => void resolve("applyLocal", item)}
123
+ >
124
+ 采用我的
125
+ </Button>
126
+ <Button
127
+ size="sm"
128
+ variant="outline"
129
+ disabled={busyId === item.id}
130
+ onClick={() => void resolve("keepRemote", item)}
131
+ >
132
+ 保留云端
133
+ </Button>
134
+ <Button
135
+ size="sm"
136
+ variant="outline"
137
+ disabled={busyId === item.id}
138
+ onClick={() => void keepBoth(item)}
139
+ >
140
+ 都保留
141
+ </Button>
142
+ </div>
143
+ </CardContent>
144
+ </Card>
145
+ ))}
146
+ </div>
147
+ )
148
+ }
@@ -0,0 +1,23 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+
5
+ // 编号重发号提示(迁移设计 §3.5):订阅 desktopBridge.onRenamed,服务端
6
+ // 因离线撞号重发号后提示「编号已调整为 X」。数据层回写已由引擎完成
7
+ // (sync-core 0.5.3+),此处仅补用户感知。
8
+ import { useEffect } from "react"
9
+ import { toast } from "@/components/ui/toast"
10
+ import { getDesktopBridge } from "@/lib/desktop-bridge-types"
11
+
12
+ export function DesktopRenamedToast() {
13
+ useEffect(() => {
14
+ const bridge = getDesktopBridge()
15
+ if (!bridge?.onRenamed) return
16
+ return bridge.onRenamed((info) => {
17
+ const label = info.table === "bugs" ? "BUG" : "需求"
18
+ toast.info(`${label}编号已调整:${info.from} → ${info.to}`)
19
+ })
20
+ }, [])
21
+
22
+ return null
23
+ }
@@ -0,0 +1,19 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+
5
+ // 桌面壳挂载组件(迁移设计 §3.7):DEPLOY_TARGET=desktop 时挂载同步状态
6
+ // 指示器 + 冲突中心。云端部署零成本(组件内部自判返回 null)。
7
+ // 云登录入口在侧边栏用户菜单(isLocalGuest → /login)与 /settings/sync
8
+ // 设置页卡片,不在壳层悬浮表单(2026-10-31 用户裁决移除右下角表单)。
9
+ import { DesktopSyncIndicator } from "@/components/desktop/sync-indicator"
10
+ import { DesktopConflictCenter } from "@/components/desktop/conflict-center"
11
+
12
+ export function DesktopShellWidgets() {
13
+ return (
14
+ <div className="fixed right-4 bottom-4 z-50 w-80 space-y-3">
15
+ <DesktopSyncIndicator />
16
+ <DesktopConflictCenter />
17
+ </div>
18
+ )
19
+ }
@@ -0,0 +1,60 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+
5
+ // 桌面同步状态指示器(迁移设计 §3.7 最小 UI):顶栏小圆点 + 同步状态。
6
+ // 仅 DEPLOY_TARGET=desktop 渲染;云端部署零成本(组件返回 null)。
7
+ // 数据源:desktopBridge.onSyncState(backend parentPort 上报的引擎状态机)。
8
+ import { useEffect, useState } from "react"
9
+ import { getDesktopBridge } from "@/lib/desktop-bridge-types"
10
+
11
+ interface SyncState {
12
+ phase: "idle" | "pushing" | "pulling"
13
+ pending: number
14
+ lastError: string | null
15
+ lastSyncedAt: string | null
16
+ }
17
+
18
+ const LABELS: Record<SyncState["phase"], string> = {
19
+ idle: "已同步",
20
+ pushing: "推送中",
21
+ pulling: "拉取中",
22
+ }
23
+
24
+ export function DesktopSyncIndicator() {
25
+ const [state, setState] = useState<SyncState | null>(null)
26
+ const [mounted, setMounted] = useState(false)
27
+
28
+ useEffect(() => {
29
+ setMounted(true)
30
+ const bridge = getDesktopBridge()
31
+ if (!bridge) return
32
+ const off = bridge.onSyncState((s) => setState(s as SyncState))
33
+ return off
34
+ }, [])
35
+
36
+ if (!mounted || !getDesktopBridge()) return null
37
+ if (!state) return null
38
+
39
+ const hasError = state.lastError !== null
40
+ const busy = state.phase === "pushing" || state.phase === "pulling"
41
+ const dotColor = hasError ? "bg-destructive" : busy ? "bg-amber-500 animate-pulse" : "bg-emerald-500"
42
+ const label = hasError
43
+ ? "同步失败"
44
+ : busy
45
+ ? LABELS[state.phase]
46
+ : state.pending > 0
47
+ ? `待同步 ${state.pending}`
48
+ : LABELS.idle
49
+
50
+ return (
51
+ <div
52
+ className="flex items-center gap-1.5 px-2 py-1 text-xs text-muted-foreground"
53
+ title={hasError ? `同步失败:${state.lastError}` : `上次同步 ${state.lastSyncedAt ?? "—"}`}
54
+ data-testid="desktop-sync-indicator"
55
+ >
56
+ <span className={`inline-block size-2 rounded-full ${dotColor}`} aria-hidden />
57
+ <span>{label}</span>
58
+ </div>
59
+ )
60
+ }
@@ -0,0 +1,60 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+
5
+ // 同步状态卡片(设置页内嵌版,比顶栏指示器信息更全):订阅
6
+ // desktopBridge.onSyncState,展示阶段/待同步数/上次同步/错误。
7
+ import { useEffect, useState } from "react"
8
+ import { getDesktopBridge } from "@/lib/desktop-bridge-types"
9
+ import { Badge } from "@/components/ui/badge"
10
+
11
+ interface SyncState {
12
+ phase: "idle" | "pushing" | "pulling"
13
+ pending: number
14
+ lastError: string | null
15
+ lastSyncedAt: string | null
16
+ }
17
+
18
+ const PHASE_LABELS: Record<SyncState["phase"], string> = {
19
+ idle: "空闲",
20
+ pushing: "推送中",
21
+ pulling: "拉取中",
22
+ }
23
+
24
+ export function DesktopSyncStatusCard() {
25
+ const [state, setState] = useState<SyncState | null>(null)
26
+
27
+ useEffect(() => {
28
+ const bridge = getDesktopBridge()
29
+ if (!bridge) return
30
+ return bridge.onSyncState((s) => setState(s as SyncState))
31
+ }, [])
32
+
33
+ if (!getDesktopBridge()) {
34
+ return <p className="text-xs text-muted-foreground">仅桌面客户端可用</p>
35
+ }
36
+ if (!state) {
37
+ return <p className="text-xs text-muted-foreground">等待同步状态…</p>
38
+ }
39
+
40
+ return (
41
+ <dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5 text-xs">
42
+ <dt className="text-muted-foreground">状态</dt>
43
+ <dd>
44
+ <Badge variant={state.lastError ? "destructive" : "secondary"}>
45
+ {state.lastError ? "异常" : PHASE_LABELS[state.phase]}
46
+ </Badge>
47
+ </dd>
48
+ <dt className="text-muted-foreground">待同步</dt>
49
+ <dd>{state.pending} 项</dd>
50
+ <dt className="text-muted-foreground">上次同步</dt>
51
+ <dd>{state.lastSyncedAt ?? "—"}</dd>
52
+ {state.lastError && (
53
+ <>
54
+ <dt className="text-muted-foreground">错误</dt>
55
+ <dd className="text-destructive">{state.lastError}</dd>
56
+ </>
57
+ )}
58
+ </dl>
59
+ )
60
+ }
@@ -37,6 +37,7 @@ import { PresenceAvatarStack } from "@/components/layout/sidebar-03/presence-ava
37
37
  import { SidebarSearchTrigger } from "@/components/layout/sidebar-03/nav-search-trigger"
38
38
  import { NavSecondary } from "@/components/layout/sidebar-03/nav-secondary"
39
39
  import { PluginNavItems } from '@/components/plugin-slots/plugin-nav-items'
40
+ import { isDesktopEdition } from "@/lib/edition-desktop"
40
41
 
41
42
  // presence 渲染工厂(§8.2 消费者两文件之一):nav-main 按 route.id 查
42
43
  // MODULE_ROUTES 得 module key 后回调;module 级单例 map,identity 稳定。
@@ -48,18 +49,25 @@ const routes: Route[] = workspaceRoutes.map((route) => ({
48
49
  export function DashboardSidebar() {
49
50
  const { state } = useSidebar();
50
51
  const isCollapsed = state === "collapsed";
52
+ // 桌面壳(Electron hiddenInset):红绿灯悬浮在窗口左上角,顶栏重排为
53
+ // 「第一行红绿灯让位 + 右侧按钮组(整行可拖动窗口)、第二行 logo」。
54
+ // web/云端 SaaS 布局不变(isDesktopEdition 为构建期常量,死分支消除)。
55
+ const isDesktop = isDesktopEdition();
51
56
 
52
57
  return (
53
58
  <Sidebar variant="floating" collapsible="icon">
54
59
  <SidebarHeader
55
60
  className={cn(
56
61
  "flex flex-col items-center justify-center gap-2 py-2",
57
- !isCollapsed && "flex-row justify-between py-2 md:pt-3.5"
62
+ !isCollapsed && !isDesktop && "flex-row justify-between py-2 md:pt-3.5",
63
+ // 桌面展开态:整块 header 为拖动区(第二行 logo 左对齐),按钮逐个 no-drag
64
+ !isCollapsed && isDesktop && "app-region-drag w-full items-stretch justify-start gap-0 py-1",
65
+ isCollapsed && isDesktop && "app-region-drag pt-8",
58
66
  )}
59
67
  >
60
68
  {isCollapsed ? (
61
69
  <div className="flex flex-col items-center gap-2">
62
- <div className="flex flex-col items-center gap-2">
70
+ <div className={cn("flex flex-col items-center gap-2", isDesktop && "app-region-no-drag")}>
63
71
  <img
64
72
  src="/logo/allblue-logo-dark.svg"
65
73
  alt="AllBlue"
@@ -71,11 +79,38 @@ export function DashboardSidebar() {
71
79
  className="hidden h-6 w-6 dark:block"
72
80
  />
73
81
  </div>
74
- <div className="flex flex-col-reverse items-center gap-1">
82
+ <div className={cn("flex flex-col-reverse items-center gap-1", isDesktop && "app-region-no-drag")}>
75
83
  <NotificationsBell />
76
84
  <SidebarTrigger />
77
85
  </div>
78
86
  </div>
87
+ ) : isDesktop ? (
88
+ <>
89
+ {/* 第一行:左侧让位 macOS 红绿灯(hiddenInset + trafficLightPosition
90
+ y=18 → 圆心 24px,与 h-8 行中心 8+16=24 对齐),右侧按钮组;
91
+ relative z-50 压过 _nav 的顶缘拖动条(否则按钮点击被 drag 吞);
92
+ 空区拖动窗口 */}
93
+ <div className="app-region-drag relative z-50 flex h-8 w-full items-center justify-end gap-2 pl-20">
94
+ <div className="app-region-no-drag flex items-center gap-2">
95
+ <NotificationsBell />
96
+ <AnimatedThemeToggler />
97
+ <SidebarTrigger />
98
+ </div>
99
+ </div>
100
+ {/* 第二行:logo(左对齐,与红绿灯行错层) */}
101
+ <a href="#" className="app-region-no-drag flex items-center gap-2 px-1 pb-1">
102
+ <img
103
+ src="/logo/allblue-logo-text-dark.svg"
104
+ alt="AllBlue"
105
+ className="h-5 dark:hidden"
106
+ />
107
+ <img
108
+ src="/logo/allblue-logo-text-light.svg"
109
+ alt="AllBlue"
110
+ className="hidden h-5 dark:block"
111
+ />
112
+ </a>
113
+ </>
79
114
  ) : (
80
115
  <>
81
116
  <a href="#" className="flex items-center gap-2">
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { describe, it, expect } from "vitest";
19
- import { AVATAR_SHAPES, toAvatarShape } from "../avatar-shape";
19
+ import { AVATAR_SHAPES, hasAvatarConfig, toAvatarShape } from "../avatar-shape";
20
20
 
21
21
  describe("AVATAR_SHAPES", () => {
22
22
  it("与 kernel ShapeType 三值一致", () => {
@@ -42,3 +42,19 @@ describe("toAvatarShape", () => {
42
42
  expect(toAvatarShape(undefined)).toBeNull();
43
43
  });
44
44
  });
45
+
46
+ describe("hasAvatarConfig", () => {
47
+ it("空对象(DB 列默认值)不算已配置", () => {
48
+ expect(hasAvatarConfig({})).toBe(false);
49
+ });
50
+
51
+ it("空值不算已配置", () => {
52
+ expect(hasAvatarConfig(null)).toBe(false);
53
+ expect(hasAvatarConfig(undefined)).toBe(false);
54
+ });
55
+
56
+ it("非空对象算已配置", () => {
57
+ expect(hasAvatarConfig({ face: 0 })).toBe(true);
58
+ expect(hasAvatarConfig({ face: 3, hair: 1 })).toBe(true);
59
+ });
60
+ });