arcane-os 0.30.0 → 0.31.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.31.0
4
+
5
+ - Standalone applications use their repository root and installed npm package
6
+ paths. New standalone projects default to `appsRoot: "."`; initialization
7
+ preserves an existing configured layout, and explicit multi-app workspaces
8
+ retain their selected application directories.
9
+ - Remove the retired `legacyAppPaths` setting and all SDK-generated nested app
10
+ redirects and duplicate PWA worker/inventory files. Consumers must remove the
11
+ obsolete setting when upgrading. Enabled PWA files remain at the app root;
12
+ public SDK files remain under `node_modules/arcane-os` or the installed alias.
13
+ - Preserve selected authored files, complete URLs and queries, installation
14
+ identity, saved data and caches. Generation does not delete preexisting app
15
+ files; application owners preserve and relocate their content independently.
16
+ - Generated and offline app files are committed for hosting workflows to
17
+ consume. Existing public Node TWiN and browser APIs remain available.
18
+
3
19
  ## 0.30.0
4
20
 
5
21
  - Add the public `arcane-os/ai/twin-cloud` subpath with stateless `fetchRequest`
package/README.md CHANGED
@@ -16,31 +16,34 @@
16
16
  `arcane-os` is the application SDK and command-line toolchain for Arcane OS. It
17
17
  supports two explicit workspace profiles: an external app repository uses the
18
18
  version-locked SDK runtime, while an integrated Arcane checkout uses its live
19
- `arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
20
- event, cancellation, and browser run contracts.
19
+ `arcane/` runtime. Both profiles share the theme, packaging, event, cancellation,
20
+ and browser run contracts while retaining their selected application layout.
21
21
 
22
- This checkout defines the `0.30.0` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.31.0` SDK contract. Applications pin one exact npm
23
23
  version and lockfile; registry state is deliberately not baked into application
24
24
  artifacts.
25
25
 
26
- External browser apps can use the installed npm package directly, without a
26
+ Standalone browser apps use their repository root and installed npm package directly, without a
27
27
  generated workspace `arcane/` directory or `arcane.lock.json`. Select the
