Skip to content

Project structure and source model

A Workspace UI Project is one versioned tree of UTF-8 text files stored in the workspace database. You author everything under src/ and the project README.md; Moltaro maintains the page manifest and owns the build toolchain and every generated project file. Workspace UI code is trusted workspace code compiled by Moltaro — the boundary is governance and review, not a hostile-code sandbox.

workspace-ui.json
src/
pages/
components/
stores/
api/
locales/
en.json
uk.json
de.json
pl.json
es.json
README.md

Besides workspace-ui.json and README.md, every file must live under src/ and end in .vue, .ts, or .json. Reserved segments such as node_modules, dist, build, bin, obj, and .git are rejected, as are Windows device names and paths longer than 512 characters. Page code goes to src/pages, shared components to src/components, Pinia stores to src/stores, API clients to src/api, and every user-facing string to all five locale files — see Pages, components, and stores.

workspace-ui.json and the five files under src/locales/ are the required system skeleton. UI Studio hides the manifest behind its typed Pages registry and protects the locale files from rename and deletion. The manual-edit and archive APIs still include the physical manifest as the portable source contract, but they do not permit renaming or deleting required system files. README.md and ordinary page, component, store, and client files remain author-owned. A manifest with an empty Pages array is a valid empty project; Check and Build do not require a placeholder page.

workspace-ui.json is declarative page metadata, not code. In UI Studio, edit this metadata through Pages: choose a component, title key, icon, Default or Fullscreen layout, optional nested route, and required permissions. Saving creates a new immutable revision. Removing a registration does not delete its source or localization files.

For ZIP and Configuration API authoring, FormatVersion is required and currently 1. Each entry in Pages declares one runtime page that end users open at /apps/<pageKey>:

{
"FormatVersion": 1,
"Pages": [
{
"Key": "record-details",
"Component": "src/pages/RecordDetailsPage.vue",
"TitleKey": "pages.record-details.title",
"Icon": "mdi-card-account-details-outline",
"Layout": "Default",
"RequiredPermissions": [],
"Route": { "Path": "entities/:entityId/instances/:instanceId" }
}
]
}
  • Key is the stable route and runtime identity of the page.
  • Component must resolve to one .vue file inside src/pages.
  • TitleKey must exist in all supported locale files.
  • Icon uses the standard mdi- icon naming.
  • Layout is Default (standard app shell) or Fullscreen (the page owns the viewport). A missing or unknown value is a build error, never an implicit fallback.
  • RequiredPermissions entries are normalized and deduplicated; unknown keys produce build diagnostics instead of being silently ignored.
  • Route.Path is optional and declares sub-routes below the page: a relative path of lowercase kebab-case literals and named :parameter segments, without leading or trailing slashes, duplicate parameter names, or query/hash characters.

The manifest cannot declare arbitrary route prefixes, navigation sections, script URLs, stylesheets, or external origins.

package.json, lockfiles (package-lock.json, pnpm-lock.yaml, yarn.lock), tsconfig*.json, vite.config.*, eslint.config.*, and the host type declarations (env.d.ts) are generated by Moltaro and are never part of the stored source. A Studio or API save that targets one of these paths is rejected as unsupported; a ZIP upload that contains them simply ignores those entries and imports only the source files.

Source is text only and must be strictly valid UTF-8; files containing binary data are rejected. A project holds at most 500 files, at most 512 KiB per file, and at most 5 MiB of total source.

Source history is a sequence of immutable revisions: every save, upload, or template application creates a new revision with a checksum of the canonical source tree, and existing revisions are never edited in place. Each revision records how it was created:

Source kindCreated by
ProvisionedMoltaro creation of the empty required source skeleton
ArchiveUploadAn uploaded ZIP source archive
ManualEditManual edits through UI Studio or the API
TemplateApplyApplying a source template or the Page Wizard

File changes carry the expected content hash of the file they replace, so concurrent edits fail with a conflict instead of overwriting each other. Disposable revisions can be deleted through DELETE /api/workspace/admin/ui-project/revisions/{revisionId}.

UI Studio (Constructor area, group Automation & logic, route /business-logic/ui-development, requires the Manage source permission) edits the stored tree directly. Create page opens the Page Wizard as the primary authoring action; generic blank-file creation is not offered. Advanced source templates remain available from the overflow menu. Save creates a manual-edit revision via POST /api/workspace/admin/ui-project/source-revisions/manual-edit, while save and check and save and build live in the adjacent Save menu and additionally queue through POST /api/workspace/admin/ui-project/builds. Queuing requires the RevisionId of an immutable revision — Kind 0 is a Check (validate only, no artifact) and Kind 1 is a Build that produces an inactive immutable artifact. The persistent Draft — publish required status opens UI Project and remains until the working revision’s artifact is active. Activation is a separate explicit step described in Build, publish, and upgrade.

UI Project (route /business-logic/ui-project) owns the ZIP round trip. GET /api/workspace/admin/ui-project/download returns a deterministic ZIP of the stored source — the same revision always downloads byte-identical source — plus a pinned, generated IDE scaffold for local Vue-aware editing. POST /api/workspace/admin/ui-project/upload imports only the allowed source files as one immutable ArchiveUpload revision and never queues a build automatically. Because source is delivered to browsers at runtime, never place confidential external API secrets in it — see Calling external APIs.

GET /api/workspace/admin/ui-project/source-templates lists ten deterministic templates, previewed and applied through .../source-templates/preview and .../source-templates/apply:

  • workspace-ui-page — a minimal page component.
  • workspace-ui-page-logic — a use<Name>Page composable.
  • workspace-ui-component — a shared component in src/components.
  • workspace-ui-pinia-store — a namespaced Pinia store.
  • workspace-ui-moltaro-api-client — a typed Moltaro API client module.
  • workspace-ui-command-client — a typed, abortable client for one published synchronous C# command.
  • workspace-ui-function-job-client — typed enqueue and bounded-wait helpers for one published C# background function.
  • workspace-ui-external-api-client — an external HTTP client module.
  • workspace-ui-locale-section — a page locale section in all five files.
  • workspace-ui-page-starter — a complete page: component, page logic, store, both API clients, locale sections, and a manifest entry with the parameterized route entities/:entityId/instances/:instanceId (both named parameters are required). This advanced template is applied only when an author explicitly selects it; workspace provisioning never applies it.

Template keys are kebab-case (up to 64 characters) and drive generated file names such as src/pages/<Name>Page.vue and src/stores/<key>Store.ts.

The Page Wizard on the UI Studio surface is a guided front end over the page-generator endpoints (.../page-generators/catalog, .../preview, .../apply). It supports four kinds: Blank (empty page scaffold), Entity list (a grid page over a selected entity), Entity details (a single-record page), and External API example with the Frankfurter exchange-rates preset that calls the public Frankfurter currency API. Every generation is previewed as an exact file diff, applies as one TemplateApply revision, and can optionally place the page as a root leaf in a user menu for eligible administrators. After generation, build the draft from the Save menu, open UI Project from the Draft status, and activate the resulting artifact.