@owdproject/core 3.3.0 → 3.4.0

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 (92) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/DESKTOP_KERNEL.md +232 -0
  3. package/MIGRATION_3.3.2.md +23 -0
  4. package/MIGRATION_3.4.md +126 -0
  5. package/PLAYGROUND.md +91 -0
  6. package/README.md +13 -80
  7. package/dist/module.d.mts +2 -2
  8. package/dist/module.json +3 -3
  9. package/dist/module.mjs +86 -99
  10. package/index.ts +6 -3
  11. package/kit/authoring.ts +340 -0
  12. package/kit/defineDesktopApp.ts +56 -0
  13. package/module.ts +76 -108
  14. package/package.json +70 -40
  15. package/runtime/components/{Desktop/Application/DesktopApplicationRender.vue → Application/DesktopCoreApplicationRender.vue} +2 -2
  16. package/runtime/components/{Desktop/Application/DesktopApplicationWindowsRender.vue → Application/DesktopCoreApplicationWindowsRender.vue} +1 -1
  17. package/runtime/components/core/DesktopCore.vue +43 -0
  18. package/runtime/components/{Desktop/Window/DesktopWindow.vue → window/DesktopCoreWindow.vue} +17 -6
  19. package/runtime/components/window/DesktopCoreWindowSnapHintsBase.vue +74 -0
  20. package/runtime/components/workspace/DesktopCoreWorkspaceEdgeHintsBase.vue +84 -0
  21. package/runtime/composables/useBlockNonInputContextMenu.ts +30 -0
  22. package/runtime/composables/useDesktopConfig.ts +8 -0
  23. package/runtime/composables/useDesktopDialogs.ts +16 -0
  24. package/runtime/composables/useDesktopExtension.ts +8 -0
  25. package/runtime/composables/useDesktopManager.ts +2 -0
  26. package/runtime/composables/useDesktopManifest.ts +17 -0
  27. package/runtime/composables/useDesktopSession.ts +27 -0
  28. package/runtime/composables/useDesktopShellIdentity.ts +99 -0
  29. package/runtime/composables/useDesktopShellOptions.ts +36 -0
  30. package/runtime/composables/useDesktopShellReady.ts +7 -0
  31. package/runtime/composables/useDesktopWorkArea.ts +93 -0
  32. package/runtime/composables/useTerminalManager.ts +102 -9
  33. package/runtime/composables/useToggleWindowMaximize.ts +35 -0
  34. package/runtime/composables/useWindowDragHandlers.ts +47 -0
  35. package/runtime/composables/useWindowSnapDrop.ts +112 -0
  36. package/runtime/composables/useWorkspaceEdgeDrop.ts +176 -0
  37. package/runtime/composables/useWorkspaceManager.ts +26 -0
  38. package/runtime/composables/useWorkspaceOverviewCapture.ts +92 -0
  39. package/runtime/composables/useWorkspaceOverviewLiveScale.ts +151 -0
  40. package/runtime/constants/desktopShellKeys.ts +12 -0
  41. package/runtime/constants/shellLayout.ts +5 -0
  42. package/runtime/dialogs/desktopDialogProvider.ts +73 -0
  43. package/runtime/internal/controllers/ApplicationController.ts +8 -2
  44. package/runtime/internal/controllers/WindowController.ts +93 -8
  45. package/runtime/plugins/01.desktop-shell-init.client.ts +28 -0
  46. package/runtime/plugins/02.desktop-register-desktop-apps.client.ts +2 -6
  47. package/runtime/stores/storeApplicationMeta.ts +2 -1
  48. package/runtime/stores/storeApplicationWindows.ts +2 -1
  49. package/runtime/stores/storeDesktop.ts +17 -57
  50. package/runtime/stores/storeDesktopDefaultApps.ts +2 -1
  51. package/runtime/stores/storeDesktopVolume.ts +2 -1
  52. package/runtime/stores/storeDesktopWindow.ts +20 -2
  53. package/runtime/stores/storeDesktopWorkspace.ts +31 -1
  54. package/runtime/stores/storeIds.ts +35 -0
  55. package/runtime/types/desktopMenu.ts +10 -0
  56. package/runtime/utils/initDesktopShell.ts +80 -0
  57. package/runtime/utils/migratePersistedStoreIds.ts +57 -0
  58. package/runtime/utils/utilApp.ts +0 -12
  59. package/runtime/utils/{captureElementToCanvas.ts → utilCaptureElementToCanvas.ts} +1 -1
  60. package/runtime/utils/utilDetectSnapZone.ts +57 -0
  61. package/runtime/utils/utilWindowLayout.ts +82 -0
  62. package/runtime/utils/utilWindowMaximizeLayout.ts +44 -0
  63. package/runtime/utils/utilWorkspaceWindows.ts +33 -0
  64. package/types/index.d.ts +125 -14
  65. package/bin/cli.js +0 -395
  66. package/bin/desktop.js +0 -5
  67. package/bin/lib/catalog.js +0 -293
  68. package/bin/lib/config.js +0 -109
  69. package/bin/lib/desktopConfig.js +0 -56
  70. package/bin/lib/generateTemplate.js +0 -389
  71. package/bin/lib/install.js +0 -408
  72. package/bin/lib/migrateModulePlayground.config.mjs +0 -164
  73. package/bin/lib/migrateModulePlayground.mjs +0 -313
  74. package/bin/lib/npmVersions.js +0 -124
  75. package/bin/lib/playgroundContext.js +0 -188
  76. package/bin/lib/scaffold.js +0 -197
  77. package/bin/lib/status.js +0 -289
  78. package/bin/lib/validateModule.js +0 -697
  79. package/bin/lib/workspace.js +0 -182
  80. package/bin/owd.js +0 -5
  81. package/bin/tui.js +0 -1379
  82. package/runtime/components/Desktop/Core/DesktopCore.vue +0 -90
  83. package/runtime/utils/explorerEntryPath.ts +0 -10
  84. package/runtime/utils/resolveDesktopConfigPath.ts +0 -52
  85. package/runtime/utils/splitDesktopUserConfig.ts +0 -85
  86. package/runtime/utils/utilDesktop.ts +0 -35
  87. package/runtime/utils/validateDesktopUserConfig.ts +0 -44
  88. /package/runtime/components/{Desktop/Background/DesktopBackground.vue → background/DesktopCoreBackground.vue} +0 -0
  89. /package/runtime/components/{Desktop/Time/DesktopTime.vue → time/DesktopCoreTime.vue} +0 -0
  90. /package/runtime/components/{Desktop/Window/DesktopWindowContent.vue → window/DesktopCoreWindowContent.vue} +0 -0
  91. /package/runtime/components/{Desktop/Window/DesktopWindowNav.vue → window/DesktopCoreWindowNav.vue} +0 -0
  92. /package/runtime/utils/{utilWindow.ts → utilWindowControllerAdapter.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owdproject/core",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Nuxt Desktop module for browser desktop experiences (windows, shell, apps)",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -12,13 +12,13 @@
12
12
  "owd",
13
13
  "open-web-desktop"
14
14
  ],
