Meridian ships as native Apple-platform builds over one shared app source tree, plus a native Windows 11 port:
Meridian.xcodeproj builds the universal iOS app (iPad + iPhone companion) from project.yml.MeridianMac.xcodeproj builds the native Mac app from project-mac.yml.project-shared.yml owns shared XcodeGen target behavior such as the Rust bridge build step and common app build settings.windows/ builds the Windows 11 app (Tauri v2 + TypeScript + MapLibre GL JS), linking the same gis_core crate and reading the same .gispad packages.Windows is a full desktop peer of the Mac app (not a scoped companion like iPhone). It reuses the shared Rust engine and JSON FFI verbatim, so parity work is almost entirely TypeScript UI calling already-exposed core functions. The Windows column below tracks that parity; cells flip to “Yes” as each parity phase lands (see windows/scripts/smoke3.mjs). Location/GPS is the one intentional Windows gap (same desktop reason as Mac).
The iPhone is a scoped companion, not a peer platform. It is a universal-iOS build gated by PlatformCapabilities.isPhone with a deliberately limited feature set (view, layer visibility, attribute edit, notes, iCloud save with conflict detection). Its design docs are docs/iphone/design_principles_iphone.md and docs/iphone/ui_ux_spec_iphone.md. The iPhone does not need to reach iPad/Mac feature parity; its intentional gaps are recorded below.
Every new shared feature is build-blocking for both platforms. A feature is not done until:
Scripts/check-platform-parity.sh passes.Use Scripts/build-all-platforms.sh for the full gate.
GISPad/App, GISPad/Core, GISPad/DesignSystem, GISPad/Features, and GISPad/Map are shared by default.GISPad/Platform is the preferred home for platform capability flags and small adapters.GISPad/Core/PlatformShims.swift should stay limited to compatibility aliases. Do not grow product behavior there.#if os(...) blocks are acceptable at app edges such as MapLibre representables, file presentation, location capability, menu/window behavior, and share sheets.| Area | Shared Core | iPad UI | Mac UI | Windows UI | iPhone UI | Status / Notes |
|---|---|---|---|---|---|---|
Projects and .gispad packages |
Yes | Yes | Yes | Yes | View only | iPhone opens iCloud Drive packages via a coordinated working copy; no blank-project creation or duplication. Mac uses native file access behavior where needed. Windows stores packages in Documents\Meridian\Projects. |
| Layers and layer metadata | Yes | Yes | Yes | Yes | Visibility toggle only | iPhone has no reorder/opacity/style/labels/filter/lock/rename/delete/metadata. Windows has visibility/opacity/colour/delete plus a layer details dialog (overview, definition filter, fields, validation, metadata). The Windows definition-filter editor adds distinct-value chips (per field, with counts, from symbology_domain) and a live match-count preview (“N of M features match”, via definition_filter_match_count); the same editor is reachable from the attribute table’s Filter button. Windows reordering is drag-only (no move up/down buttons): drag a row to restack, drop onto a group header or member to join that group, drop onto an ungrouped row or the list background to leave the group. |
| Map canvas rendering | Mostly | Yes | Yes | Yes | Yes | MapLibre package differs by platform; behavior should remain equivalent. Windows uses MapLibre GL JS in WebView2. |
| Camera (center/zoom/bearing/pitch) | Yes | Yes | Yes | Yes | Yes | MapCameraState.pitch added (default 0); iPad/Mac defaults unchanged; layout snapshots stay top-down. |
| Bookmarks | Yes | Yes | Yes | Yes | Yes | iPhone has a Bookmarks sheet (Map menu): save the current view, fly-to (direct camera incl. pitch), inline rename, delete with confirmation. Reorder is intentionally omitted on the phone. Bookmark create/rename/delete mutate the package, so they mark the document needs-publish; fly-to is camera-only and does not. Windows bookmarks pane: capture/fly-to/reorder saved views, plus inline rename, update a saved view to the current camera/extent, and delete with confirmation. |
| Basemaps and glyph loading | Mostly | Yes | Yes | Yes | Yes | iPhone has a Basemap sheet (Map menu) to switch between online presets (Standard + Satellite); satellite presets accept a provider key inline, stored in the local Keychain and never written to iCloud packages. Switching marks the document needs-publish. The custom-URL form and PMTiles import stay on iPad/Mac (authoring/import is out of iPhone scope). Mac uses bundled glyph/resource handling. Windows stores provider keys in Windows Credential Manager through the Tauri credential bridge, migrates legacy machine-local localStorage keys on first use, and never writes secrets into .gispad packages. |
| Import/open data | Yes | Yes | Yes | Yes | No | iPhone does not expose GeoJSON import, Open Data Library, or any provider catalog in v1. Windows has GeoJSON/CSV/OSM/Geofabrik/OGC/WFS import plus the Open Data Library: Natural Earth (multi-scale 1:110m/50m/10m, ~19 datasets), US Census TIGERweb (states/counties/congressional districts/tribal areas), live OpenStreetMap — Overpass “Download Visible Area” (5 presets, area-guarded bbox query through the shared osm_overpass_convert), Geofabrik regional GeoPackage extracts (live index-v1-nogeom.json catalog + region picker → download → in-app single-file ZIP extract → geofabrik_gpkg_convert → theme import), GeoNames bulk gazetteer (public zipped-TSV city dumps → points, no key), OGC API — Features, GeoNames place search, and Data Commons Population. ArcGIS Feature Service import now accepts a per-host API token (sent as ?token= + Authorization: Bearer); OGC API — Features import supports a “limit to current map view” bbox and a CQL2 attribute filter. Data Commons keys are machine-local; Census GEOID fields are scanned as text and imported into a local table for explicit joins. OGC API Features and WFS now persist connected-source provenance with package-safe dataset IDs, endpoint-host display only, local endpoint profiles, HTTPS/no-secret URL policy, legacy metadata migration on project open, and Layer Details Refresh from Source / Replace-after-success. ArcGIS tokens, GeoNames usernames, Data Commons keys, and provider keys now use Windows Credential Manager via the Tauri credential bridge, with legacy localStorage secrets migrated and cleared after a successful OS write. Connected-source endpoint profiles remain machine-local metadata and are not written to .gispad. |
| GeoPackage (.gpkg) round-trip | Yes | Yes | Yes | Yes | No | Native multi-table import + multi-table export, implemented in pure Rust inside gis_core (no GDAL), so iPad/Mac/Windows share one code path. Export writes one feature table per layer (active layer, all visible layers, or all layers) in stacking order, de-duplicating colliding table names and recording each table’s CRS. WGS84/EPSG:4326 imports directly; supported projected tables are transformed to CRS84 on import and marked with sourceCrs/transformable in preflight. Windows now exposes the same Active/Visible/All export scope picker and target-CRS control for GeoPackage export. |
| KML / KMZ import (.kml, .kmz) | Yes | Yes | Yes | Yes | No | Native multi-layer import in pure Rust inside gis_core (no GDAL, no core network) — iPad/Mac/Windows share one code path on the same shared XML parser as WFS. Each top-level folder becomes a WGS84 layer; loose placemarks form a document layer; >1 layer offers the same “Group imported layers” toggle as GeoPackage (default on). Import preserves geometry, attributes, dominant KML style/labels, built-in marker-shape hints, and point altitude as kml_altitude; overlays and non-point Z are warned/deferred. NetworkLinks are listed and can be fetched explicitly once as HTTPS local snapshots; there is no automatic or live refresh. KMZ reads the local archive (prefers doc.kml). |
| Shapefile import/export (.shp, zipped) | Yes | Yes | Yes | Yes | No | Native, hand-rolled in pure Rust inside gis_core (no GDAL/shapefile/dbase crates) — .shp/.shx/.dbf/.prj readers+writers, bidirectional ring re-winding, dBASE cp1252/Latin-1/UTF-8 decoding, 10-char field-name truncation, lightweight .prj CRS detection (EPSG authority + name match; unknown→WGS84, untransformable EPSG→blocked), and mixed-geometry auto-split into one .shp set per family inside one zip. Import converts each .shp to temp GeoJSON through the shared importer; non-WGS84 sources with a supported CRS are transformed to CRS84 with provenance. Export writes a single active layer to .zip, supports selected-only, honors target_crs, writes .prj, and never exports editor IDs. Multi-layer Shapefile export remains intentionally unsupported. |
| File Geodatabase import (.gdb) | Yes | Yes | Yes | Yes | No | iPad/Mac: native read-only import in pure Rust inside gis_core (no GDAL) — a hand-rolled OpenFileGDB v10 reader (filegdb module: varint/table/geometry/catalog) validated byte-for-byte against GDAL output. Reads the system catalog to find feature classes, decodes .gdbtable/.gdbtablx rows and the compressed shape buffers (point/multipoint/polyline/polygon, Z/M and curve flags dropped to 2D with a warning), and imports each feature class as a layer via the shared GeoJSON path. WGS84 imports directly; supported projected CRS transform to CRS84 (incl. the full WGS84 and NAD83/NAD83(CSRS) UTM families); an unresolvable projected CRS is blocked with a warning (never silently assumed WGS84). Picking the .gdb folder opens the same in-pane per-feature-class selection UI as Shapefile/GeoPackage (checkboxes, per-layer rename, group toggle, working-CRS adoption, skipped-CRS section). Windows: separate path via the installed GDAL OpenFileGDB driver (cannot run on iPad). Write/edit is out of scope on all platforms. |
| Symbology and labels | Yes | Yes | Yes | Yes | No | iPhone is visibility-only. Windows has single/categorized/graduated symbology, marker shapes, five line-dash presets (solid/dashed/dotted/dash-dot/long-dash — string-identical raw values across platforms so styles round-trip), fill patterns, per-feature overrides, labels, and label filters. Graduated symbology has a named colour-ramp picker + reverse and four classification methods (equal-interval/quantile/natural-breaks/standard-deviation); point layers add a Heatmap mode and graduated point size by field. Data-driven opacity by field, saved style presets (machine-local capture/apply/delete), and per-feature override authoring (fill/outline/point colour + point size in the inspector) are supported; lines have direction arrows; labels expose a visible zoom-range and background plates. Polygon layers add a 3D extrusion mode (constant or field-driven height × scale with min/max clamps, base height, fill, opacity, base shading, and graduated extrusion colour by field — view by tilting to 3D). Label plates are rendered through MapLibre GL JS-compatible generated text-fit icons, so collision/zoom behavior still needs Windows runtime screenshot verification after style edits. All rendered via the viewport-GeoJSON path. |
| Drawing and editing | Yes | Touch/Pencil first | Pointer/trackpad first | Pointer first | No | No geometry drawing/editing, snapping, tool dock, or draw bar on iPhone. iPhone editing is attributes-only. Windows draws point/line/polygon/rectangle and edits geometry via the shared edit sessions: move/insert/delete vertices (click an edge to insert; right-click a vertex for delete/split), multi-part vertex editing (MultiLineString/MultiPolygon, holes), whole-feature move (drag), close polygon, split line, duplicate, a precision loupe (live coordinate/Δ-distance/bearing during a vertex drag), and configurable snapping (scope = visible vs active layer; endpoint/vertex/segment targets). |
| Pencil precision loupe | Yes | Pencil/touch vertex drag feedback | Pointer drag feedback | Pointer drag feedback | No | No geometry editing on iPhone, so no loupe. |
| Snapping and validation | Yes | Yes | Yes | Yes | Validation only | Rust/SQLite authoritative; iPhone runs attribute/publish validation but has no snapping. Windows has snapping (with a user-configurable snap tolerance setting that now drives the snap request) and a geometry validation report (per-layer + project, zoom-to-issue). |
| Project Health | Yes | Yes | Yes | Yes | No | iPad/Mac/Windows surface a Project Health view from the shared project_check_integrity + project_scan_privacy FFI: overall status, package info, a manifest/database/schema/spatial-index/privacy/export status grid, recoverable-issue overview, and recommended actions. On Windows it opens from the Layers panel header. |
| Measure (read-only) | Yes | Yes | Yes | Yes | Tap-to-measure | iPhone has a read-only tap-to-measure (Map menu): tap points for live geodesic distance, close 3+ points to a polygon for area, via the shared measureGeometry core (no edit session, no geometry creation). It is ephemeral and never marks the document dirty. No snapping or vertex precision on the phone. |
| Attribute editing | Yes | Yes | Yes | Yes | Yes | iPhone and Windows edit authoritative attributes via the shared feature form (validate_feature_form) committed through an UpdateProperties edit session. Windows surfaces an “Edit attributes” inspector form (change/add/remove values, validation gate). |
| Notes / markup | Yes | Yes | Yes | Yes | Text + callout only | iPhone reuses createMarkupLayer/addMarkupItem/updateMarkupItem; freehand/arrows/shapes deferred. Windows has line/area/text markup plus freehand ink strokes and arrow/callout/circle/rectangle authoring, written as the cross-platform gispad_markup envelope (schemaVersion/type/style/text) so markup round-trips to Mac/iPad. Windows still lacks the full selected-markup edit/delete flow and compact markup style editor. |
| Selection and multi-selection | Yes | Yes | Yes | Yes | Single only | iPhone supports single-feature selection only. Windows has single + box + lasso select with a selection bar: zoom, export, batch delete, clear, merge (LineString), copy → new layer, copy → existing layer (geometry-compatible), and bulk set attribute. A “Select across visible layers” toggle (in the Select-mode menu) spans box/lasso/tap across every visible GIS layer, aggregating the count/overlay and deleting/clearing across all of them (single-layer batch ops stay scoped to one layer). When a tap lands on several stacked features/layers, iPad/Mac/Windows default to the topmost and expose a “Layer” picker in the inspector to re-target any feature under the same point. |
| Window and pane layout | Yes | Floating/touch panels | Same positions, Mac scaled | Mica Glass floating chrome with dockable/resizable Layers + Inspector panes | Bottom sheets + compact chrome | iPhone replaces side panels/tool dock with sheets and a bottom action cluster; one primary sheet at a time. Windows now mirrors the Mac information architecture: pin/resize Layers (240–480px) and Inspector (296–520px) panes that inset the map; Layer Details and Field Calculator are right-side panes (not modals); Settings moved out of the workspace into the Project Gallery; the top-right cluster mirrors Mac (Find/Command/Basemap/Layers/Add Data/Notes/Labels/Table/Analysis/Guide + Share), bookmarks folded into Finder; tool dock has a Select-mode disclosure (Tap/Box/Lasso) and the nav cluster a 2D⇄3D tilt toggle. The Layers panel adds manual New Group, a layer-name filter, discrete Move Up/Down (a11y/no-drag reorder), and per-row status chips (unsaved-edit dot, non-WGS84 CRS, validation error/warning counts, markup badge). The workspace top chrome now includes a Guide button and uses priority horizontal overflow at narrow widths instead of clipping tool controls; the expanded markup bar is also scrollable inside the viewport. The Finder adds a Find / Go-to mode toggle: Find has a scope picker (visible / editable-visible / all layers) and live debounced search; Go-to parses a pasted coordinate (with hemisphere suffixes), converts from another CRS via crs_convert, flies to the point, and can drop it as a feature in the active layer. The Command Planner is deepened from 6 plan kinds / 3 dispatch paths to ~19 kinds (import, open-data, symbology, labels, table, join, chart, layout, basemap, navigation, CRS, project-health, …) plus a multi-step recipe, each routing to a prefilled workspace surface. Settings add an accent-tint preset, reachable default-basemap and default-project-CRS pickers, an OSS-licenses acknowledgements list, and a real build-sourced version. The in-app User Guide adds the Capabilities & Limits trust warning, the Coordinate Systems (CRS) cluster, workflow recipes, command-planner, 3D, project-sharing, and import-formats topics, with keyword-weighted search. Windows project-gallery cards now read/write the same .gispad/previews/gallery-thumbnail.jpg + metadata files as Mac/iPad, capturing a throttled MapLibre snapshot from the workspace and falling back to the procedural thumbnail when no stored preview exists. Download-on-demand sample projects remain deferred pending a hosted sample bundle. |
| Attribute table, fields, and field calculator | Yes | Yes | Yes | Yes | No | iPhone uses the bottom feature inspector + attribute form, not the virtualized table or calculator. iPad/Mac/Windows share canonical field types, import inference, table-header type badges, Layer Details > Fields management, and previewed field type conversion that creates a new typed field by default. Windows adds a standalone table viewer (paged/search/sort + CSV export) for analysis output tables. The Windows field calculator now honours the shared large-run confirmation gate (it no longer auto-confirms big writes), runs with a live progress bar + Cancel (field_calc_run_with_progress), and after a run offers Undo / Open table / Style field. The Windows attribute table adds a frozen FID column, a selected-only scope toggle, and a Filter button (the same value-chip + match-count filter editor as Layer Details). Field-type conversion on Windows is now a previewed dialog (type picker, before→after samples, convertible/invalid/null counts, replace-existing) rather than window.prompt/confirm. Windows Layer Details > Fields now edits form rules — required / read-only / hidden per field and a value-domain editor (allowed values + labels, enable + enforce, import-from-data) — which feed the attribute form’s validate_feature_form checks. |
| Spatial analysis | Yes | Yes | Yes | Yes | No | Not exposed on iPhone. Windows exposes the full tool catalog. Select by Location’s predicate carries the same per-option plain-English hints on every platform and the same “same layer as input and overlay selects every feature” note; iPad/Mac render the predicate as an Aero segmented control while Windows keeps the native <select> (each platform’s own idiom). Network Reachability now runs as a non-persisting preview on Windows too (analysis_preview): the reachable network and reached targets render as a temporary teal/amber overlay and are written only on Confirm; Discard drops them, and Save Targets writes the reached features into a new layer. Windows surfaces the reachability advanced params from the shared catalog (cost direction, target buffer, start-selected-only). |
| Analysis history | Yes | Yes | Yes | Yes | No | Not exposed on iPhone. Windows analysis pane opens a history viewer with per-run detail and zoom/table/set-active, plus Re-run from the stored recipe (with a blocked-reason when an input layer was deleted) via analysis_rerun_with_progress. |
| Charts | Yes | Yes | Yes | Yes | No | Transient chart builder on iPad/Mac/Windows supports bar, histogram, pie, box plot, line, scatter, statistical density/KDE, and matrix heatmap with X/Y fields, optional grouping, stacked/clustered bars, themes, selected-only scope, and PDF/PNG/SVG/JPG export. Selection-linked feature highlighting remains limited to safe layer-backed bucket charts. Windows can also chart a Summary Statistics output table (summaryStatisticsBar): pick a statistic and, when grouped, drill into one field’s groups. |
| Export and PDF layout | Yes | Yes | Yes | Yes | No | Not exposed on iPhone. iPad/Mac/Windows export GeoJSON, Shapefile .zip, GeoPackage (multi-layer feature tables), styled KML/KMZ (multi-layer folders, labels, styles, KMZ marker icons), and CSV+WKT/WKP. GeoPackage/KML/KMZ share Active/Visible/All scope controls; GeoPackage and CSV/WKT expose target-CRS export; CSV+WKT also offers an All properties / Geometry only column scope (geometry-only is CSV-only, matching the core). Windows now also shares the whole project as a sanitized .gispad package (project_share) through an Export Inspector (mirrors the Mac ExportInspectorPane / ExportPlan): included/excluded items, attribution obligations, a project_scan_privacy status, integrity blockers, and a canExport gate that disables the share until clean. The Windows export dialog also runs a validation preflight (checked/errors/warnings + CRS status) before writing layer files and blocks export when the report says it can’t run. Layout is a guided assistant on iPad/Mac/Windows with Page/Map/Contents/Review sections, presets, current/visible/layer/bookmark/custom extents, graticule, legend, scale bar, north arrow, dynamic text chips, review guardrails, and Bookmark Packet multi-page output; the Standard/High quality control renders the snapshot at 2×/3× (Windows PDF output remains raster, not vector). In full-bleed (“map fills page”) mode Windows now exposes per-element corner placement (title/legend/scale bar/north arrow) and card styling (none/frosted/solid background, drop shadow, opacity), honoured in both the live preview and the printed PDF. Layouts remain transient and are not saved into .gispad. |
| CRS import + working-CRS UX | Yes | Yes | Yes | Yes | No | Implements docs/crs_import_working_crs_plan.md on iPad/Mac/Windows. Storage is always CRS84; working CRS is a display/import-default/export-default preference only. GeoJSON import is preflight-first (gis_core_project_geojson_preflight — same FFI on all platforms): a pre-commit decision panel prefills a declared projected CRS (Case A) or requires an explicit choice when none is declared (Case B), and never guesses (declared geographic/in-range data imports directly). First projected import can adopt the source CRS as the project working CRS with explicit consent (offered only while working CRS is still the default), for GeoJSON, GeoPackage, and Shapefile. CSV/WKT suggests the working CRS (tap-to-apply), never pre-selects it (D2). Export Output-CRS pre-selects the working CRS for GeoPackage/Shapefile/CSV+WKT (non-sticky, re-resolved on format switch); GeoJSON/KML/KMZ show a WGS84-only note. Layer Details shows import provenance from crs_transform_runs. An in-app CRS guide (opening invariant + 9 scenarios) is reachable from every decision panel and the working-CRS chip. iPhone has no import/export flows, so it is out of scope. |
| iCloud document sync | Partial | Entry points | Entry points | N/A | Yes | iCloud Drive document packages (not CloudKit). iPad/Mac add a “Store in iCloud Drive” entry point; iPhone publishes with conflict detection. Windows is local-only (no iCloud). |
| Location workflows | Partial | Yes | Limited | No | Limited | Mac precise device location is not assumed for V1; Windows desktop has no field GPS (intentional exception below); iPhone location use is map-context only. |
Record exceptions here before merging a feature that is intentionally platform-specific.
| Feature | Platform | Reason | Follow-up |
|---|---|---|---|
| Precise live location | Mac | Desktop hardware often lacks field GPS; iPad remains the field-capture default. | Keep Mac location UI disabled or clearly limited unless a reliable provider is added. |
| Precise live location / GPS capture | Windows | Same reason as Mac: desktop hardware lacks reliable field GPS; iPad remains the field-capture platform. Not a parity gap. | Omit the location/capture chrome on Windows. Revisit only if a reliable Windows location provider is added. |
| Geometry drawing/editing, snapping, import, Open Data, analysis, history, field calculator, export, PDF layout, symbology/labels, layer reorder/opacity/lock, blank-project creation, duplication, multi-select | iPhone | The iPhone companion is a scoped field viewer/light editor, not a full GIS workspace. | Omit the chrome entirely (do not show disabled controls). Revisit per future iPhone phases; track in agent_tasks.md. |
| No automatic merge on iCloud conflict | iPhone | v1 conflict resolution is explicit user choice (reload / conflict copy / cancel). | Auto-merge is out of scope until a merge model is designed. |
| iCloud entry points dormant until signing team | iPad / Mac | The iCloud capability is provisioned in code (entitlements for the shared container iCloud.com.gispad.GISPad + NSUbiquitousContainers), and the entry points are gated on isICloudAvailable. What remains is account-side: the container registered in the Apple Developer portal so device/Release signing authorizes the entitlement. Until then forUbiquityContainerIdentifier returns nil on device, so the entry points stay hidden and the iPhone library falls back to Open from Files. |
Register the container; the coordinator code is covered by unit tests against an injected container. |
| Callout note target-pick UI | iPhone | Callout notes place a fixed short leader; choosing a callout target/leader interactively is not built. | Add target selection in a later iPhone phase. |