7 Commits
Author SHA1 Message Date
Qiusheng Wu 133aa899d6 feat: startup layers and shareable interface files (#3039)
* feat: startup layers and shareable interface files

Startup settings could only preload layer styles. A layers file
(Project > Export > Export Layers) now seeds every new project with
URL- or path-referenced layers, and Settings > Interface can export and
import the language, layout, theme and profile from a file or URL.

* Address Claude review feedback

- Fetch features only for the layers the Startup setting seeded, not every
  URL GeoJSON layer in whatever project startup opened.
- Clear undo history after the startup fetch lands in a clean workspace, so
  undo cannot strip the fetched features back out.
- Cap interface files at 256 KB (and a declared oversize Content-Length)
  before parsing.
- Make normalizeLayersFileContent catch parser errors and apply the 1 MB cap,
  so a corrupt or oversized settings copy cannot break settings loading.

* Address review feedback

- Seed the Startup setting's layers when the restore gate times out too, and
  move the restore's ownership check onto the seeded workspace so a slow
  restore can still land (CodeRabbit).
- Merge a partial interface file field by field onto the current settings,
  so a file setting only hiddenPlugins keeps the rest of the profile
  (CodeRabbit).
- Let newProject take seed layers and use it for File > New and startup
  seeding, so its resets (attribute filter, selection) still apply (Claude).
- Apply the startup fetch with withoutHistory (zundo pause) instead of
  clearing all undo history afterwards (Claude).
- Read a URL interface file as a capped stream regardless of Content-Length,
  and re-check the stored profile lock before applying an import (Claude).
- Build the Export submenu from a list, keeping ProjectMenu.tsx under the
  untested-module ratchet's 500-line threshold (CI).
2026-10-07 22:43:22 -04:00
Qiusheng Wu 38ed979d3b docs: review the docs against the code and fix drift (#3008)
* docs: review the docs against the code and fix drift

Check every docs page against the current code and correct what no
longer matches: menu paths and labels, plugin placement (most plugins
now open in a docked panel), Cesium ion tokens being optional, desktop
OAuth sign-in for sharing, browser project save/open, project schema
fields, server and collaboration endpoints, Python and MCP APIs, and
stale paths in the source-code deep dives.

Fix the Spatial SQL tutorial's ST_Area_Spheroid query, which needs
ST_FlipCoordinates for lat/lon axis order. Document the Settings
sections that were listed but never described (Language, Appearance,
Interface, Geocoding, AI Providers), and add the USGS NLDI and ArcGIS
feature editing pages to the nav.

* docs: keep the NLDI and ArcGIS editing pages out of the nav

* Address CodeRabbit review feedback

- collaboration.md: give both snapshot-cap defaults, 10 MB for the
  relay and 1 MB in the Docker Compose setup.
- features.md, cesium-renderer.md: without an Ion token the globe still
  gets terrain from keyless Terrarium elevation (or a configured COG),
  not a bare ellipsoid (cesium-engine.ts enableWorldTerrain).

* Address CodeRabbit review feedback

- project-format.md: type blankBackgroundColor and selectedLayerId as
  string | null, matching ProjectFile in packages/core/src/types.ts.
2026-10-06 19:03:19 -04:00
Daniel HofbauerandQiusheng Wu fff0083a04 docs: document deployment policy cutover (#2900)
* docs: document deployment policy cutover

* docs: address deployment policy review feedback

* docs: clarify legacy profile lock field

* docs: drop dated release note and part numbers from availability

* docs: restore dropped headings and list spacing

* docs: separate Docker and desktop availability remedies

---------

Co-authored-by: Qiusheng Wu <giswqs@gmail.com>
2026-10-04 10:25:41 -04:00
3443f6f735 feat: enforce deployment capabilities for kiosk/classroom modes (#1673) (#2143)
* feat: enforce deployment capabilities in UI and embed API

* style: auto-format (ruff + oxfmt) [pre-commit.ci]

* Address CodeRabbit review feedback

- Add the missing deployment capability state to `@geolibre/core`. The PR read
  `deploymentCapabilities` / `setDeploymentCapabilities` off the store but
  neither existed, so every build failed with TS2339. New
  `deployment-capabilities.ts` defines the vocabulary, the full default grant,
  and a fail-closed parser for the configured list; `store.ts` holds the set
  (outside undo history and outside the project file) with the full set as its
  default, so an unconfigured build is unchanged.
- Filter the command registry by capability before it reaches the command
  palette, the keyboard cheat sheet, and the global shortcut layer. Those three
  call each command's `run()` without going through a menu, so the menu-only
  gates left denied project/data/processing/plugin/settings actions both
  advertised and callable.
- Move the per-surface capability tables into `lib/deployment-gates.ts` and
  point ProjectMenu at it, so the menu and the palette cannot disagree. This
  also closes the gaps the hand-written checks left: Open/Open Recent/Import/
  History/Save as Template/Collaborate/StoryMap now need `project:edit`, and
  Share/Print/Print Layout/Offline Region need `export:data`.
- Add `VITE_GEOLIBRE_CAPABILITIES` to `BUILD_ENV_KEYS`. Vite deletes any
  `VITE_` var not on that allowlist, so the value the app reads could never
  have reached the bundle.
- Clean up the bootstrap in `main.tsx`: hoist the mid-file import, drop the
  `as any[]` cast, and parse through the shared parser so unknown capability
  names are dropped rather than granted.
- Cover both modules with tests.

Claude-Session: https://claude.ai/code/session_01SyK1hvQT7sbzaYCKZdKYM5

* Address review feedback: close two client-gate bypasses

- Gate drag-and-drop on `data:add` (DesktopShell). Both drop paths — the
  webview handlers and the Tauri native listener — only checked the viewer
  preset, so a deployment that hid the Add Data menu still loaded a file
  dragged onto the map. The local flag is renamed `dropDisabled`, since it is
  no longer only about the viewer preset.
- Put `plugin.*` (activate/deactivate a plugin) and `settings.manage-plugins`
  (the marketplace, despite its `settings.` id) under `plugins:install` rather
  than leaving them unprivileged / under `settings:manage`. The toolbar already
  hides the whole Plugins menu on `plugins:install`, so the command palette was
  the way around it.

Claude-Session: https://claude.ai/code/session_01SyK1hvQT7sbzaYCKZdKYM5

* Address review feedback: Share classification and cancelled drops

- Classify `project.share` as `export:data` in the command table. The generic
  `project.` fallthrough gave it `project:edit`, while the Project-menu table
  already called it `export:data` — so a deployment granting `project:edit` and
  denying `export:data` hid Share from the menu and left it live in the command
  palette. Added a test that walks the id pairs and asserts the two tables
  agree, since they key off different ids for the same actions.
- Cancel a restricted file drop instead of ignoring it. `handleDragOver` and
  `handleDrop` returned before `preventDefault()` when drops were disabled, so
  the browser fell back to its own default and navigated the tab to the dropped
  file — which takes a viewer or a kiosk out of the app entirely. Both now
  cancel first and check the capability after, with `dropEffect = "none"` so
  the cursor says the drop is refused.

Claude-Session: https://claude.ai/code/session_01SyK1hvQT7sbzaYCKZdKYM5

* Address Claude review feedback: gate the Edit menu

The Edit menu was the surface the capability gate missed. Its items are not in
the `commands` registry, so `filterCommandsByCapabilities` never saw them, and
the menu itself was only behind the interface profile.

- Undo, Redo, and Export Selection now require `project:edit`, via the same
  dual gate ProjectMenu uses. The selection tools (select by expression /
  location, invert, clear, zoom to selection) stay unprivileged: they act on
  `selectedFeatureIds`, which is ephemeral store state that never reaches the
  project file, and a kiosk still wants them.
- `useUndoRedoShortcuts` checks the same capability. It installs its own
  `keydown` listener rather than registering a command, so hiding the menu
  items alone would have left Ctrl/Cmd+Z authoring the project.

Export Selection names an export but adds a layer built from features already
loaded — no file, URL, or service — so by the vocabulary's own definitions it
is project authoring rather than `data:add` or `export:data`.

Claude-Session: https://claude.ai/code/session_01SyK1hvQT7sbzaYCKZdKYM5

* docs: document deployment capabilities

Adds `docs/deployment-capabilities.md` (linked from the Reference nav) covering
the six capabilities, what each one gates, how to configure it, and the parsing
rules. Cross-referenced from Self-Hosting (hardening table row), Getting
Started (Docker build args), Embedding (the origin allowlist decides *who* may
send commands, capabilities decide *which* commands exist), Features, and UI
Profiles — the page most likely to be confused with this one, so it now says
plainly that profiles declutter and can be undone while capabilities pin what
the deployment permits.

The page leads with the limitation rather than burying it: this is a
client-side gate, `/sidecar` and `/ai` answer the same requests regardless, and
it complements the server-side protections instead of replacing them. The
build-time-only constraint and the missing `-e` path are stated, pointing at
 #1673.

Two changes writing the docs forced:

- `Dockerfile` did not declare `ARG VITE_GEOLIBRE_CAPABILITIES`, so the
  `--build-arg` invocation the docs describe would have been silently dropped.
  Declared it alongside the other build-time-only vars.
- There was no way to spell "grant nothing". A blank value reads as unset (an
  empty string is what `-e VAR=` produces, and unset has to keep meaning
  "full"), so the most locked-down configuration was the one you could not
  write — it only worked by accident, via any unrecognized token parsing to the
  empty set. `none` is now a reserved token, with a test.

Verified against a real build: `VITE_GEOLIBRE_CAPABILITIES="data:add,export:data"
npm run build`, served with `vite preview` and driven in Chromium — Processing,
Plugins, and Settings gone; Add Data, Controls, and Help intact; the Project
menu down to Share/Export HTML/Print Layout/Offline Basemap; the Edit menu down
to the selection tools; the command palette showing the same filtered set.

Claude-Session: https://claude.ai/code/session_01SyK1hvQT7sbzaYCKZdKYM5

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: giswqs <giswqs@gmail.com>
2026-08-28 22:38:07 -04:00
Qiusheng Wu 8c35a987bf feat(ui): default to Advanced interface and skip the welcome dialog (#1229)
* feat(ui): default to Advanced interface and skip the welcome dialog

Ship the Advanced interface out of the box and suppress the first-launch
onboarding wizard by defaulting `uiProfile.onboarded` to true. The existing
`enabled: false` default already reveals every menu/data source/plugin, which
`activeInterfaceProfile` reports as "advanced", so no visibility change is
needed. Users can still switch to a curated preset from the Settings dialog.

Existing stored profiles are untouched; this only affects fresh installs.

* docs: point Launch GeoLibre Web badge to the plain web app

Drop the 3d-tiles demo deep link from the badge in the README and Getting
Started page so it opens a blank workspace, matching the other Launch buttons
in the docs.

* Address Claude review feedback

- docs(ui-profiles): update the Onboarding section to reflect that fresh
  installs now default to the Advanced interface with no first-launch welcome
  dialog, keeping the reference doc in sync with the new `onboarded: true`
  default.
2026-07-13 22:52:23 -04:00
Qiusheng Wu 08a4fac842 fix(interface): replace Simplify toggle with a 4-state profile selector (#559)
* fix(interface): replace Simplify toggle with a 4-state profile selector (#540)

The Settings > Interface panel exposed a standalone 'Simplify the
interface' checkbox alongside the Beginner/Intermediate/Advanced
presets. The presets only applied when that checkbox was also ticked
(they were disabled otherwise), so selecting a profile had no effect on
its own and the two settings could contradict each other.

Replace the checkbox plus preset radios with a single four-state
selector: Beginner, Intermediate, Advanced, and Custom. Picking a level
enforces its layout immediately. Advanced reveals everything (the full
interface). Custom is a derived status that activates automatically the
moment the user hand-edits any item, and is never directly selectable.

Individual item toggles now also turn filtering on, so an edit made from
the legacy 'show everything' state takes effect. The underlying tier
machinery, persisted schema, and admin-profile format are unchanged.

Fixes #540

* Address review feedback

- SettingsDialog: always disable the "Custom" dialog preset button (not
  just when inactive), so it never becomes a focusable button that
  silently ignores clicks. This matches the dropdown, where "custom" is
  always disabled; the button still highlights when it is the active
  state. (Copilot/Claude + CodeRabbit)
- ui-profile.test: add an edge case asserting that an enabled, null-level
  profile with all hidden lists empty still reports "custom", documenting
  that a hand-edited "show everything" state is intentionally not
  "advanced". (Claude)

* Address Claude review feedback

- ui-profile / SettingsDialog: drop the "(issue #540)" references from
  three code comments per the project convention to keep task references
  out of source.
- SettingsDialog dropdown: note that EXPERIENCE_LEVELS must stay in sync
  with the selectable entries of INTERFACE_PROFILES, since the guard uses
  the former while the radio items render from the latter.
- SettingsDialog dialog: add aria-current to the active preset button so
  assistive tech announces the current selection even when the inert
  "Custom" button is disabled.
2026-06-19 14:16:35 -04:00
Qiusheng Wu be75f869ec feat(settings): customizable UI profiles / data source filtering (#500) (#509)
* feat(settings): customizable UI profiles / data source filtering (#500)

Add an opt-in UI profile system so beginners and organizational deployments
can declutter the interface by hiding data sources, web services, and plugins.
Hiding is non-destructive and fully reversible.

- New `uiProfile` in the localStorage desktop settings (defaults to everything
  visible, so existing users see no change).
- `lib/ui-profile.ts`: a tier-based catalog (basic/intermediate/advanced) for
  every Add Data item and plugin, with experience-level presets.
- Add Data and Plugins menus filter hidden items (empty sections collapse).
- Settings → Interface: enable switch, Beginner/Intermediate/Advanced presets,
  and per-item checklists.
- First-launch onboarding wizard that picks a profile when none exists.
- Admin config file (`admin-profile.json`) that pre-configures and can lock the
  profile for a deployment; read on desktop via a new `read_admin_profile`
  Tauri command, or served from the web root.
- docs/ui-profiles.md, i18n strings, and unit tests for the profile helpers and
  settings normalization.

* Address Claude and CodeRabbit review feedback

- useUiProfileBootstrap: reconcile a level preset's hidden plugin list as
  external/bundled plugins load after startup (add-only), so beginner/intermediate
  profiles keep hiding late-loading advanced plugins — fixes the external-plugin
  gap in both the admin-file and onboarding paths.
- useUiProfileBootstrap: make the one-time bootstrap effect's deps honest ([] +
  documented eslint-disable) instead of the misleading [plugins].
- useUiProfileBootstrap: release a previously-applied admin lock when the admin
  file is absent, so a machine is not stuck locked after the file is removed;
  documented in docs/ui-profiles.md.
- useUiProfileBootstrap: wrap the admin check in try/finally so markChecked()
  always runs and onboarding can never be permanently blocked.
- Dedup the experience-level allowlist: export EXPERIENCE_LEVELS from
  useDesktopSettings and reuse it in admin-profile, OnboardingDialog, and
  SettingsDialog instead of four separate copies.

* feat(settings): add Interface submenu to the Settings dropdown

Mirror the Layout submenu: a quick "Interface" submenu with a live "Simplify the
interface" toggle, the Beginner/Intermediate/Advanced presets, and an "Interface
Settings" item that opens the full section. Presets are disabled until the
profile is enabled, and the whole submenu is disabled when admin-locked.

* feat(settings): make all toolbar menus and items customizable

Extend the UI profile (issue #500) beyond Data Sources and Plugins so every
toolbar menu and its items can be hidden:

- Add hiddenMenus + hiddenMenuItems to the profile (model, normalizer, defaults).
- ui-profile.ts: TOP_LEVEL_MENUS and MENU_ITEM_CATALOG with complexity tiers;
  isMenuVisible/isMenuItemVisible; presetHiddenSets now also returns
  hiddenMenus/hiddenMenuItems so the Beginner/Intermediate/Advanced presets
  drive them.
- Gate items in Project, Edit, Processing, Controls, and Help menus and the
  Settings dropdown; gate whole top-level menus in TopToolbar.
- Settings -> Interface: a Menus toggle list plus per-menu item checklists.
- The Settings menu and its Language/Layout/Interface entries are never
  hideable, so the profile UI can't be locked away.
- admin-profile supports hiddenMenus/hiddenMenuItems; docs + unit tests updated.

* fix(settings): keep default-on controls and Assistant API keys at Beginner level

Tier-consistency fixes so nothing on-screen by default is unreachable for
beginners (issue #500):

- Globe and Scale map controls are shown by default, so move them to the basic
  tier (they were intermediate, leaving beginners no way to toggle them off).
- The Plugins menu hosts the default-on Layer Control and Basemap plugins, so
  make the menu basic; advanced plugins inside stay hidden by their own tier.
- The AI Assistant (basic) needs an API key from Environment Variables, so make
  that Settings item basic too.

Update the unit test: the beginner preset now hides no whole menus.

* feat(settings): drop DuckDB, Assistant, Legend, Env Variables from Beginner

Move these out of the basic tier so the Beginner preset hides them
(they remain available at Intermediate and Advanced):
- DuckDB Layer (data source)
- AI Assistant (Processing)
- Legend (Controls)
- Environment Variables (Settings)

With the Assistant no longer basic, its Environment Variables setting returns to
the intermediate tier alongside it. Unit test updated.

* feat(settings): Beginner tier tweaks (attribution, Processing menu)

- Attribution control is shown by default, so move it to the basic tier (it
  joins Globe and Scale as default-on controls that beginners can toggle).
- Hide the whole Processing menu for beginners (intermediate tier); its tools
  target intermediate+ users. Unit test updated.

* Address Claude and CodeRabbit review feedback

- resetUiProfile now also clears hiddenMenus/hiddenMenuItems (was leaving hidden
  menus/items in place on Reset). [flagged 3x]
- Gate the inter-group separators in Project/Processing/Controls/Help menus so a
  fully hidden group no longer leaves an orphaned/double separator. [flagged 4x]
- PluginsMenu: remove the unreachable `webServicePlugins.length === 0` guard.
- Plugin scope: add shared MENU_MANAGED_PLUGIN_IDS / toggleablePluginIds and use
  them in OnboardingDialog, the bootstrap, and the reconcile effect so onboarding
  no longer writes the four menu-managed plugin ids into hiddenPlugins; TopToolbar
  reuses the same set.
- admin-profile: surface read_admin_profile errors in dev, and don't let a
  malformed desktop file silently fall through to the bundled web file.
- saveSettings: when a level preset is active, recompute its hidden lists from the
  current registry at save time so late-loaded plugins aren't dropped.
- ui-profile: tighten menuId to a MenuOwnerId union; document the geoparquet/vector
  shared handler; memoize dismissOnboarding; drop redundant mr-2 on the
  Layout/Interface submenu-trigger icons (DropdownMenuSubTrigger already has gap-2).

* Address review feedback (round 2)

- Guard the experience-level radio onValueChange so a stray value can't be cast
  to ExperienceLevel and reach presetHiddenSets.
- Warn in dev when the desktop admin-profile.json exists but is not valid JSON
  (scoped to the desktop file so the web SPA fallback doesn't log noise).
2026-06-18 20:53:40 -04:00