15
- "homepage": "https://github.com/owdproject/client#readme",
15
+ "homepage": "https://github.com/owdproject/core#readme",
16
16
  "bugs": {
17
- "url": "https://github.com/owdproject/client/issues"
17
+ "url": "https://github.com/owdproject/core/issues"
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",
21
- "url": "git+https://github.com/owdproject/client.git"
21
+ "url": "git+https://github.com/owdproject/core.git"
22
22
  },
23
23
  "license": "MIT",
24
24
  "author": {
@@ -28,23 +28,20 @@
28
28
  "type": "module",
29
29
  "types": "types/index.d.ts",
30
30
  "files": [
31
- "bin",
32
31
  "dist",
32
+ "kit",
33
33
  "runtime",
34
34
  "types",
35
35
  "index.ts",
36
36
  "module.ts",
37
37
  "package.json",
38
- "README.md"
38
+ "README.md",
39
+ "DESKTOP_KERNEL.md",
40
+ "CHANGELOG.md",
41
+ "MIGRATION_3.3.2.md",
42
+ "MIGRATION_3.4.md",
43
+ "PLAYGROUND.md"
39
44
  ],
40
- "scripts": {
41
- "dev": "pnpm run dev:prepare",
42
- "dev:prepare": "nuxt-module-build build --stub",
43
- "build:module": "nuxt-module-build build",
44
- "prepack": "nuxt-module-build build",
45
- "test": "vitest run",
46
- "test:watch": "vitest"
47
- },
48
45
  "exports": {
49
46
  ".": {
50
47
  "types": "./types/index.d.ts",
@@ -63,50 +60,83 @@
63
60
  "default": "./module.ts"
64
61
  },
65
62
  "./package.json": "./package.json",
66
- "./runtime/*": {
67
- "types": "./runtime/*",
68
- "development": "./runtime/*",
69
- "import": "./runtime/*",
70
- "default": "./runtime/*"
63
+ "./kit/*": {
64
+ "types": "./types/index.d.ts",
65
+ "development": "./kit/*",
66
+ "import": "./kit/*",
67
+ "default": "./kit/*"
68
+ },
69
+ "./runtime/composables/*": {
70
+ "types": "./runtime/composables/*",
71
+ "development": "./runtime/composables/*",
72
+ "import": "./runtime/composables/*",
73
+ "default": "./runtime/composables/*"
74
+ },
75
+ "./runtime/constants/*": {
76
+ "types": "./runtime/constants/*",
77
+ "development": "./runtime/constants/*",
78
+ "import": "./runtime/constants/*",
79
+ "default": "./runtime/constants/*"
80
+ },
81
+ "./runtime/dialogs/*": {
82
+ "types": "./runtime/dialogs/*",
83
+ "development": "./runtime/dialogs/*",
84
+ "import": "./runtime/dialogs/*",
85
+ "default": "./runtime/dialogs/*"
86
+ },
87
+ "./runtime/utils/*": {
88
+ "types": "./runtime/utils/*",
89
+ "development": "./runtime/utils/*",
90
+ "import": "./runtime/utils/*",
91
+ "default": "./runtime/utils/*"
92
+ },
93
+ "./runtime/stores/*": {
94
+ "types": "./runtime/stores/*",
95
+ "development": "./runtime/stores/*",
96
+ "import": "./runtime/stores/*",
97
+ "default": "./runtime/stores/*"
98
+ },
99
+ "./runtime/components/*": {
100
+ "types": "./runtime/components/*",
101
+ "development": "./runtime/components/*",
102
+ "import": "./runtime/components/*",
103
+ "default": "./runtime/components/*"
104
+ },
105
+ "./runtime/types/*": {
106
+ "types": "./runtime/types/*",
107
+ "development": "./runtime/types/*",
108
+ "import": "./runtime/types/*",
109
+ "default": "./runtime/types/*"
71
110
  }
72
111
  },
73
- "bin": {
74
- "desktop": "bin/desktop.js",
75
- "owd": "bin/owd.js"
76
- },
77
112
  "dependencies": {
78
113
  "@nuxt/fonts": "^0.14.0",
79
114
  "@nuxt/icon": "^2.2.1",
80
115
  "@nuxt/kit": "^4.4.4",
81
116
  "@nuxtjs/i18n": "^10.3.0",
82
- "@nuxtjs/tailwindcss": "^6.14.0",
83
117
  "@pinia/nuxt": "^0.11.3",
84
- "@primeuix/themes": "^2.0.3",
85
- "@primevue/forms": "^4.5.5",
86
- "@primevue/nuxt-module": "^4.5.5",
87
118
  "@vueuse/components": "^14.3.0",
88
119
  "@vueuse/core": "^14.3.0",
89
120
  "@vueuse/nuxt": "^14.3.0",
121
+ "defu": "^6.1.4",
90
122
  "mitt": "^3.0.1",
91
123
  "nanoid": "^5.1.11",
92
124
  "nuxt": "^4.4.4",
93
125
  "pinia": "^3.0.4",
94
126
  "sanitize.css": "^13.0.0",
95
127
  "sass": "^1.99.0",
96
- "shellwords": "^1.1.1",
97
- "vue-resizable": "^2.1.7",
98
- "getopts": "^2.3.0",
99
- "defu": "^6.1.4",
100
- "neo-blessed": "^0.2.0"
101
- },
102
- "optionalDependencies": {
103
- "html2canvas": "^1.4.1"
128
+ "vue-resizable": "^2.1.7"
104
129
  },
105
130
  "devDependencies": {
106
- "@nuxt/devtools": "^3.2.4",
107
131
  "@nuxt/module-builder": "^1.0.2",
108
- "@nuxt/schema": "^4.4.4",
109
- "typescript": "~5.8.3",
110
- "vue-tsc": "^2.2.10"
132
+ "typescript": "~6.0.3"
133
+ },
134
+ "scripts": {
135
+ "dev": "pnpm run dev:prepare",
136
+ "dev:prepare": "nuxt-module-build build --stub",
137
+ "build:module": "nuxt-module-build build",
138
+ "test": "vitest run",
139
+ "test:watch": "vitest",
140
+ "test:coverage": "vitest run --coverage"
111
141
  }
112
- }
142
+ }
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { useApplicationManager } from '../../../composables/useApplicationManager'
2
+ import { useApplicationManager } from '../../composables/useApplicationManager'
3
3
 