28
28
  [four installed-package routes](docs/reference/protocols.md#installed-package-browser-routes)
29
29
  in `arcane-packager.json`: development, managed import maps, and PWA resources
30
30
  read the installed SDK at real `/node_modules/arcane-os/...` browser URLs when
31
31
  each destination equals its source. An alias uses its actual installed folder.
32
- Select `appsRoot: "."` for a standalone root app, or scaffold one with
33
- `arcane new my-app --apps-root .`. After npm installation, `arcane import-map`
34
- refreshes managed maps and the root app's static PWA/navigation files for ordinary
35
- static hosting. Portable app packages still contain their selected runtime.
32
+ `arcane new my-app` defaults to `appsRoot: "."`. After npm installation,
33
+ `arcane import-map` refreshes managed maps and the enabled root PWA files for
34
+ ordinary static hosting. It generates no nested app redirects or duplicate PWA
35
+ files. Remove the retired `legacyAppPaths` setting when upgrading. Commit generated
36
+ and offline app files for hosting workflows to consume. Portable app packages
37
+ still contain their selected runtime.
36
38
  Root/direct maps use `arcane-os/modules/<filename>` and
37
39
  `arcane-os/entities/<filename>` (including extensions) plus the focused lowercase
38
40
  exports, rather than `arcane/*` aliases. These paths resolve to the actual npm
39
41
  files, preserving relative component URLs. New root apps put the SDK in runtime
40
42
  `dependencies`; init preserves existing runtime/optional declarations and promotes
41
43
  a root app's SDK development declaration without changing its other packages.
42
- Existing `apps/<id>`, virtual `/arcane` routes, and materialized workspaces remain
43
- supported. Node services continue to
44
+ Explicit multi-app `appsRoot: "apps"`, virtual runtime routes, and existing
45
+ materialized workspaces remain supported; initialization preserves their selected
46
+ layout. Node services continue to
44
47
  use the installed CLI or public imports such as `arcane-os/mail`.
45
48
 
46
49
  The [mail gateway](docs/reference/mail.md) serves HTTPS with HTTP/2 on port 4433
@@ -82,7 +85,7 @@ Create one browser application, install its pinned SDK, and start its source
82
85
  server:
83
86
 
84
87
  ```bash
85
- npx arcane-os@0.26.0 new hello-speech --path ./hello-speech --target browser
88
+ npx arcane-os@0.31.0 new hello-speech --path ./hello-speech --target browser
86
89
  cd hello-speech
87
90
  npm install
88
91
  ```
@@ -74,11 +74,11 @@ export function getBrowserDeviceSettings(navigatorObject = globalThis.navigator)
74
74
 
75
75
  // Only an explicit browser adapter type or fallback flag establishes its class.
76
76
  // Vendor names and powerPreference (including Chromium's echoed request) do not.
77
- export function describeBrowserGpu(info, legacyFallbackAdapter) {
77
+ export function describeBrowserGpu(info, fallbackAdapter) {
78
78
  const adapterType = is.string(info?.type) ? info.type : null;
79
79
  const isFallbackAdapter = is.boolean(info?.isFallbackAdapter)
80
80
  ? info.isFallbackAdapter
81
- : is.boolean(legacyFallbackAdapter) ? legacyFallbackAdapter : null;
81
+ : is.boolean(fallbackAdapter) ? fallbackAdapter : null;
82
82
  let performanceStatus = 'unknown';
83
83
  if (isFallbackAdapter === true || adapterType === 'CPU') performanceStatus = 'fallback';
84
84
  else if (adapterType === 'discrete GPU') performanceStatus = 'discrete';
@@ -120,8 +120,9 @@ directory as a repository. Native target scaffolds also retain `browser` and
120
120
  include the required icon. The result reports the workspace, app, descriptor,
121
121
  target, and created paths.
122
122
 
123
- `--apps-root .` creates a standalone root application using its installed npm
124
- SDK directly. The default `--apps-root apps` preserves `apps/<id>`. Root setup
123
+ Each standalone app's root is its repository root. The default `--apps-root .`
124
+ uses the installed npm SDK directly. Explicit `--apps-root apps` selects a
125
+ multi-app workspace with each app beneath `apps/<id>`. Root setup
125
126
  does not install dependencies or copy a runtime: run `npm install`, then
126
127
  `npm run import-map`. Until installation, its result reports the import map as
127
128
  pending with reason `sdk-install-required`.
@@ -152,7 +153,7 @@ idempotent only for files whose existing content satisfies the scaffold
152
153
  contract.
153
154
 
154
155
  `--apps-root .` selects root setup for a standalone workspace. Omission retains
155
- the configured layout, or `apps` for a new configuration. `init` never moves an
156
+ the configured layout, or `.` for a new standalone configuration. `init` never moves an
156
157
  existing application or converts the integrated Arcane OS layout.
157
158
 
158
159
  ### Example
@@ -217,8 +218,9 @@ the workspace does not already identify exactly one. The command accepts no
217
218
  positional arguments and supports app scope only. `arcane-os import-map` is the
218
219
  identical executable alias.
219
220
 
220
- The generated artifact is
221
- `apps/<id>/modules/arcane.importmap.json`. Its exact JSON is also installed in
221
+ The generated artifact is `modules/arcane.importmap.json` at a standalone
222
+ app's repository root, or `apps/<id>/modules/arcane.importmap.json` for an
223
+ explicit multi-app workspace. Its exact JSON is also installed in
222
224
  the configured entry and every other admitted browser document as `<script
223
225
  type="importmap" data-arcane-import-map>` before module loading. The complete
224
226
  runtime map derives its entries from the selected runtime and browser-runtime
@@ -228,15 +230,13 @@ portable runtime subpaths such as `arcane-os/preference-store` and
228
230
  modules. The result reports the complete map written to the selected
229
231
  application; no fixed entry count is a release contract.
230
232
 
231
- For `appsRoot: "."`, the artifact is `modules/arcane.importmap.json` at the
232
- application root. Set `"legacyAppPaths": false` in `arcane-packager.json` to
233
- omit SDK-generated `apps/<id>/` navigation/PWA compatibility files and aliases.
234
- The same workspace choice applies to `arcane dev` and `arcane package`; restart
235
- an already running dev server after changing it. The default remains `true`.
236
- Root PWA files, app/installation identity and selected authored files are
237
- preserved. Existing files are never deleted by this option. See
238
- [root-only generated output](pwa.md#root-only-generated-output) before changing
239
- the URLs needed by previously installed apps.
233
+ For `appsRoot: "."`, managed imports use the installed package paths and enabled
234
+ PWA files are written beside the root entry. The SDK generates no nested
235
+ `apps/<id>/` redirects or duplicate PWA files and no repository-root `arcane/`
236
+ projection. App and installation identity, saved data, and selected authored
237
+ files remain unchanged. The same layout applies to `arcane dev` and
238
+ `arcane package`. Generated and offline app files are committed; GitHub Actions
239
+ consume those committed files. See [root generated output](pwa.md#root-generated-output).
240
240
 
241
241
  SDK `0.5.17` preserves the physical workspace route count and ordered include
242
242
  list. External and modern integrated routes require `components`, `css`,
@@ -531,8 +531,9 @@ npm exec -- arcane check --app hello-world
531
531
 
532
532
  Creates one complete browser release beneath `dist/<id>/`, preserving the prior
533
533
  output until the replacement is complete. It consumes saved source and managed
534
- import maps, places app files beneath `apps/<id>/`, and retains the configured
535
- shared route destinations. When selected shared content supplies no root
534
+ import maps, keeps standalone app files at the output root, and retains the
535
+ configured shared route destinations. Explicit multi-app workspaces retain
536
+ their selected app beneath `apps/<id>/`. When selected shared content supplies no root
536
537
  `index.html`, the SDK generates one that opens the selected app entry.
537
538
  Source document bases and resource URLs therefore retain their development
538
539
  layout. Packaging does not run tests or checks automatically.
@@ -542,11 +543,10 @@ selected SDK content directly from `node_modules`. Only the portable output
542
543
  receives copies; no workspace `arcane/` projection is required. Its runtime URLs,
543
544
  managed import-map targets, and PWA inventory destinations match source serving.
544
545
 
545
- With `appsRoot: "."`, app files retain their root-relative layout. The optional
546
- root-config `legacyAppPaths: false` omits SDK-generated compatibility files
547
- beneath `apps/<id>/` from the planned and actual output. It does not omit
548
- explicitly selected authored resources at those paths or change the default
549
- packaged installation identity. Omission or `true` preserves existing behavior.
546
+ With `appsRoot: "."`, the planned and actual output retain root-relative app
547
+ files and direct npm package paths. The SDK adds no nested app redirects or
548
+ duplicate PWA worker/inventory files. Explicitly selected authored resources
549
+ and the default packaged installation identity remain unchanged.
550
550
 
551
551
  ```text
552
552
  arcane package [--app <id>] [--dry-run]
@@ -898,10 +898,10 @@ Resend key for all its requests. An absent named key never falls back to the
898
898
  default account. Existing root `RESEND_API_KEY` and
899
899
  `MAIL_PROFILES[name].RESEND_API_KEY` remain fallbacks when the corresponding
900
900
  nested key property is absent. A nested property containing null or an empty
901
- string means the selected key is absent and takes precedence over a legacy key.
901
+ string means the selected key is absent and takes precedence over a root or profile key.
902
902
 
903
903
  Set and delete preserve the file's other settings and profile containers. New
904
- keys are written to the nested mail member. Existing legacy keys are updated
904
+ keys are written to the nested mail member. Existing root or profile keys are updated
905
905
  in place unless the selected nested key property exists; delete removes both
906
906
  representations of only the selected key. Status returns the selected profile,
907
907
  `provider:'resend'`, `storage:'.arcane.env.json'`, and `exists`. Delete returns
@@ -963,7 +963,7 @@ paths. It consumes the configured provider timeout and retry guidance.
963
963
 
964
964
  The Resend credential comes from the selected `.arcane.env.json` entry.
965
965
  An explicit `--profile` overrides `arcane.config.json.mail.profile`; omitting
966
- both selects the default `mail.apiKey`, with the legacy fallback described
966
+ both selects the default `mail.apiKey`, with the root-key fallback described
967
967
  above. Neither the key nor report content is accepted through argv or process
968
968
  environment variables.
969
969
 
@@ -1006,7 +1006,7 @@ Add the listener's certificate paths to `arcane.config.json`:
1006
1006
 
1007
1007
  Supply an existing PEM certificate chain and its private key. Paths resolve
1008
1008
  relative to the selected configuration directory, or may be absolute. They
1009
- belong to the listener regardless of the provider key selected. Legacy root
1009
+ belong to the listener regardless of the provider key selected. Existing root
1010
1010
  `MAIL_TLS_CERT_PATH` and `MAIL_TLS_KEY_PATH` in `.arcane.env.json` remain
1011
1011
  fallbacks for omitted config paths. Explicit programmatic `certPath` and
1012
1012
  `keyPath` options override those files. Missing TLS settings name the fields
@@ -381,7 +381,7 @@ application name and subscriber key. Existing top-level `RESEND_API_KEY` and
381
381
  `MAIL_PROFILES[profile].RESEND_API_KEY` remain supported. A nested selected
382
382
  `apiKey` takes precedence when the property exists, including null or an empty
383
383
  string, which means the selected key is absent. Only an absent nested key
384
- property permits fallback to the corresponding legacy key.
384
+ property permits fallback to the corresponding root or profile key.
385
385
 
386
386
  Programmatic operations resolve both files from `options.cwd`, then
387
387
  `options.workspaceRoot`, then `process.cwd()`, choosing the first supplied
@@ -397,7 +397,7 @@ Configuration precedence is explicit:
397
397
  An explicit null retains the option's existing meaning; it does not select
398
398
  the file value again.
399
399
  2. `arcane.config.json.mail` supplies nonsecret settings absent from those options.
400
- 3. Legacy `.arcane.env.json` root `MAIL_TLS_CERT_PATH` and `MAIL_TLS_KEY_PATH`
400
+ 3. `.arcane.env.json` root `MAIL_TLS_CERT_PATH` and `MAIL_TLS_KEY_PATH`
401
401
  supply certificate paths absent from the selected options and config member.
402
402
  4. Remaining settings use the defaults above.
403
403
 
@@ -452,7 +452,7 @@ non-interactive alternative and rejects a TTY. Each command accepts an optional
452
452
  profile argument, defaulting to `mail` independently of `arcane.config.json.mail.profile`.
453
453
  Set and delete preserve other JSON settings and profiles; status reports
454
454
  existence without returning the key. A new credential is written to the nested
455
- mail member. An existing legacy credential is updated at its existing location
455
+ mail member. An existing root or profile credential is updated at its existing location
456
456
  unless the selected nested key property exists, in which case set updates that
457
457
  nested property. Delete removes both representations of only the selected key,
458
458
  so an older key cannot reappear through fallback. Other settings and profile
@@ -500,7 +500,7 @@ guidance from the same configuration. It does not require gateway TLS paths.
500
500
  Send and serve read each required JSON file once, concurrently when both are
501
501
  needed, before consuming their settings. An injected `readCredential` remains
502
502
  the credential owner and reads once. With that injection, send reads only
503
- `arcane.config.json`; serve also reads `.arcane.env.json` for legacy TLS paths
503
+ `arcane.config.json`; serve also reads `.arcane.env.json` for root TLS paths
504
504
  without interpreting its unused file credential.
505
505
 
506
506
  Start the gateway:
@@ -522,7 +522,7 @@ domain, for example `https://mail.example.com:4433/v1/mail`.
522
522
 
523
523
  Set `arcane.config.json.mail.certPath` to the PEM certificate chain and
524
524
  `mail.keyPath` to its PEM private-key file. These settings belong to the listener
525
- and apply regardless of the selected provider profile. The legacy root
525
+ and apply regardless of the selected provider profile. The root
526
526
  `MAIL_TLS_CERT_PATH` and `MAIL_TLS_KEY_PATH` fields in `.arcane.env.json` remain
527
527
  fallbacks. Relative certificate paths resolve from the selected configuration
528
528
  directory, including explicit programmatic path options; absolute paths are also accepted.
@@ -157,16 +157,17 @@ the same browser URLs and saved managed import maps.
157
157
 
158
158
  Existing `physical-v1` configurations whose first source is `arcane` remain
159
159
  supported. The explicit materializer below still refreshes those projections,
160
- and the default multi-app scaffold retains its existing physical layout. The
160
+ and an explicitly selected multi-app scaffold retains its physical layout. The
161
161
  earlier installed routes with virtual `/arcane` destinations also remain
162
162
  supported. Selecting `installed-v1`
163
163
  does not delete any preexisting workspace files. The separate host-document
164
164
  `generateDocumentImportMaps()` API continues to accept an already materialized
165
165
  runtime; its input contract is unchanged.
166
166
 
167
- ### Optional standalone root application
167
+ ### Standalone root application
168
168
 
169
- `appsRoot: "."` selects one application whose `arcane-app.json`,
169
+ Each standalone app's root is its repository root. `appsRoot: "."` selects
170
+ that layout: `arcane-app.json`,
170
171
  `arcane-package.json`, entry, and app-owned files occupy the workspace root.
171
172
  The declared application ID remains unchanged. `appsRoot: "apps"` continues
172
173
  to discover `apps/<id>` and supports the existing integrated and multi-app
@@ -174,29 +175,19 @@ layouts. Entries and include/exclude paths remain relative to the application.
174
175
 
175
176
  Root HTML uses `<base href="./">`; nested navigable documents use their actual
176
177
  depth back to the workspace. Managed bare imports remain the public interface;
177
- their targets follow the selected npm routes. `arcane import-map` also writes
178
- root-app navigation pages for the previous `/apps/<id>/` links, preserving
179
- query strings and fragments. It preserves authored files at those destinations.
180
- For a direct-installed root PWA it generates the static PWA records at the root,
181
- so normal static hosting needs no SDK request handler or runtime copy.
182
-
183
- To select root-only generated output, set `"legacyAppPaths": false` beside
184
- `"appsRoot": "."` in `arcane-packager.json`. This optional boolean defaults to
185
- `true`; it has no effect on the `appsRoot: "apps"` layout. The shared import-map
186
- refresh, source dev server, package inspection, dry run and package output then
187
- omit SDK-generated `apps/<id>/` navigation pages, navigation aliases, and the
188
- legacy PWA worker/offline inventory. Root PWA files and normal managed imports
189
- remain available. Edit the root configuration before starting `arcane dev`;
190
- restart an existing server after changing this workspace-level choice.
191
-
192
- This option does not change the application ID, stored data, the existing
193
- default installation ID or an authored `pwa.manifest.id`. It neither deletes
194
- existing files nor removes authored resources from the app's include/exclude
195
- selection. Explicitly included files under `apps/<id>/` still serve and package
196
- as authored resources. Previously installed launch URLs and worker update URLs
197
- need their old resources to remain available; retain the default compatibility
198
- output when those URLs still need SDK support. See the
199
- [root PWA compatibility boundary](pwa.md#root-only-generated-output).
178
+ their targets follow the selected npm routes. `arcane import-map` generates
179
+ enabled static PWA records at the app root, so normal static hosting needs no
180
+ SDK request handler or runtime copy. Managed refresh, development serving,
181
+ inspection, dry run and packaging add no nested `apps/<id>/` navigation pages,
182
+ redirects, or duplicate PWA worker/inventory files. There is no output-retention
183
+ switch for those retired records.
184
+
185
+ Application identity, stored data, the existing default installation ID and an
186
+ authored `pwa.manifest.id` remain unchanged. Authored resources continue to
187
+ follow the app's include/exclude selection; generation does not delete existing
188
+ files. Published SDK internals stay package-owned under `node_modules`.
189
+ Generated and offline app files are committed, and GitHub Actions consume
190
+ those committed files. See [root generated output](pwa.md#root-generated-output).
200
191
 
201
192
  Direct-installed root maps expose `arcane-os/modules/<filename>` and
202
193
  `arcane-os/entities/<filename>` (including extensions), plus the existing
@@ -38,8 +38,9 @@ comes from the selected application entry. The app owns names, icons, colors,
38
38
  display preference, routes and descriptions. Use real app icons; the SDK does
39
39
  not invent branding or claim that a browser has installed the app.
40
40
 
41
- Manifest URL fields are relative to the application directory. Source delivery
42
- and packaged delivery retain the app tree at `apps/<id>/`. For packaged output,
41
+ Manifest URL fields are relative to the application directory. Each standalone
42
+ app's root is its repository root. Explicit multi-app source and packaged
43
+ delivery retain the selected app tree at `apps/<id>/`. For multi-app packaged output,
43
44
  the default `start_url` is `./apps/<id>/<entry>`, resolved from the generated
44
45
  root manifest. The default `id` and `scope` remain `./`, preserving the existing
45
46
  deployment-root installation identity. Authored relative URL fields, including
@@ -55,51 +56,30 @@ ordinary static host; it does not copy the installed runtime into `arcane/`.
55
56
  The source root defaults its installation ID to `/apps/<id>/` to preserve the
56
57
  previous source identity while its start URL and scope move to the root. An
57
58
  authored `manifest.id` remains authoritative. Packaged default identity remains
58
- `./`. Existing `/apps/<id>/` navigation aliases retain query strings and
59
- fragments and lead to the selected root document. No stored application data is
60
- rewritten by changing the layout.
61
-
62
- Root PWA generation also retains `apps/<id>/arcane-sw.js` and
63
- `apps/<id>/arcane-offline.json`. These are ordinary generated files served at
64
- their original URLs, including on a static host. The same canonical worker and
65
- current inventory use root resource/navigation URLs, rebased for the previous
66
- scope when deployed beneath a directory. An existing worker's inventory refresh
67
- can learn the new navigation destinations; the browser can update the worker at
68
- its retained script URL through normal update and activation. No cache deletion,
69
- forced activation, user-data migration or application-owned server handler is
70
- introduced. Offline clients require a later successful connection to receive
71
- updated files; fixture coverage does not establish a particular installed app's
72
- actual browser lifecycle.
73
-
74
- ### Root-only generated output
75
-
76
- For a root application, add `"legacyAppPaths": false` to the workspace's
77
- `arcane-packager.json`, alongside `"appsRoot": "."`. The default is `true`.
78
- This selects root-only SDK-generated navigation and PWA output in import-map
79
- refresh, `arcane dev`, package inspection/dry run, and browser packaging:
80
-
81
- - The four root PWA files remain generated normally.
82
- - The SDK generates no `apps/<id>/` navigation pages, legacy worker or legacy
83
- offline inventory, and adds no legacy navigation aliases or dev redirects.
59
+ `./`. App identity and saved application data remain unchanged.
60
+
61
+ ### Root generated output
62
+
63
+ For a standalone application, `appsRoot: "."` places the app at its repository
64
+ root. Import-map refresh, `arcane dev`, package inspection/dry run and browser
65
+ packaging use that layout directly:
66
+
67
+ - The four root PWA files are generated normally when PWA is enabled.
68
+ - The SDK generates no `apps/<id>/` navigation pages, nested worker or duplicate
69
+ offline inventory, and adds no redirects for that path family.
70
+ - Runtime resources use the selected npm package paths; no repository-root
71
+ `arcane/` projection is generated.
84
72
  - The default source installation ID remains `/apps/<id>/`, the default
85
73
  packaged installation ID remains `./`, and an explicit `manifest.id` remains
86
- authoritative. An ID is an installation identifier, not a request to generate
87
- a directory. App identity and saved application data remain unchanged.
88
- - Existing files are left on disk. Explicitly selected authored old-path
89
- resources remain in the normal source/package/offline inventory; this setting
90
- is not a deletion or migration command.
91
-
92
- Restart `arcane dev` after changing this workspace configuration. Omission or
93
- `true` retains the compatibility behavior described above. The option has no
94
- effect on apps whose configured `appsRoot` is `"apps"`.
95
-
96
- An installed app may still launch an old `/apps/<id>/` URL, and an existing
97
- worker registration may still update its old script/inventory URL. Disabling
98
- generation does not redirect those installed clients, unregister their worker,
99
- clear caches or guarantee their next update. Keep compatibility output enabled
100
- while old URLs still require SDK support, or supply the required resources
101
- through the application's own declared files and hosting policy. This SDK
102
- option alone makes no claim about any existing installation's adoption.
74
+ authoritative. An ID identifies the installation; it does not generate a
75
+ directory or a redirect.
76
+ - Authored resources follow their normal include/exclude selection. Generation
77
+ does not delete existing files, stored application data, or worker caches.
78
+
79
+ Generated and offline app files are committed. GitHub Actions consume those
80
+ committed files rather than generating them. Actual multi-app workspaces keep
81
+ their explicit `appsRoot: "apps"` layout. This source/package contract does not
82
+ establish any particular installed application's browser lifecycle.
103
83
 
104
84
  ### Offline resource selection
105
85
 
@@ -1057,14 +1057,13 @@ async function usevalidateAppConfig(...arguments_) {
1057
1057
 
1058
1058
  Validates one root packager mapping and its fixed shared-route boundaries.
1059
1059
 
1060
- The schema-1 `arcane-packager.json` accepts the optional boolean
1061
- `legacyAppPaths`, normalized to `true` when omitted. With `appsRoot: "."`,
1062
- setting it to `false` omits generated legacy `apps/<id>/` navigation/PWA output
1063
- across managed-map refresh, development serving, inspection and packaging.
1064
- It leaves installation identity and authored file selection unchanged and has
1065
- no effect with `appsRoot: "apps"`. See
1066
- [standalone root applications](protocols.md#optional-standalone-root-application)
1067
- for configuration, retained-file and existing-installation behavior.
1060
+ The schema-1 `arcane-packager.json` uses `appsRoot: "."` for standalone apps:
1061
+ each app's root is its repository root. Managed-map refresh, development
1062
+ serving, inspection and packaging use root app files and direct npm package
1063
+ paths without generated nested app redirects or duplicate PWA files.
1064
+ Installation identity and authored file selection remain unchanged.
1065
+ Explicit `appsRoot: "apps"` supports real multi-app workspaces. See
1066
+ [standalone root applications](protocols.md#standalone-root-application).
1068
1067
 
1069
1068
  ### Signature and result
1070
1069
 
@@ -3730,7 +3729,7 @@ actual HTTPS port while preserving the original request path and query. Both lis
3730
3729
  use the selected host.
3731
3730
 
3732
3731
  Source servers default to HTTPS, including localhost; packaged browser previews
3733
- require HTTPS. The legacy `https` option is accepted but `https:false` and
3732
+ require HTTPS. The existing `https` option is accepted; `https:false` and
3734
3733
  `tls:false` alone do not disable HTTPS. HTTPS startup reads `.arcane/dev/server-cert.pem` and
3735
3734
  `.arcane/dev/server-key.pem` relative to `workspaceRoot` unless explicit
3736
3735
  `certPath` and `keyPath` are supplied together; relative paths resolve from the
@@ -4192,7 +4191,7 @@ The operation refreshes the selected authored descriptor's `arcane-package.json`
4192
4191
  projection and managed import maps under one development-refresh lock, then
4193
4192
  releases that lock before opening the selected source listener or listeners.
4194
4193
  It returns their application endpoints and shared shutdown
4195
- lifecycle. Legacy package-only apps remain unchanged. The operation generates
4194
+ lifecycle. Package-only apps remain unchanged. The operation generates
4196
4195
  no packaged output; enabled PWA manifests
4197
4196
  are served directly from the selected source resources.
4198
4197
 
@@ -489,7 +489,7 @@ files. Their explicit precedence and credential compatibility rules are in the
489
489
  | CLI's early default host, port, and send/serve profile values | Y/Y/Y | Remove premature default assignment. The same defaults remain after file configuration resolves, while explicit CLI values retain priority. Assigning defaults before reading JSON would conceal the deployment's chosen settings. |
490
490
  | `origins` string array and list replacement | Y/Y/N | Keep one exact list for multiple caller domains. Explicit options replace the file list, including empty arrays. Existing `origin`, `allowTo`, `errorTo`, and `requestTimeout` aliases remain compatible and win over their canonical programmatic names when both are supplied. No normalization or list-merging helper is needed. |
491
491
  | Root provider-key, named-profile, and TLS compatibility | Y/Y/N | Preserve supported inputs using `RESEND_API_KEY`, `MAIL_PROFILES`, `MAIL_TLS_CERT_PATH`, and `MAIL_TLS_KEY_PATH`. Nested selected key presence takes priority even when null or empty. Named profiles never select a different account's default key. Named-profile deployment use is unverified; the retained behavior is a compatibility contract. |
492
- | Key set/status/delete operations | Y/Y/N | Preserve credential management and secret-free status. New keys use the nested member; existing legacy keys are updated in place unless a nested key exists. Delete removes both selected representations to prevent an old credential reappearing, preserving other keys, settings, and profile containers. Key commands still default to `mail` independently of the serving profile. |
492
+ | Key set/status/delete operations | Y/Y/N | Preserve credential management and secret-free status. New keys use the nested member; existing root or profile keys are updated in place unless a nested key exists. Delete removes both selected representations to prevent an old credential reappearing, preserving other keys, settings, and profile containers. Key commands still default to `mail` independently of the serving profile. |
493
493
  | Send configuration | Y/Y/N | Use the same profile, sender, provider-timeout, and retry-guidance settings for direct sending. A send needs provider authority and its report, so listener certificates remain a serve-only prerequisite. Reports and idempotency keys remain per-operation inputs. |
494
494
  | Explicit runtime dependencies and callbacks | Y/Y/N | Preserve `readCredential`, provider injection, observation, cancellation, and subscription callbacks as programmatic inputs. A function or signal is not JSON configuration. The portable browser mail import retains its existing dependency boundary. |
495
495
  | Shared sender omission | Y/Y/N | Preserve per-report `from` and provider-template sender selection when no shared override is configured. One listener can therefore serve independent application sender identities. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -5,12 +5,6 @@ export function appRelativeRoot(config, appId) {
5
5
  return config.appsRoot === '.' ? '' : `apps/${appId}`;
6
6
  }
7
7
 
8
- // Generated compatibility URLs are separate from installation and storage identity.
9
- export function rootAppLegacyPath(config, appId) {
10
- return config.appsRoot === '.' && config.legacyAppPaths !== false
11
- ? `apps/${appId}` : undefined;
12
- }
13
-
14
8
  export function resolveAppRoot(workspaceRoot, config, appId) {
15
9
  return path.resolve(workspaceRoot, appRelativeRoot(config, appId));
16
10
  }
@@ -20,19 +14,3 @@ export function appBaseHref(workspaceRoot, appRoot, document = 'index.html') {
20
14
  const relative = path.relative(directory, workspaceRoot).split(path.sep).join('/');
21
15
  return relative ? `${relative}/` : './';
22
16
  }
23
-
24
- export function rootAppNavigation(appId, entry, documents = []) {
25
- const redirects=new Map();
26
- for(const document of documents)redirects.set(`apps/${appId}/${document}`,document);
27
- redirects.set(`apps/${appId}/index.html`,entry);
28
- return [...redirects].map(([file,target])=>{
29
- const relative=path.posix.relative(path.posix.dirname(file),target);
30
- const destination=JSON.stringify(relative).replaceAll('<','\\u003c');
31
- return {
32
- path:file,target:`/${target}`,
33
- content:'<!doctype html>\n<!-- Arcane root application navigation -->\n'
34
- +'<meta charset="utf-8">\n<title>Opening application</title>\n'
35
- +`<script>const target=new URL(${destination},location.href);target.search=location.search;target.hash=location.hash;location.replace(target.href);</script>\n`
36
- };
37
- });
38
- }
package/src/cli/main.mjs CHANGED
@@ -506,7 +506,7 @@ function operationOptions(command,parsed,cwd){
506
506
  return {
507
507
  targetPath:path.resolve(cwd,values.path??appId),
508
508
  appId,
509
- appsRoot:values['apps-root']??'apps',
509
+ appsRoot:values['apps-root']??'.',
510
510
  displayName:values['display-name'],
511
511
  target:values.target??'browser',
512
512
  initializeGit:flags.has('git')
@@ -6,7 +6,7 @@ import https from 'node:https';
6
6
  import os from 'node:os';
7
7
  import path from 'node:path';
8
8
  import {resolveWorkspace} from './workspace.mjs';
9
- import {appRelativeRoot,rootAppLegacyPath} from './app-layout.mjs';
9
+ import {appRelativeRoot} from './app-layout.mjs';
10
10
  import {readInstalledSdkLayout} from './sdk-runtime-layout.mjs';
11
11
  import {APP_DESCRIPTOR_NAME, projectPackageManifest} from './app-descriptor.mjs';
12
12
  import {APP_CONFIG_NAME, validateAppConfig} from './packager/core.mjs';
@@ -1047,26 +1047,8 @@ async function startOwnedDevServer({
1047
1047
  const pwaResourceUrls = new Set();
1048
1048
  function developmentPwaArtifacts(selectedRoutes, assets = [], version = assetVersion) {
1049
1049
  const rootApp = selectedRoutes.config.appsRoot === '.';
1050
- const legacyAppPath = rootAppLegacyPath(selectedRoutes.config, selectedRoutes.appId);
1051
1050
  const appBase = applicationSourcePath(selectedRoutes.config,selectedRoutes.appId);
1052
1051
  const navigationAliases = {'/': selectedRoutes.startPath};
1053
- if (legacyAppPath) {
1054
- const legacyBase = `/${legacyAppPath}`;
1055
- navigationAliases[legacyBase] = selectedRoutes.startPath;
1056
- navigationAliases[`${legacyBase}/`] = selectedRoutes.startPath;
1057
- for (const asset of [selectedRoutes.startPath,...assets]) {
1058
- const pathname = new URL(asset,'http://arcane.invalid').pathname;
1059
- if (!/\.html?$/iu.test(pathname)) continue;
1060
- const segments = decodeURIComponent(pathname).split('/').filter(Boolean);
1061
- const mapping = selectedRoutes.mappings.find(function matchingNavigationRoute(route) {
1062
- return route.prefix.every(function matchingNavigationSegment(segment,index) {
1063
- return segments[index] === segment;
1064
- });
1065
- });
1066
- if (mapping?.kind === 'app') navigationAliases[`${legacyBase}${pathname}`] = pathname;
1067
- }
1068
- navigationAliases[`${legacyBase}/index.html`] = selectedRoutes.startPath;
1069
- }
1070
1052
  return createPwaArtifacts(
1071
1053
  {
1072
1054
  app: {
@@ -1085,7 +1067,6 @@ async function startOwnedDevServer({
1085
1067
  ...(rootApp ? {
1086
1068
  installationId:`/apps/${routeSet.appId}/`
1087
1069
  } : {}),
1088
- legacyAppPath,
1089
1070
  runtimeBase: selectedRoutes.browserRuntimeBase
1090
1071
  ?? selectedRoutes.installed?.browserRuntimeBase ?? '/arcane/sdk/',
1091
1072
  mode: 'development'
@@ -1177,34 +1158,17 @@ async function startOwnedDevServer({
1177
1158
  const target=parseRequestTarget(request.url);
1178
1159
  if(!target){deny(response,400,'Invalid request path.');return;}
1179
1160
  const {segments}=target;
1180
- const legacyAppRequest = mode === 'source' && rootAppLegacyPath(routeSet.config, routeSet.appId)
1181
- && segments[0] === 'apps' && segments[1] === routeSet.appId;
1182
- const legacyPwaRequest = legacyAppRequest && segments.length === 3
1183
- && ['arcane-sw.js', 'arcane-offline.json'].includes(segments[2]);
1184
- const generatedPwaPath = legacyPwaRequest
1185
- || ['/arcane.webmanifest', '/arcane-offline.json', '/arcane-sw.js', '/arcane-pwa.mjs']
1186
- .includes(target.path);
1161
+ const generatedPwaPath = ['/arcane.webmanifest', '/arcane-offline.json', '/arcane-sw.js', '/arcane-pwa.mjs']
1162
+ .includes(target.path);
1187
1163
  const requestedMapping = currentSourceRoutes.mappings.find(function currentRequestMapping(route) {
1188
1164
  return route.prefix.every(function currentRequestSegment(segment,index) {
1189
1165
  return segments[index] === segment;
1190
1166
  });
1191
1167
  });
1192
- const appRequest = requestedMapping?.kind === 'app' || legacyAppRequest;
1168
+ const appRequest = requestedMapping?.kind === 'app';
1193
1169
  const selectedRoutes = mode === 'source' && (appRequest || segments.length === 0 || generatedPwaPath)
1194
1170
  ? await refreshSourceRoutes() : currentSourceRoutes;
1195
1171
  const pwaEnabled = mode === 'source' ? selectedRoutes.app?.pwa?.enabled === true : routeSet.pwa;
1196
- const authoredLegacyResource = legacyAppRequest
1197
- && sourcePathAllowed(segments, selectedRoutes.app);
1198
- if (legacyAppRequest
1199
- && !(pwaEnabled && legacyPwaRequest)
1200
- && !authoredLegacyResource) {
1201
- const legacyPath = target.pathname.slice(`/apps/${routeSet.appId}`.length);
1202
- const location = !legacyPath || legacyPath === '/' || legacyPath === '/index.html'
1203
- ? selectedRoutes.startPath : legacyPath;
1204
- response.writeHead(302,{location:`${location}${target.search}`});
1205
- response.end();
1206
- return;
1207
- }
1208
1172
  if (mode === 'source' && pwaEnabled
1209
1173
  && generatedPwaPath) {
1210
1174
  const generated = await sourcePwaArtifact(target.path, selectedRoutes);
@@ -81,9 +81,9 @@ function mailCredentialEntry(settings, location) {
81
81
  : `mail.profiles[${JSON.stringify(location.profile)}].apiKey`
82
82
  };
83
83
  if (nestedSettings && Object.hasOwn(nestedSettings, 'apiKey')) return nestedEntry;
84
- const legacySettings = mailProfileSettings(settings, location);
85
- if (legacySettings && Object.hasOwn(legacySettings, 'RESEND_API_KEY')) {
86
- return {profileSettings: legacySettings, key: 'RESEND_API_KEY', setting: location.setting};
84
+ const profileSettings = mailProfileSettings(settings, location);
85
+ if (profileSettings && Object.hasOwn(profileSettings, 'RESEND_API_KEY')) {
86
+ return {profileSettings, key: 'RESEND_API_KEY', setting: location.setting};
87
87
  }
88
88
  return nestedEntry;
89
89
  }
@@ -12,7 +12,7 @@ import {
12
12
  } from 'node:fs/promises';
13
13
  import path from 'node:path';
14
14
  import {pathToFileURL} from 'node:url';
15
- import {appRelativeRoot,resolveAppRoot,rootAppLegacyPath,rootAppNavigation} from '../app-layout.mjs';
15
+ import {appRelativeRoot,resolveAppRoot} from '../app-layout.mjs';
16
16
  import {readInstalledSdkLayout} from '../sdk-runtime-layout.mjs';
17
17
  import {withWorkspaceOperationLock} from '../workspace-operation-lock.mjs';
18
18
  import {
@@ -275,14 +275,11 @@ function validateSharedRoute(route,label){
275
275
  }
276
276
 
277
277
  export function validateRootConfig(value,configPath=ROOT_CONFIG_NAME){
278
- assertOnlyKeys(value,new Set(['schemaVersion','appsRoot','distRoot','sharedPayloads','legacyAppPaths']),ROOT_CONFIG_NAME);
278
+ assertOnlyKeys(value,new Set(['schemaVersion','appsRoot','distRoot','sharedPayloads']),ROOT_CONFIG_NAME);
279
279
  if(value.schemaVersion!==1)fail(`${ROOT_CONFIG_NAME}.schemaVersion must be 1.`);
280
280
  if(!['apps','.'].includes(value.appsRoot)||value.distRoot!=='dist'){
281
281
  fail(`${ROOT_CONFIG_NAME} must bind appsRoot to "apps" or "." and distRoot to "dist".`);
282
282
  }
283
- if (value.legacyAppPaths !== undefined && !is.boolean(value.legacyAppPaths)) {
284
- fail(`${ROOT_CONFIG_NAME}.legacyAppPaths must be a boolean.`);
285
- }
286
283
  if(!isPlainObject(value.sharedPayloads)){
287
284
  fail(`${ROOT_CONFIG_NAME}.sharedPayloads must be an object.`);
288
285
  }
@@ -297,8 +294,7 @@ export function validateRootConfig(value,configPath=ROOT_CONFIG_NAME){
297
294
  );
298
295
  }
299
296
  return {
300
- schemaVersion:1,appsRoot:value.appsRoot,distRoot:'dist',sharedPayloads,configPath,
301
- legacyAppPaths:value.legacyAppPaths ?? true
297
+ schemaVersion:1,appsRoot:value.appsRoot,distRoot:'dist',sharedPayloads,configPath
302
298
  };
303
299
  }
304
300
 
@@ -581,15 +577,6 @@ async function optionalDescriptor(context){
581
577
  async function inspectContext(context,{signal}={}){
582
578
  const records=await collectPackageRecords(context,{signal});
583
579
  const documents=await browserDocuments(records,context.config.entry);
584
- const navigation=rootAppLegacyPath(context.rootConfig,context.appId)?rootAppNavigation(
585
- context.appId,context.config.entry,documents.map(document=>document.path)
586
- ):[];
587
- for(const redirect of navigation){
588
- const selected=records.find(record=>pathKey(record.destination)===pathKey(redirect.path));
589
- if(selected&&!(await readFile(selected.source,'utf8')).includes('<!-- Arcane root application navigation -->')){
590
- fail(`Root application navigation would replace selected content: ${redirect.path}.`);
591
- }
592
- }
593
580
  return {
594
581
  appId:context.appId,
595
582
  displayName:context.config.displayName,
@@ -607,7 +594,7 @@ async function inspectContext(context,{signal}={}){
607
594
  ...(context.config.adapter===undefined?{}:{adapter:context.config.adapter}),
608
595
  descriptor:await optionalDescriptor(context),
609
596
  browserDocuments:documents,
610
- files:[...new Set(['index.html',...records.map(record=>record.destination),...navigation.map(redirect=>redirect.path)])].sort(compareText),
597
+ files:[...new Set(['index.html',...records.map(record=>record.destination)])].sort(compareText),
611
598
  output:path.relative(context.workspaceRoot,context.outputRoot).split(path.sep).join('/')
612
599
  };
613
600
  }
@@ -763,13 +750,9 @@ async function replaceDirectory(stagingRoot,outputRoot){
763
750
  async function packageWithContext(context,options={}){
764
751
  const {signal,onEvent,browserPwa=true}=options;
765
752
  const pwaEnabled=browserPwa&&context.config.pwa?.enabled===true;
766
- const legacyAppPath=rootAppLegacyPath(context.rootConfig,context.appId);
767
753
  const appPath=appRelativeRoot(context.rootConfig,context.appId);
768
754
  const entryPath=appPackagePath(context,context.config.entry);
769
755
  const inspected=await inspectContext(context,{signal});
770
- const navigation=legacyAppPath?rootAppNavigation(
771
- context.appId,context.config.entry,inspected.browserDocuments.map(document=>document.path)
772
- ):[];
773
756
  if(options.dryRun){
774
757
  return {
775
758
  appId:context.appId,
@@ -782,11 +765,7 @@ async function packageWithContext(context,options={}){
782
765
  PWA_MANIFEST_NAME,
783
766
  PWA_OFFLINE_MANIFEST_NAME,
784
767
  PWA_WORKER_NAME,
785
- PWA_BOOTSTRAP_NAME,
786
- ...(legacyAppPath?[
787
- `${legacyAppPath}/${PWA_WORKER_NAME}`,
788
- `${legacyAppPath}/${PWA_OFFLINE_MANIFEST_NAME}`
789
- ]:[])
768
+ PWA_BOOTSTRAP_NAME
790
769
  ]:[])
791
770
  ].sort(compareText)
792
771
  };
@@ -824,18 +803,6 @@ async function packageWithContext(context,options={}){
824
803
  }else{
825
804
  await copyBase();
826
805
  }
827
- for(const redirect of navigation){
828
- throwIfAborted(signal);
829
- const filePath=path.join(stagingRoot,...redirect.path.split('/'));
830
- try{
831
- const current=await readFile(filePath,'utf8');
832
- if(!current.includes('<!-- Arcane root application navigation -->')){
833
- fail(`Root application navigation would replace package content: ${redirect.path}.`);
834
- }
835
- }catch(error){if(error.code!=='ENOENT')throw error;}
836
- await mkdir(path.dirname(filePath),{recursive:true});
837
- await writeFile(filePath,redirect.content,'utf8');
838
- }
839
806
  const files=await listOutputFiles(stagingRoot,{signal});
840
807
  // Traverse actual browser resources after the adapter finishes. Files
841
808
  // included only as application documents retain their original content.
@@ -957,14 +924,6 @@ async function packageWithContext(context,options={}){
957
924
  fail(`Package output is missing its entry file: ${context.config.entry}.`);
958
925
  }
959
926
  const installed=pwaEnabled?await readInstalledSdkLayout(context.workspaceRoot,context.rootConfig):null;
960
- const navigationAliases=navigation.length?{
961
- './':packageResourceUrl(entryPath),
962
- [`./apps/${context.appId}`]:packageResourceUrl(entryPath),
963
- [`./apps/${context.appId}/`]:packageResourceUrl(entryPath),
964
- ...Object.fromEntries(navigation.map(redirect=>[
965
- packageResourceUrl(redirect.path),packageResourceUrl(redirect.target.slice(1))
966
- ]))
967
- }:undefined;
968
927
  const pwaArtifacts=pwaEnabled?createPwaArtifacts({
969
928
  app:{
970
929
  id:context.appId,
@@ -973,9 +932,7 @@ async function packageWithContext(context,options={}){
973
932
  entry:packageResourceUrl(entryPath)
974
933
  },
975
934
  appPath,
976
- ...(legacyAppPath?{legacyAppPath}:{}),
977
935
  ...(installed?.direct?{runtimeBase:`.${installed.browserRuntimeBase}`} : {}),
978
- ...(navigationAliases?{navigationAliases}:{}),
979
936
  sdkVersion:assetVersion,
980
937
  pwa:context.config.pwa,
981
938
  files,
package/src/pwa.mjs CHANGED
@@ -1,6 +1,5 @@
1
1
  import Is from 'strong-type';
2
2
  import {randomUUID} from 'node:crypto';
3
- import path from 'node:path';
4
3
  import {createPwaWorkerScript} from './pwa-worker.mjs';
5
4
  import {versionAssetUrl} from './import-map.mjs';
6
5
 
@@ -204,7 +203,6 @@ export function createPwaArtifacts(
204
203
  runtimeBase = './arcane/sdk/',
205
204
  appBase,
206
205
  installationId,
207
- legacyAppPath,
208
206
  appPath = '',
209
207
  navigationAliases,
210
208
  revision
@@ -316,47 +314,6 @@ controller.ready.catch(
316
314
  },
317
315
  {path: PWA_BOOTSTRAP_NAME, content: bootstrap}
318
316
  ];
319
- if (legacyAppPath) {
320
- const directory = legacyAppPath.endsWith('/') ? legacyAppPath : `${legacyAppPath}/`;
321
- const priorDirectory = new URL(resourceUrl('./', directory), 'https://arcane.invalid/').pathname;
322
- // Existing registrations continue updating their own script and inventory URLs.
323
- function priorScopeUrl(value) {
324
- if (value.startsWith('/') || /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(value)) return value;
325
- const resolved = new URL(value, 'https://arcane.invalid/');
326
- let relative = path.posix.relative(priorDirectory, resolved.pathname);
327
- if (!relative) {
328
- relative = resolved.pathname.endsWith('/')
329
- ? './'
330
- : `../${path.posix.basename(resolved.pathname)}`;
331
- }
332
- else {
333
- if (!relative.startsWith('.')) relative = `./${relative}`;
334
- if (resolved.pathname.endsWith('/') && !relative.endsWith('/')) relative += '/';
335
- }
336
- return `${relative}${resolved.search}${resolved.hash}`;
337
- }
338
- const legacyOfflineManifest = {
339
- ...offlineManifest,
340
- assets: [...new Set([
341
- ...offlineManifest.assets.map(priorScopeUrl),
342
- `./${PWA_OFFLINE_MANIFEST_NAME}`
343
- ])],
344
- navigationAliases: Object.fromEntries(
345
- Object.entries(offlineManifest.navigationAliases).map(
346
- function priorScopeNavigation([alias, target]) {
347
- return [priorScopeUrl(alias), priorScopeUrl(target)];
348
- }
349
- )
350
- )
351
- };
352
- generatedFiles.push(
353
- {
354
- path: `${directory}${PWA_WORKER_NAME}`,
355
- content: createPwaWorkerScript(legacyOfflineManifest, priorScopeUrl(`${runtimeBase}pwa.mjs`))
356
- },
357
- {path: `${directory}${PWA_OFFLINE_MANIFEST_NAME}`, content: json(legacyOfflineManifest)}
358
- );
359
- }
360
317
  return {
361
318
  manifest,
362
319
  offlineManifest,
package/src/scaffold.mjs CHANGED
@@ -316,7 +316,7 @@ async function runGitInit(workspaceRoot,signal,onEvent){
316
316
  export async function createWorkspace({
317
317
  targetPath,
318
318
  appId,
319
- appsRoot='apps',
319
+ appsRoot='.',
320
320
  displayName,
321
321
  target='browser',
322
322
  initializeGit=false,
@@ -400,7 +400,7 @@ export async function initWorkspace({
400
400
  },async workspaceOperationLease=>{
401
401
  const profile=await existingWorkspaceProfile(resolvedRoot);
402
402
  const workspaceMode=profile?.workspaceMode??'external';
403
- const selectedAppsRoot=appsRoot??profile?.config.appsRoot??'apps';
403
+ const selectedAppsRoot=appsRoot??profile?.config.appsRoot??'.';
404
404
  if(!['apps','.'].includes(selectedAppsRoot))fail('appsRoot must be apps or .','ARCANE_USAGE');
405
405
  if(profile&&selectedAppsRoot!==profile.config.appsRoot){
406
406
  fail('appsRoot must match the existing workspace layout; init does not relocate an application.','ARCANE_USAGE');
@@ -439,6 +439,7 @@ export async function initWorkspace({
439
439
  const template=workspaceMode==='integrated'
440
440
  ?workspaceTemplate({
441
441
  appId,
442
+ appsRoot:selectedAppsRoot,
442
443
  displayName,
443
444
  appOnly:true,
444
445
  namedImports:true,
@@ -60,12 +60,12 @@ export function createWorkspaceLockDocument({
60
60
 
61
61
  export function workspaceTemplate({
62
62
  appId,
63
- appsRoot='apps',
63
+ appOnly=false,
64
+ appsRoot=appOnly?'apps':'.',
64
65
  displayName,
65
66
  sdkDependencyName=SDK_NAME,
66
67
  sdkDependencySpecifier=SDK_VERSION,
67
68
  sdkPackageSource=`node_modules/${sdkDependencyName}`,
68
- appOnly=false,
69
69
  namedImports=true,
70
70
  minimumCoreVersion='0.8.12',
71
71
  target='browser',
@@ -199,6 +199,7 @@ import map in every directly navigable descriptor-admitted \`.html\`/\`.htm\`
199
199
  document. HTML component fragments remain package files but do not receive a
200
200
  document-level base or managed import map.
201
201
  Development, package, and build refresh that shared inventory when the selected operation needs it.
202
+ Commit generated import maps and enabled offline app files. Hosting workflows consume those committed files rather than generating them.
202
203
  Named \`${directRuntime?'arcane-os/modules/* and arcane-os/entities/*':'arcane/*'}\` imports resolve through the managed map to the selected SDK files. Packaging copies the complete selected application, runtime, and specifier
203
204
  map to \`dist/${appId}\` without running application tests. Run \`verify\` only when
204
205
  the user explicitly selects verification or a release artifact that requires it;
package/src/toolchain.mjs CHANGED
@@ -12,7 +12,6 @@ import {
12
12
  import {loadArcaneIntegratedProvider} from './integrated-provider-loader.mjs';
13
13
  import {startDevServer} from './dev-server.mjs';
14
14
  import {applyPwaEntryReferences,generateImportMap,readApplicationTestImportMapContext} from './import-map.mjs';
15
- import {rootAppLegacyPath,rootAppNavigation} from './app-layout.mjs';
16
15
  import {createPwaArtifacts} from './pwa.mjs';
17
16
  import {readInstalledSdkLayout} from './sdk-runtime-layout.mjs';
18
17
  import {withWorkspaceOperationLock} from './workspace-operation-lock.mjs';
@@ -327,48 +326,15 @@ async function refreshPreparedImportMap(prepared,{signal,onEvent,workspaceOperat
327
326
  async function refreshRootApplicationFiles(prepared,inspected,importMap,{signal,onEvent}){
328
327
  const {workspaceRoot,appId}=prepared;
329
328
  const manifest=prepared.validation.app.manifest;
330
- const legacyAppPath = rootAppLegacyPath(prepared.validation.config, appId);
331
- const navigation = legacyAppPath ? rootAppNavigation(
332
- appId,
333
- manifest.entry,
334
- inspected.browserDocuments.map(
335
- function rootBrowserDocumentPath(document) {
336
- return document.path;
337
- }
338
- )
339
- ) : [];
340
- // These aliases belong to the SDK only after generation; retained app files stay authored.
341
- for(const redirect of navigation){
342
- throwIfAborted(signal);
343
- try{
344
- const current=await readFile(path.join(workspaceRoot,...redirect.path.split('/')),'utf8');
345
- if(!current.includes('<!-- Arcane root application navigation -->')){
346
- throw new ArcaneError(ERROR_CODES.workspaceInvalid,
347
- `Root application navigation would replace authored content: ${redirect.path}.`);
348
- }
349
- }catch(error){if(error.code!=='ENOENT')throw error;}
350
- }
351
329
  const installed=await readInstalledSdkLayout(workspaceRoot,prepared.validation.config);
352
330
  const entry=`/${manifest.entry.split('/').map(encodeURIComponent).join('/')}`;
353
331
  const navigationAliases={
354
- '/':entry,
355
- ...(legacyAppPath ? {
356
- [`/${legacyAppPath}`]:entry,
357
- [`/${legacyAppPath}/`]:entry,
358
- ...Object.fromEntries(
359
- navigation.map(
360
- function rootNavigationAlias(redirect) {
361
- return [`/${redirect.path}`,redirect.target];
362
- }
363
- )
364
- )
365
- } : {})
332
+ '/':entry
366
333
  };
367
334
  // The source host serves the installed files in place. There is no runtime projection.
368
335
  const files=[...new Set([
369
336
  ...inspected.files.filter(file=>file!=='index.html'||manifest.include.includes('index.html')),
370
- importMap.artifactRelativePath,
371
- ...navigation.map(redirect=>redirect.path)
337
+ importMap.artifactRelativePath
372
338
  ])];
373
339
  const pwa=installed?.direct&&manifest.pwa?.enabled?createPwaArtifacts({
374
340
  app:{id:appId,displayName:manifest.displayName,version:manifest.version,entry},
@@ -378,12 +344,11 @@ async function refreshRootApplicationFiles(prepared,inspected,importMap,{signal,
378
344
  basePath:'/',
379
345
  appBase:'/',
380
346
  installationId:`/apps/${appId}/`,
381
- legacyAppPath,
382
347
  runtimeBase:installed.browserRuntimeBase,
383
348
  mode:'development',
384
349
  navigationAliases
385
350
  }):null;
386
- for(const file of [...navigation,...(pwa?.files??[])]){
351
+ for(const file of pwa?.files??[]){
387
352
  throwIfAborted(signal);
388
353
  const filePath=path.join(workspaceRoot,...file.path.split('/'));
389
354
  await mkdir(path.dirname(filePath),{recursive:true});
@@ -402,7 +367,7 @@ async function refreshRootApplicationFiles(prepared,inspected,importMap,{signal,
402
367
  }
403
368
  await emit(onEvent,{
404
369
  type:'import-map.root-files.completed',appId,
405
- navigation:navigation.map(redirect=>redirect.path),
370
+ navigation:[],
406
371
  pwa:pwa?.files.map(file=>file.path)??[]
407
372
  });
408
373
  }