# ExoPlaSim web UI ## Objective and invariants Build a usable scientific workbench from the maintained PHP web-app-starter. Remove demo content. Manage parameters, background runs, logs, and real spatial output. Keep the Fortran simulator and checked build. PHP owns HTTP handling, queue management, and background-job control. Python programs are scientific executables called by PHP, not server implementations. Do not run simulation or result extraction in HTTP requests. Support relative filesystem paths. All application operations use ordinary query parameters through the PHP front controller. PHP reads `$_GET` and `$_POST`; do not parse URL locators or paths. Do not add custom servers, CGI endpoints, or a separate routing layer. Preserve truthful saves, immutable job inputs, cancellation, and outputs. Use one workspace with no accounts, sign-in, or logout. Keep runtime data outside the served source tree. Use starter shell utilities and components. Do not use application path routing. Do not add external services or automation. ## Success criteria - [x] The browser opens the single-user workspace directly. It creates, edits, clones, exports, and queues a parameter set. - [x] An unprivileged worker runs the real simulator and survives request termination. - [x] The browser shows queue state, progress, bounded logs, failures, cancellation, and retry. - [x] Real output supports field, time, and level selection, map/globe rendering, charts, and downloads. - [x] No accounts, sessions, CSRF checks, network gates, TLS requirements, or application-specific server configuration remain. - [x] Setup, service files, tests, and current documentation ship in the project repository. - [x] The user edits height, land mask, land roughness, and broadband land albedo; saved versions feed real runs through immutable captures. ## Current state Status: complete. Current source, runtime, browser checks, and repository delivery have been verified. Source: project `web/`, `web/server/`, `config/web.json`, and `scripts/`. Runtime uses the host's ordinary PHP setup. The private checkout is outside the webroot. `public_root` contains only the PHP entry and browser assets. No application-specific nginx configuration, redirects, aliases, custom servers, or CGI endpoints are permitted. The starter import is the clean maintained local commit `1a246ce4550a0b63dedaefbc62ef7946c4000b2d`. Keep the maintained componentized shell and record provenance. The imported `.git`, logs, agent state, and code-search tools are excluded. ## Goal tree - [x] G1: A clean starter-derived application shell works. - [x] G1.1: Import the maintained starter and record its source. - [x] G1.2: Remove demos, unused themes/assets, and unused broker tooling. - [x] G1.3: Preserve starter shell utilities and component dispatch. Do not use sessions or security gates in this private dev app. - [x] G2: Scientific parameter sets are reproducible. - [x] G2.1: Generate a parameter catalog from the installed Model signature and scientific docstrings. - [x] G2.2: Save, clone, import, export, and validate parameter sets. - [x] G2.3: Snapshot validated inputs into immutable jobs. - [x] G3: Background jobs have reliable lifecycle management. - [x] G3.1: PHP persists the queue in SQLite and claims jobs atomically. - [x] G3.2: An unprivileged PHP worker calls the scientific executables with finite limits. - [x] G3.3: Publish phase/progress/logs and support cancel, retry, and interrupted-run recovery. - [x] G4: Model output is useful in the browser. - [x] G4.1: Extract NPZ output into indexed binary fields and bounded statistics. - [x] G4.2: Render real flat maps and interactive globes with time/level/field controls. - [x] G4.3: Provide global-mean charts, data inspection, and downloads. - [x] G6: The user can generate a random exoplanet. - [x] G6.1: Use a saved seed so generation is repeatable. - [x] G6.2: Generate a spherical elevation map and a matching land/ocean mask. - [x] G6.3: Provide controls for land fraction, terrain relief, and geographic scale. - [x] G6.4: Generate compatible planet, orbit, star, and atmosphere parameters. Keep them editable before a run. - [x] G6.5: Preview the generated surface on a map and globe. Export its recipe and surface files. - [x] G6.6: Convert the surface to ExoPlaSim inputs at the selected resolution. Test coordinate order and elevation-to-geopotential conversion. - [x] G7: Editable surface maps feed real simulation inputs. - [x] G7.1: Store immutable height, land-mask, roughness, and broadband albedo versions through PHP with real Gaussian grids. - [x] G7.2: Provide geographic brushes, range limits, smoothing, undo, redo, keyboard access, and touch controls. - [x] G7.3: Start from generated terrain or saved versions. Keep output fields separate from input maps. - [x] G7.4: Snapshot map versions into each queued job. Verify retry and later edits cannot change those inputs. - [x] G7.5: Run edited maps through the native model and verify SRA coordinates, gravity conversion, and actual model inputs. - [x] G5: Deployment and acceptance are repeatable. - [x] G5.1: Publish public-only assets and an ordinary PHP entry. Keep private source outside the webroot. Install the systemd worker without application-specific nginx configuration. - [x] G5.2: Test queue concurrency, scientific validation, frame bounds, failures, and ordinary cookie-free saves. - [x] G5.3: Complete a real browser-to-worker-to-visualization run. - [x] G5.4: Test relative assets, query operations, APIs, and downloads through the existing PHP front controller. Do not add test servers or CGI mount schemes. - [x] G5.5: Test relative storage paths and scientific commands from another working directory. - [x] G5.6: Verify that PHP owns every server lifecycle operation. Remove Python server implementations. ## Acceptance evidence | Requirement | Evidence | | --- | --- | | Direct workspace, parameter CRUD, validation, truthful pending saves | `tests/web-http.php`, `tests/web-backend.php`, `tests/web-gui-workbench.js`, `tests/web-gui-numbers.py`, and dated GUI acceptance | | Immutable specs and native map captures, retry after changes and deletion | Backend and worker tests, retained native inputs, four-map runs in `docs/work/2026-10-10/web-acceptance.md` | | Independent worker, atomic claims, finite limits, cancellation and recovery | `tests/web-worker.php`, systemd state, real native jobs, and installer lock tests | | Native fields, units, times, levels, weighted means, bounds and downloads | Python results tests, `tests/web-api.php`, native GUI frames and download checks | | Reproducible planet generation and compatible native maps | Python planet tests, generated and edited climate runs in dated acceptance | | Brushes, smoothing, undo/redo, keyboard, touch, saved versions and failure recovery | `tests/web-map-brush.js`, `tests/web-gui-editor.js`, and VM 101 mobile checks | | RGB temperature/precipitation/elevation view | `tests/web-composite.js`, `tests/web-gui-composite.js`, and `docs/work/2026-10-10/rgb-output.md` | | Ordinary PHP query operations without security/session machinery | Source audit, cookie-free live HTTP harness, and `tests/web-public.php` | | Relative paths and repeatable installation without data replacement | Worker/Python fixtures from another working directory, `tests/web-installer.py`, and `docs/work/2026-10-11/final-ui.md` | | Repository delivery | Private forge push and clean-checkout suites recorded in `docs/work/2026-10-11/final-ui.md` | ## Shared implementation contract `config/web.json` is the shared configuration source. Consumers resolve relative storage keys against `storage.root`. Resolve the Python path against the project root. `EXOPLASIM_CONFIG` can select another configuration file. Do not paste host paths or operational limits into another definition. A saved experiment contains `id`, `owner`, `name`, `spec`, `created_at`, and `updated_at`. IDs are 32 lowercase hexadecimal characters. A job snapshots an experiment's name and spec. Editing an experiment never edits an existing job. A spec has this shape: ```json {"model":{"resolution":"T21","layers":10,"ncpus":1},"parameters":{},"run":{"cycles":1,"steps":1440}} ``` Only the constructor fields shown are public. Precision remains FP64. `run.steps` may be null for a full model year, or a bounded integer for an explicit cycle. Do not call a custom-length cycle a year in UI copy. The worker controls model name, source, paths, and executables. Parameters are Model.configure overrides, not a second source of defaults. The main worker owns `scripts/web/parameters.py` with `catalog(config)` and `validate_spec(spec, config)`. Validation returns `{ok, spec, errors}`. The cached catalog contains `fields` as an array of `{name,label,type,default,nullable,description,group,editable,choices?}`, plus model/run defaults and limits. Types are `number`, `integer`, `boolean`, `string`, `object`, or `array`. Derive defaults from the signature. Reject paths, scripts, raw namelist injection, and unknown fields. File-backed inputs need managed uploads before they become editable. Control runsteps through the run envelope. All runnable specs require spatial output. The main worker also owns `web/lib/parametercatalog.class.php`, with `ParameterCatalog::schema()` and `ParameterCatalog::validate($spec)`, using the same cached schema. Save validation and queue validation must report field errors rather than silently discard input. ### PHP storage boundary The PHP backend owns `web/server/worker.php` and `web/config/lab.php`, which exposes `lab_config()` with resolved keys `database`, `jobs`, `runtime`, `runtime_lock`, `catalog`, `python`, and `project_root`, while retaining other raw config values. It owns `web/server/schema.sql` as the shared SQLite DDL, `web/lib/labdb.class.php`, `web/lib/experiment.class.php`, and `web/lib/job.class.php`. `LabDb::connection()` returns PDO. SQLite uses WAL, bounded busy waits, and indexes for ownership, state, and listing. Methods return ordinary records or structured results `{ok, data?, errors?, error?}`. No application exceptions or request aborts for expected validation/not-found cases. Experiment methods: `all($owner)`, `get($id,$owner)`, `save($owner,$data)`, `delete($id,$owner)`. Deletion affects the saved parameter set, not existing job snapshots. Job methods: `all($owner)`, `get($id,$owner)`, `enqueue($owner,$experiment)`, `cancel($id,$owner)`, `retry($id,$owner)`, `log($id,$owner,$offset=0)`, and `directory($job)`. Log reads are incremental and bounded. Listing is bounded. Retry creates a new job with the same immutable inputs. State names are `queued`, `running`, `completed`, `failed`, `cancelled`, and `interrupted`. A running cancellation sets `cancel_requested`; queued cancellation is immediate. Jobs expose `id`, `experiment_id`, `name`, `spec`, `status`, `phase`, `progress`, `completed_cycles`, `total_cycles`, `created_at`, `started_at`, `finished_at`, `updated_at`, `error`, and `cancel_requested`. Timestamps use UTC ISO 8601. The worker owns execution PIDs and state transitions. ### HTTP boundary The PHP frontend owns query API operations under `web/views/api/`. It does not use CSRF checks. Use the existing PHP front controller with query parameters, for example `?api=frame&id=…&time=…`. Do not parse route locators or request paths. Responses are `{ok:true,data:...}` or `{ok:false,error:...,errors?:...}`. GET operation names are `schema`, `experiments`, `jobs`, `log`, `results`, `series`, `frame`, and `download`. Detail requests use `id`. POST `?api=experiments` saves `{id?,name,spec}`. POST `?api=experiment_delete` deletes a saved set. POST `?api=jobs` queues `{experiment_id}` after successful save. POST `?api=cancel` and `?api=retry` take `{id}`. The UI can clone or import through normal experiment save. Downloads and exports use only owned IDs and approved artifact identifiers. The application is single-user. It has no accounts, sign-in, or logout. API mutations do not require sessions, CSRF tokens, or network checks. The workbench root carries a bootstrap object `window.EXOPLASIM` with `api` mapping operation names to relative query strings. `config.workspace` identifies the one workspace in storage. Do not add security gates or sessions to this private dev toy. The UI must show request errors and preserve edits. ### Results boundary The visualization implementation is `scripts/web/results.py` with `prepare_results(npz_path, destination, dataset_id)` and a CLI entry point. The worker calls it after each cycle. Each job uses `artifacts/datasets//`. A dataset stores `index.json` with `id`, `lat`, `lon`, `time`, `time_unit`, `levels`, and `fields`. Each field is `{name,label,unit,shape:[time,level,lat,lon],min,max}`. Coordinate variables are not fields. Metadata labels and units come from ExoPlaSim NPZ metadata, not an invented registry. Each field has `.f32` in little-endian C order and `.json` with `{mean,min,max}` arrays shaped `[time][level]`. Global means use area weights and ignore nonfinite cells. Do not include large per-field statistics in the dataset index. `artifacts/index.json` lists datasets as `{id,label}` and identifies `latest`. The PHP worker maintains this job index after the scientific child prepares each dataset. Publish indexes atomically after files are ready. Keep memory and storage bounded by config limits. Preserve NPZ, exported config, logs, and restart files for downloads. GET `?api=results&id=...&dataset=...` returns the selected dataset index plus `datasets` and `latest`. GET `?api=series` selects `id`, `dataset`, `field`, and `level`, returning `{time,time_unit,mean,min,max,unit}`. GET `?api=frame` selects those plus `time` and returns one binary float32 spatial frame. Bounds and file names come from the owned manifest. No Python process or full field-file read in a PHP request. The visualization delegate owns `web/js/visualization.js`. Expose `window.ExoVisualization` with constructor `(container)` and methods `setDataset(index, frameUrlFunction, seriesUrlFunction, options?)`, `destroy()`. `options.gravity` supplies the saved job's gravity for the RGB elevation channel. It creates its own field/time/level controls, map/globe, legend, inspector, and chart inside that container. URL callbacks take `(field,time,level)` and `(field,level)`. It must handle aborts, stale responses, resize, no WebGL, and missing values. Use local native browser APIs, no CDN or new package installation. The frontend delegate owns the workbench component, index view, workbench JS, and the ExoPlaSim theme. Render through `component()` / `component_call()`. Use one clear workspace with experiment selection, parameter editing, jobs, visualization, and logs. No demo data, marketing cards, planning explanations, or unnecessary subheadings. ## Next The accepted UI scope is complete. Earlier evidence is in `docs/work/2026-10-10/web-acceptance.md`. RGB evidence is in `docs/work/2026-10-10/rgb-output.md`. Final suites, screenshots, installation, and delivery are in `docs/work/2026-10-11/final-ui.md`. ## Remaining scope and risks Random exoplanet generation is part of the requested scope. Generated surfaces must reach the simulator, not remain decorative previews. Describe them as procedural scenarios, not reconstructed planets or geological predictions. Managed height, land-mask, roughness, and broadband land-albedo maps are part of the active scope. Restart-from-UI, parameter sweeps, SBDART pipelines, comparative climate analysis, and administrator storage cleanup may need later increments. Do not expose inactive controls or claim these work. Large models can exceed resource limits. Native settings and complete MPI acceptance remain outside this UI slice.