4
4
  defineProps<{
5
5
  workspaceFilter?: string
@@ -10,7 +10,7 @@ const runningApps = applicationManager.appsRunning
10
10
  </script>
11
11
 
12
12
  <template>
13
- <DesktopApplicationWindowsRender
13
+ <DesktopCoreApplicationWindowsRender
14
14
  v-for="app of runningApps"
15
15
  :key="app.id"
16
16
  :workspace-filter="workspaceFilter"
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
- import { useDesktopWorkspaceStore } from '../../../stores/storeDesktopWorkspace'
3
+ import { useDesktopWorkspaceStore } from '../../stores/storeDesktopWorkspace'
4
4
 
5
5
  const props = defineProps<{
6
6
  workspaceFilter?: string
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+ import { useAttrs, computed } from 'vue'
3
+ import { useAppConfig } from 'nuxt/app'
4
+
5
+ defineOptions({ inheritAttrs: false })
6
+
7
+ defineProps<{
8
+ windows?: DesktopWindowsConfig
9
+ systemBar?: DesktopSystemBarConfig
10
+ dockBar?: DesktopDockBarConfig
11
+ workspaces?: DesktopWorkspacesConfig
12
+ }>()
13
+
14
+ const appConfig = useAppConfig()
15
+ const attrs = useAttrs()
16
+
17
+ const classes = computed(() => {
18
+ const list = ['owd-desktop']
19
+
20
+ if (appConfig.desktop?.name) {
21
+ list.push(`owd-desktop--${appConfig.desktop.name}`)
22
+ }
23
+
24
+ return list
25
+ })
26
+
27
+ const rootClass = computed(() => [classes.value, attrs.class])
28
+ const rootStyle = computed(() => attrs.style)
29
+ </script>
30
+
31
+ <template>
32
+ <div :class="rootClass" :style="rootStyle">
33
+ <slot />
34
+ </div>
35
+ </template>
36
+
37
+ <style scoped lang="scss">
38
+ .owd-desktop {
39
+ width: 100vw;
40
+ height: 100dvh;
41
+ user-select: none;
42
+ }
43
+ </style>
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
- import { ref, provide } from 'vue'
2
+ import { ref, provide, onMounted } from 'vue'
3
3
  import { computed } from '@vue/reactivity'
4
4
  import { useAppConfig } from 'nuxt/app'
5
- import { handleWindowControllerProps } from '@owdproject/core/runtime/utils/utilWindow'
6
- import { useDesktopWorkspaceStore } from '../../../stores/storeDesktopWorkspace'
5
+ import { handleWindowControllerProps } from '../../utils/utilWindowControllerAdapter'
6
+ import { useDesktopWorkspaceStore } from '../../stores/storeDesktopWorkspace'
7
7
 
8
8
  const props = defineProps<{
9
9
  window?: IWindowController
@@ -41,6 +41,12 @@ provide<WindowContent>('windowContent', props.content ?? {})
41
41
  const isDragging = ref(false)
42
42
  const isResizing = ref(false)
43
43
 
44
+ onMounted(() => {
45
+ if (windowController?.state?.focused) {
46
+ windowController.actions.bringToFront()
47
+ }
48
+ })
49
+
44
50
  function onWindowPointerDown() {
45
51
  windowController?.actions?.setActive(true)
46
52
  windowController?.actions?.bringToFront()
@@ -144,9 +150,13 @@ function onOverviewNativeDragStart(e: DragEvent) {
144
150
  }
145
151
  }
146
152
 
153
+ const windowPositionMode = computed(
154
+ () => appConfig.desktop?.windows?.position ?? 'fixed',
155
+ )
156
+
147
157
  /** `fit-parent` clamps drag/resize to the parent box — wrong for viewport-fixed shells (Win11, GNOME). */
148
158
  const shouldFitParent = computed(
149
- () => appConfig.desktop.windows.position !== 'fixed',
159
+ () => windowPositionMode.value !== 'fixed',
150
160
  )
151
161
  </script>
152
162
 
@@ -183,11 +193,11 @@ const shouldFitParent = computed(
183
193
  <style scoped lang="scss">
184
194
  /* vue-resizable sets `.resizable-component { position: relative }` — override so desktop geometry stays consistent */
185
195
  .owd-window.resizable-component {
186
- position: v-bind('appConfig.desktop.windows.position') !important;
196
+ position: v-bind('windowPositionMode') !important;
187
197
  }
188
198
 
189
199
  .owd-window {
190
- position: v-bind('appConfig.desktop.windows.position');
200
+ position: v-bind('windowPositionMode');
191
201
 
192
202
  &--dragging,
193
203
  &--resizing {
@@ -199,6 +209,7 @@ const shouldFitParent = computed(
199
209
  :deep(.owd-window__content) {
200
210
  overflow-y: auto;
201
211
  height: 100%;
212
+ min-height: 0;
202
213
  }
203
214
  }
204
215
  </style>
@@ -0,0 +1,74 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { useDesktopWorkspaceStore } from '@owdproject/core/runtime/stores/storeDesktopWorkspace'
4
+ import { useWindowSnapDrop } from '@owdproject/core/runtime/composables/useWindowSnapDrop'
5
+
6
+ const props = defineProps<{
7
+ enabled?: boolean
8
+ }>()
9
+
10
+ const desktopWorkspaceStore = useDesktopWorkspaceStore()
11
+ const { snapHint, snapHintRect, isSnapDragActive } = useWindowSnapDrop()
12
+
13
+ const visible = computed(
14
+ () =>
15
+ props.enabled !== false &&
16
+ !desktopWorkspaceStore.overview &&
17
+ isSnapDragActive.value &&
18
+ snapHint.value !== null &&
19
+ snapHintRect.value !== null,
20
+ )
21
+
22
+ const hintStyle = computed(() => {
23
+ const rect = snapHintRect.value
24
+ if (!rect) return undefined
25
+
26
+ return {
27
+ left: `${rect.x}px`,
28
+ top: `${rect.y}px`,
29
+ width: `${rect.width}px`,
30
+ height: `${rect.height}px`,
31
+ }
32
+ })
33
+ </script>
34
+
35
+ <template>
36
+ <div
37
+ v-if="visible"
38
+ class="owd-window-snap-hints"
39
+ aria-hidden="true"
40
+ >
41
+ <div
42
+ class="owd-window-snap-hints__zone"
43
+ :class="`owd-window-snap-hints__zone--${snapHint}`"
44
+ :style="hintStyle"
45
+ >
46
+ <slot
47
+ :zone="snapHint"
48
+ :rect="snapHintRect"
49
+ />
50
+ </div>
51
+ </div>
52
+ </template>
53
+
54
+ <style scoped>
55
+ .owd-window-snap-hints {
56
+ position: fixed;
57
+ inset: 0;
58
+ pointer-events: none;
59
+ z-index: 999998;
60
+ }
61
+
62
+ .owd-window-snap-hints__zone {
63
+ position: fixed;
64
+ box-sizing: border-box;
65
+ border: 2px solid rgba(96, 165, 250, 0.85);
66
+ background: rgba(96, 165, 250, 0.18);
67
+ border-radius: 4px;
68
+ transition:
69
+ left 80ms ease,
70
+ top 80ms ease,
71
+ width 80ms ease,
72
+ height 80ms ease;
73
+ }
74
+ </style>
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { useWorkspaceEdgeDrop } from '@owdproject/core/runtime/composables/useWorkspaceEdgeDrop'
4
+ import { useDesktopWorkspaceStore } from '@owdproject/core/runtime/stores/storeDesktopWorkspace'
5
+
6
+ const props = defineProps<{
7
+ enabled?: boolean
8
+ }>()
9
+
10
+ const desktopWorkspaceStore = useDesktopWorkspaceStore()
11
+ const { edgeHint, edgeTargetDesktopIndex } = useWorkspaceEdgeDrop()
12
+
13
+ const visible = computed(
14
+ () =>
15
+ props.enabled !== false &&
16
+ !desktopWorkspaceStore.overview &&
17
+ desktopWorkspaceStore.list.length > 1 &&
18
+ edgeHint.value !== null &&
19
+ edgeTargetDesktopIndex.value !== null,
20
+ )
21
+ </script>
22
+
23
+ <template>
24
+ <div
25
+ v-if="visible"
26
+ class="owd-workspace-edge-hints"
27
+ aria-hidden="true"
28
+ >
29
+ <div
30
+ class="owd-workspace-edge-hints__zone owd-workspace-edge-hints__zone--left"
31
+ :class="{
32
+ 'owd-workspace-edge-hints__zone--active': edgeHint === 'left',
33
+ }"
34
+ >
35
+ <slot
36
+ name="left"
37
+ :desktop-index="edgeTargetDesktopIndex"
38
+ :side="'left' as const"
39
+ />
40
+ </div>
41
+ <div
42
+ class="owd-workspace-edge-hints__zone owd-workspace-edge-hints__zone--right"
43
+ :class="{
44
+ 'owd-workspace-edge-hints__zone--active': edgeHint === 'right',
45
+ }"
46
+ >
47
+ <slot
48
+ name="right"
49
+ :desktop-index="edgeTargetDesktopIndex"
50
+ :side="'right' as const"
51
+ />
52
+ </div>
53
+ </div>
54
+ </template>
55
+
56
+ <style scoped>
57
+ .owd-workspace-edge-hints {
58
+ position: fixed;
59
+ inset: 0;
60
+ pointer-events: none;
61
+ z-index: 999998;
62
+ }
63
+
64
+ .owd-workspace-edge-hints__zone {
65
+ position: fixed;
66
+ top: 0;
67
+ bottom: 0;
68
+ width: 72px;
69
+ opacity: 0;
70
+ transition: opacity 120ms ease;
71
+ }
72
+
73
+ .owd-workspace-edge-hints__zone--left {
74
+ left: 0;
75
+ }
76
+
77
+ .owd-workspace-edge-hints__zone--right {
78
+ right: 0;
79
+ }
80
+
81
+ .owd-workspace-edge-hints__zone--active {
82
+ opacity: 1;
83
+ }
84
+ </style>
@@ -0,0 +1,30 @@
1
+ import { useEventListener } from '@vueuse/core'
2
+ import { isDebugMode } from '../utils/utilDebug'
3
+
4
+ /**
5
+ * Blocks the browser context menu on non-input elements (typical desktop shell behaviour).
6
+ * Debug builds skip blocking so DevTools stay usable when {@link isDebugMode} is true.
7
+ */
8
+ export function useBlockNonInputContextMenu() {
9
+ useEventListener(
10
+ document,
11
+ 'contextmenu',
12
+ (event: MouseEvent) => {
13
+ if (isDebugMode()) return
14
+
15
+ const target = event.target as HTMLElement | null
16
+ if (!target) return
17
+
18
+ const tag = target.tagName?.toLowerCase()
19
+ const isInput =
20
+ tag === 'input' ||
21
+ tag === 'textarea' ||
22
+ Boolean(target.isContentEditable)
23
+
24
+ if (!isInput) {
25
+ event.preventDefault()
26
+ }
27
+ },
28
+ { passive: false },
29
+ )
30
+ }
@@ -0,0 +1,8 @@
1
+ import { useRuntimeConfig } from 'nuxt/app'
2
+ import { computed } from 'vue'
3
+
4
+ /** Reactive view of `runtimeConfig.public.desktop` (full desktop.config merge + coreVersion). */
5
+ export function useDesktopConfig() {
6
+ const runtimeConfig = useRuntimeConfig()
7
+ return computed(() => runtimeConfig.public.desktop)
8
+ }
@@ -0,0 +1,16 @@
1
+ import { inject } from 'vue'
2
+ import { DESKTOP_DIALOG_PROVIDER_KEY } from '../constants/desktopShellKeys'
3
+ import {
4
+ createBrowserFallbackDialogProvider,
5
+ type DesktopDialogProvider,
6
+ } from '../dialogs/desktopDialogProvider'
7
+
8
+ /**
9
+ * Returns the active desktop dialog provider (theme) or a browser fallback.
10
+ */
11
+ export function useDesktopDialogs(): DesktopDialogProvider {
12
+ return (
13
+ inject(DESKTOP_DIALOG_PROVIDER_KEY, null) ??
14
+ createBrowserFallbackDialogProvider()
15
+ )
16
+ }
@@ -0,0 +1,8 @@
1
+ import { computed } from 'vue'
2
+ import { useDesktopConfig } from './useDesktopConfig'
3
+
4
+ /** Readonly slice for an extension namespace (`fs`, `terminal`, or custom `configKey`). */
5
+ export function useDesktopExtension<K extends string>(key: K) {
6
+ const desktop = useDesktopConfig()
7
+ return computed(() => desktop.value?.[key as keyof typeof desktop.value])
8
+ }
@@ -17,6 +17,8 @@ export function useDesktopManager() {
17
17
  * Merges and sets the desktop config at runtime (shell geometry, feature flags, etc.).
18
18
  */
19
19
  function setConfig(newConfig: DesktopConfig) {
20
+ if (!appConfig) return
21
+
20
22
  appConfig.desktop = defu(
21
23
  newConfig,
22
24
  (appConfig.desktop ?? {}) as DesktopConfig,
@@ -0,0 +1,17 @@
1
+ import { computed } from 'vue'
2
+ import { useDesktopConfig } from './useDesktopConfig'
3
+
4
+ export function hasDesktopModule(pkg: string) {
5
+ const desktop = useDesktopConfig()
6
+ return computed(() => desktop.value?.modules?.includes(pkg) ?? false)
7
+ }
8
+
9
+ export function hasDesktopApp(pkg: string) {
10
+ const desktop = useDesktopConfig()
11
+ return computed(() => desktop.value?.apps?.includes(pkg) ?? false)
12
+ }
13
+
14
+ export function hasDesktopExtension(key: string) {
15
+ const desktop = useDesktopConfig()
16
+ return computed(() => Boolean(desktop.value?.[key as keyof typeof desktop.value]))
17
+ }
@@ -0,0 +1,27 @@
1
+ import { useRouter } from 'nuxt/app'
2
+ import { ref } from 'vue'
3
+
4
+ /**
5
+ * Neutral API for “leave the desktop shell” flows (shutdown animation → start route).
6
+ * Themes keep their own `/start` / `/boot` pages and styling.
7
+ */
8
+ const shuttingDown = ref(false)
9
+
10
+ export function useDesktopSession() {
11
+ const initiateShutdownToStart = () => {
12
+ shuttingDown.value = true
13
+
14
+ setTimeout(() => {
15
+ useRouter()
16
+ .push('/start')
17
+ .then(() => {
18
+ shuttingDown.value = false
19
+ })
20
+ }, 2000)
21
+ }
22
+
23
+ return {
24
+ shuttingDown,
25
+ initiateShutdownToStart,
26
+ }
27
+ }
@@ -0,0 +1,99 @@
1
+ import { computed, ref } from 'vue'
2
+ import { useRuntimeConfig } from 'nuxt/app'
3
+
4
+ export type ShellIdentity = {
5
+ userId: string
6
+ displayName: string
7
+ avatarUrl: string | null
8
+ userHome: string
9
+ }
10
+
11
+ const GUEST_USER_ID = 'guest'
12
+
13
+ const identity = ref<ShellIdentity | null>(null)
14
+
15
+ export function sanitizeUserHomeSegment(input: string): string {
16
+ const cleaned = input
17
+ .trim()
18
+ .replace(/^@/, '')
19
+ .replace(/[^\w.\-]/g, '_')
20
+ .slice(0, 64)
21
+ return cleaned || 'user'
22
+ }
23
+
24
+ /** Build a VFS home path from an auth handle (e.g. Bluesky `@alice.bsky.social`). */
25
+ export function userHomeFromHandle(handle: string, base = '/home'): string {
26
+ const baseNorm = base.endsWith('/') ? base.slice(0, -1) : base
27
+ return `${baseNorm}/${sanitizeUserHomeSegment(handle)}`
28
+ }
29
+
30
+ function resolveDefaultHome(): string {
31
+ try {
32
+ const config = useRuntimeConfig()
33
+ const desktop = config.public.desktop as {
34
+ fs?: { defaultUserHome?: string }
35
+ }
36
+ const home = desktop.fs?.defaultUserHome?.trim()
37
+ if (home) return home.startsWith('/') ? home : `/${home}`
38
+ } catch {
39
+ /* outside Nuxt setup */
40
+ }
41
+ return '/home/Guest'
42
+ }
43
+
44
+ function guestIdentity(defaultHome: string): ShellIdentity {
45
+ return {
46
+ userId: GUEST_USER_ID,
47
+ displayName: 'Guest',
48
+ avatarUrl: null,
49
+ userHome: defaultHome,
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Shell session identity (display + VFS home). Auth modules call {@link setShellIdentity}
55
+ * after login; defaults to Guest.
56
+ */
57
+ export function useDesktopShellIdentity() {
58
+ function ensureIdentity(): ShellIdentity {
59
+ if (!identity.value) {
60
+ identity.value = guestIdentity(resolveDefaultHome())
61
+ }
62
+ return identity.value
63
+ }
64
+
65
+ const userId = computed(() => ensureIdentity().userId)
66
+ const displayName = computed(() => ensureIdentity().displayName)
67
+ const avatarUrl = computed(() => ensureIdentity().avatarUrl)
68
+ const userHome = computed(() => ensureIdentity().userHome)
69
+ const isGuest = computed(() => userId.value === GUEST_USER_ID)
70
+
71
+ function setShellIdentity(partial: Partial<ShellIdentity>) {
72
+ const current = ensureIdentity()
73
+ identity.value = {
74
+ ...current,
75
+ ...partial,
76
+ userId: partial.userId?.trim() || current.userId,
77
+ displayName: partial.displayName?.trim() || current.displayName,
78
+ userHome: partial.userHome?.trim()
79
+ ? partial.userHome.startsWith('/')
80
+ ? partial.userHome
81
+ : `/${partial.userHome}`
82
+ : current.userHome,
83
+ }
84
+ }
85
+
86
+ function clearShellIdentity() {
87
+ identity.value = guestIdentity(resolveDefaultHome())
88
+ }
89
+
90
+ return {
91
+ userId,
92
+ displayName,
93
+ avatarUrl,
94
+ userHome,
95
+ isGuest,
96
+ setShellIdentity,
97
+ clearShellIdentity,
98
+ }
99
+ }