# Area Guide catalogue map — requirements and implementation plan

**Date:** 2026-09-13. **Status:** registry, project map, shared Malaysian detail drawer,
model placement, panorama building markers (polygons until 2026-09-15), location video and shared media lifecycle are implemented
in the local environment. See the [acceptance record](/docs/modules_handbook/shared/project-catalogue/area-guide-map/validation.md)
for tested journeys and outstanding external checks. The production master was
not migrated or written. See the [implemented map handbook](/docs/modules_handbook/shared/project-catalogue/area-guide-map/readMe.md)
for contracts, upload limits and deployment steps. Passing automated tests is not a substitute
for representative-asset, physical-device and deployed cross-site checks.

Companion to [Area Guide](/docs/modules_handbook/main/area-guide/readMe.md),
[Sales Projects](/docs/modules_handbook/manage/engagement/sales-projects.md), and the
[Project Catalogue map](/docs/modules_handbook/shared/project-catalogue/start-here.md).
The requested outcome is an Area Guide where catalogue projects, positioned building
models, real panoramas, and location videos can be explored together.

## What it does

The intended Guide reader flow is: browse the map, see published catalogue projects, click a
project marker or its uploaded building model, and read the existing project-detail
content, including all tabs, in a sliding panel. Location markers additionally open a
video or, for an authorized administrator only in this release, a real uploaded panorama.
A panorama can identify several buildings, each linked to a published catalogue project.
New launches and subsale projects are both eligible.

The intended admin flow is: choose a catalogue project and upload its model; place,
rotate, scale, and raise/lower that model on the map; or click a map location and add a
panorama/video. Admins preview content, edit its associations, and control its publication.
The user requires shared content: an edit must be reflected on every participating site.
The [shared-content design](/docs/modules_handbook/shared/project-catalogue/area-guide-shared-content-plan.md)
describes the same-codebase deployment specified by the user: each site's own Manage
editor reads and writes the same live master database and shared file storage. A separate
application, central-editor redirect or content gateway is not required.

### Requirements checklist

Acceptance rules describe the required end state. A partially implemented row is not accepted
as complete. Decisions D1–D8 are listed after the checklist; the registry implementation and
local setup are documented in [its handbook](/docs/modules_handbook/shared/project-catalogue/area-guide-registry/readMe.md).

| ID | Required behavior | Constraints and acceptance criteria | State |
|---|---|---|---|
| R1 | Show published catalogue projects at their coordinates | Use canonical catalogue identity and coordinates. Preserve country/hostname and per-site listing rules. Exclude unpublished, suppressed, deleted, or invalid-coordinate records from the reader layer. Include both eligible shared and site-owned catalogue records without duplicate UUIDs (Universally Unique Identifiers). A project without a model still has a clickable marker. | Implemented and locally checked: bounded published/listed feed, master/local UUID deduplication, country/coordinate filtering and fail-closed slug collisions. Real Binastra Cochrane map and detail journey passed. |
| R2 | Open complete project details in a Slider / Drawer | All existing detail tabs are required, defaulting to Overview. The standalone project page and drawer must render the same shared components and payload contract, so one component change affects both hosts. Opening, switching projects, and closing preserve the Guide's area, camera, and URL state; previous project data must not leak into the next selection. Desktop panel and mobile presentation must be usable. | Implemented for Malaysia: standalone and drawer share complete content/payload. Component isolation, actual Overview, desktop/390-pixel layout, camera and URL preservation passed. Country-specific HK/AE drawer adapters and physical-device checks remain pending. |
| R3 | Upload a 3D (three-dimensional) building model associated with a catalogue project | Dedicated validated model assets, not an unsupported value silently added to catalogue image/PDF uploads. Verify file structure, supported features, texture/resource dependencies, and size. Bad files produce an actionable error and leave any working model intact. Recommend a self-contained GLB (binary glTF, Graphics Library Transmission Format) starting format; actual formats and limits depend on D6. | Implemented: private, validated self-contained GLB uploads with configurable limits and safe replacement. Synthetic GLB upload/render passed. Representative production model acceptance remains pending. |
| R4 | Let admins position the project model freely on the map | Map placement plus numeric longitude, latitude, heading, elevation, and scale controls; save, reload, edit, and reset. Define source model units, axes and anchor; use metres for height and explicitly distinguish ground offset from sea-level altitude. Transform coordinates are placement metadata and do not overwrite canonical project coordinates. Support a project having several placements/towers in the data model. Preview must reveal buried, floating, or wrongly scaled models. | Implemented: map/numeric transforms, ground offset, reset and up to 100 towers per asset. Save/reload passed in browser; complete offscreen placements and stale-response protection passed automated tests. Real surveyed alignment remains pending. |
| R5 | Make the model itself clickable and visually coherent with the basemap | A click on an uploaded building opens the same project drawer as its marker; dragging the map must not count as selection. Identify and handle overlap with the basemap's existing building. Confirm clipping, depth, hit testing, and alignment in the installed Mapbox version before declaring support. Loading failure leaves the marker usable. | Implemented and exercised: actual mesh click opens shared details; drag does not select; renderer lifecycle tests pass. A reversible basemap 3D-object toggle handles overlap inspection; exact per-building clipping is not supported by this implementation. |
| R6 | Add and manage content by clicking a map location | An authorized admin can create, move, edit, preview, and remove a panorama/video location. Save the actual capture/display coordinates independently of project coordinates. A location may relate to more than one catalogue project through its media annotations. | Implemented: authorized map location editor, reusable upload/edit modal, placement and removal paths. Model create/edit/remove journey passed; panorama/video final browser results are recorded in the acceptance report. |
| R7 | Upload a panorama and generate a stable 360-degree viewing link | Accept and validate a complete equirectangular panorama, with an explicit starting direction. Drag/look, zoom, fullscreen, mobile controls, loading, and failure states work. Share a stable authenticated application URL. Only authorized administrators may see panorama entry points, pages, original images, previews and annotation data in this release; knowing the URL must not bypass that check. Keep audience separate from publication so a future member rollout can change the policy. Image replacement must not serve a stale cached original. | Implemented: stable admin-only panorama link, private/versioned image and annotations, starting view. Full HTTP permission and replacement tests passed. Actual panorama interaction results are recorded in the acceptance report; representative originals remain pending. |
| R8 | Make the building clickable inside the panorama | **Revised 2026-09-15 (user decision):** the admin clicks ONE spot on the building and places a marker — a ring with the building's name above it — then binds a published catalogue project (new launch or subsale) or a custom building; the marker's size and position are adjustable in edit mode. The earlier wording (manually traced building boundary, any point inside the region clickable, vertices, seam wraparound, overlapping regions) is superseded. Markers are stored as a yaw/pitch point + size bound to the image version; replacing the original image invalidates them; publication is rechecked on selection/save/read. Automatic building recognition remains out of scope. | Implemented as markers (`area_guide_hotspots`): published-project or custom-building binding, image revisions and preservation of unseen cross-site markers. Save, visibility, replacement and stale-write tests passed. The traced-polygon implementation this row first recorded was removed with its geometry rule and tests. |
| R9 | Upload a location video and a separate cover image | The map displays the uploaded cover with a play affordance. Clicking plays the selected video; pause, seek, fullscreen, mobile playback, replacement, and error handling work. No video download/autoplay merely from panning the map. Correct byte-range delivery or an equivalent working storage-delivery strategy is required for seeking. | Implemented: H.264 MP4 with optional AAC, separate map cover, protected ranges/HEAD and user-initiated player. Real codec upload and delivery tests passed. Actual browser playback results are recorded in the acceptance report; representative-size infrastructure acceptance remains pending. **2026-09-15 (D8):** the same video asset can also be placed INSIDE a panorama as a marker, uploaded from there through the same endpoint, and opens on its own full-screen page in a new tab; see the map handbook's *Video points*. |
| R10 | Edit shared content from the current site's own backend | Reuse the same Manage editor code on each site, with local administrator login and explicit Area Guide permissions. Follow `withSuite()`, Form Requests, repositories, correctly scoped transactions and feedback patterns. Proposed lifecycle: draft → preview → publish/unpublish. Publishing never bypasses an admin-only audience. Reader endpoints cannot expose drafts merely because their UUID is known. Grant only the shared-content access required for this module; preserve unrelated Catalogue write restrictions. Record source site and administrator identity, and detect conflicting edits. | Implemented and tested: same-site Manage entry, administrator/permission checks, scoped writes, draft/publish policy, editor/site snapshots and optimistic revisions. Actual production grants and deployment remain pending. |
| R11 | Preserve the current module boundaries | Keep the Area Guide button hidden while its direct URL remains reachable subject to existing access rules. Retain tutorials, narration, and walkable-session behavior unless explicitly redesigned. Sales pipelines, bookings, commissions, and working-project facts are outside this change. Do not turn project selection into a completion/quiz submission. | Preserved: direct Guide URL and hidden Tab verified in browser; existing Guide/tutorial/preview regression selection passed 61 tests / 952 assertions. Learning and business records remain site-local. |
| R12 | Keep the map usable with many projects and large assets | Fetch bounded viewport data; use clustering/zoom thresholds where needed; lazy-load models and media; release graphics resources and cancel stale requests. Avoid downloading the whole catalogue or all model files at country zoom. Test the supplied panorama's original size and representative models/videos on desktop and mobile; record measured results and device/browser, not guessed performance promises. | Implemented and unit-tested: bounded feeds/clusters, zoom gates, at most 12 nearby models, aborted stale requests and released graphics resources. Synthetic desktop/narrow-layout checks passed; large originals and physical-mobile performance remain unverified. |
| R13 | Preserve media and catalogue integrity | Reuse MediaService with explicit shared database/storage ownership covering media records, outbox and cleanup jobs; existing site-local uploads retain their default behavior. Store replacement → attach successfully → clean up superseded files. A failure keeps the prior published content working. Use integer relationship keys plus durable UUID identity, without cross-connection SQL joins or schema foreign-key constraints. Real photographs, rendered views, and illustrative models must retain accurate provenance. | Implemented and tested: explicit shared MediaContext, dedicated media/outbox, private versioned delivery and writer cleanup. Model removal cleaned its local media record. Actual shared cloud bucket and deployed-worker checks remain pending. |
| R14 | Deliver documentation and verifiable results | Update affected module documents and the catalogue map where applicable. Run relevant backend/frontend tests, isolated migration checks if schema changes, and the client plus server-rendering production build. Complete browser checks for each interaction; explicitly list failures and untested requirements. | Implementation/docs and relevant automated checks are complete; final browser/build evidence and explicit external limitations are maintained in area-guide-map/validation.md. Do not infer production or representative-asset acceptance from automated passes. |
| R15 | Move editable Area content to a shared database and make the new functionality country-independent | Reuse country codes; persist region/area identity, translations, camera profile, media references and per-area feature settings. A single registry serves Guide, tutorial pickers, Road, narration, chat and walkable configuration. Keep old area keys/deep links and learning records. Import is repeatable and does not overwrite admin edits. First import existing Malaysia content; additional countries use the same new map/model/panorama/video functionality. Do not claim existing country-specific analysis logic became universal through a data migration. | Implemented locally: database registry, preserved keys, repeatable import and generic country-profile map/media functionality. Imported 3 countries / 6 regions / 24 areas; country-specific learning/analysis has not been universalized. Production cutover remains pending. |
| R16 | Reflect one shared edit across participating sites | All participating sites use the same codebase, live master database and shared file storage. Other sites retrieve the same published revision on their next load/revalidation, without a code deployment or catalogue mirror. A shared revision must invalidate separately cached content; a current-site cache clear alone is insufficient. No per-site divergent placement/media copy is writable. Access rules still differ by viewer and allowed country/site catalogue visibility. Two-site integration tests must prove replacement/unpublish propagation, edit-conflict handling and graceful shared-database failure. | Implemented shared ownership, fresh reads, revision conflicts and cross-site polygon preservation. Isolated ownership/cache/repository tests passed. Two real deployments, master migration, shared cloud storage and actual cross-site propagation remain pending. |

### Decision record

This table incorporates the user's numbered answers and subsequent clarifications.
An unanswered choice is not approval; confirmed product decisions are not implementation evidence.

| ID | Decision | Options / reason |
|---|---|---|
| D1 | Geographic rollout | Confirmed: Malaysia first, with the same new capabilities reusable by Hong Kong and the United Arab Emirates. User proposed a database-backed Area registry; recommended design is R15. Existing authored Malaysia content is western Malaysia; data import does not invent eastern Malaysia stories or boundary assets. New country profiles must not hardcode the current peninsula bounds. |
| D2 | Content ownership and distribution | Confirmed outcome: one change is reflected on all participating sites. The user specifies `propertylabglobal.com` as production and all sites using the same codebase and live master project database. Store authored Area content and media metadata in that shared database, with file bytes in common cloud storage. Keep site-private business data separate. Validate the shared connection explicitly; do not silently fall back to a writable local copy. This deployment description is user-supplied, not a live audit of every site. |
| D3 | Project drawer scope | Confirmed: complete detail tabs, shared as components between the standalone page and drawer. Updates to a component affect both consumers. Separate deployments still need the same code release; shared data updates do not deploy frontend code. |
| D4 | Viewing-link access | Confirmed: panoramas are visible and accessible only to administrators now; member access is a future policy change. Cover the entry, page, image and annotation endpoints. The existing Guide access policy remains in force for the other functionality; no public location-video share policy has been requested. |
| D5 | Building selection inside panoramas | Confirmed in two follow-ups (2026-09-13/14): the whole building region must be clickable; admins manually trace the boundary and then bind a published new-launch or subsale project. **Revised 2026-09-15:** tracing was judged unnecessary — a click-placed marker (ring + name, one style, movable and resizable in edit mode) replaces the traced region; the binding rules stay. No automatic image segmentation required for this phase. |
| D6 | File handling | User requires reuse of MediaService and asked why format/size information was needed. Do not block general architecture on a file-size questionnaire. Proposed first model format is self-contained GLB; panorama uses complete equirectangular images; video formats/codecs and configurable limits must be documented and tested. File conversion and arbitrary-format support are not capabilities MediaService supplies. Representative originals remain necessary for actual acceptance. |
| D7 | Admin entry | Confirmed preference: keep editing inside the current site's own backend. Recommended design follows this preference: deploy the same editor with the existing codebase and write the same live shared database directly, using that site's administrator login and explicit module permissions. No separate project folder, central-editor redirect, delegated-login gateway or site-to-site synchronization is required for the specified topology. |
| D8 | Video points — map and panorama share one backend (2026-09-15) | Confirmed: an admin places a video point on the map AND inside a panorama; clicking it opens a page of ours that renders the video (new tab, like the panorama link). Design: the panorama point is a third marker kind linking the SAME location-video asset the map already stores (one upload path, one validation, one page `/manage/area-guide/videos/{uuid}`); both editors validate and resolve their project/building/video links through one class (`AreaGuideLinks`) — "backend same code, only the frontend differs". A video used by markers cannot be deleted. **Watch progress and every other reader activity are to be recorded by ONE activity-tracking module built later, once the guide's functions are complete — explicitly deferred by the user, nothing recorded today.** |

These are product choices, not requests for permission to perform routine coding. The user
explicitly requested clarification before filling important gaps by assumption.

## How it works

### Verified distinction between the four modules

| Module | Record / identity | Owns | Relationship to this enhancement |
|---|---|---|---|
| Sales / working Projects | Site `projects` rows; `origin=custom` or `origin=catalog` | Sales/marketing work, group scope, bookings and commission settings | A catalogue-linked row resolves its identity through canonical helpers. Custom rows can have no catalogue reference. Neither case makes this table the public map catalogue. |
| Project Catalogue | `catalog_projects`, stable UUID, country-specific slug | Canonical name, developer, geography, coordinates, floor plans, media, reference facts, publication | Supplies map project identity and the existing project detail. `published_at` and a working project's Active status are different concepts. |
| Area Guide | Shared registry countries/regions/areas plus map assets, placements and building markers | Continuous map, stories, narration, chat, walkable-session entry | Becomes the reader-facing host for the new project and location layers. |
| Area Tutorials | `area_tutorials`, stops and episode cells | Ordered route lessons, timed stops, video references, editorial scripts | Remains a curated learning route that can reference catalogue projects. It is not the owner of every map project, model, or panorama. |

The sales document's `?view=projects` word "catalogue" describes that sales hub's list.
It does not mean those rows are the shared `catalog_projects` table. Custom project writes
go through `SalesProjectRepository`; the catalogue-linked Property writer requires a
catalogue reference. A linked project's cached `projects.name`/price columns are not a
second source of truth.

### Implemented content relationships

The following shows the implemented ownership model. The exact tables and local/production
connection settings are documented in the shared map handbook. Production deployment remains pending.

```mermaid
flowchart LR
    CP[Catalogue project facts] --> MP[Published map marker]
    CP --> D[Shared project detail content]
    CP --> WP[Optional sales working project]
    CP --> TS[Optional tutorial stop]
    CP --> MA[Project model asset]
    MA --> PL[One or more map placements]
    MP --> D
    PL --> D
    L[Authored map location] --> P[Panorama and image version]
    L --> V[Video and cover image]
    P --> H[Building markers]
    H --> CP
```

All authored locations, model assets and placements belong to the shared master database,
with uploaded file bytes in shared cloud storage. Each site's backend edits those same records.
Catalogue remains the project-fact source. Consumer sites read the common content and
apply their viewer/country/project visibility rules; they do not own divergent asset copies.
An uploaded photo is not a Google-tile bake, and the building seen in a photo is not
automatically recognized or converted into a 3D model by uploading it.

### Initial inspection: existing reuse points and limits

This subsection records the pre-implementation investigation. The current implementation
and its evidence are listed above and in the shared map handbook.

- **Map rendering:** `MalaysiaMap.vue` owns a Mapbox Standard instance, configured regions,
  area markers and camera/orbit behavior. It does not fetch a public catalogue layer or
  expose a location/model editor. `utils/mapboxLoader.js` pins Mapbox GL JS to **3.7.0**.
  `utils/areaGuide/avatar.js` already demonstrates a Three.js custom layer sharing the
  map canvas/depth buffer. Its avatar deliberately changes apparent size with zoom;
  building models must instead retain their geographic metre scale.
- **Country portability:** the registry slice removed the first-region assumption and
  parameterized the continuous map's camera/bounds. Existing illustrated countries retain
  their presentation, while new country records can use the street renderer. Multiple
  region/country fixtures pass; real browser graphics and later media layers remain pending.
- **Geographic queries:** `AreaStationFinder` selects a small high-rise teaching set;
  `AreaTutorialsController::projectsInBounds()` is an admin evidence picker. Neither is
  the required public, federated, country-and-site-filtered project feed. Reuse the
  visibility/federation patterns of `BuildNewProjectListing`, with a bounded viewport.
- **Coordinates:** publication configuration requires latitude and longitude, but
  `published()` itself only checks `published_at`. This proves a data contract, not that
  every current production coordinate has been audited. Apply finite/range checks and
  expose missing-coordinate problems to admins rather than placing bad rows at zero.
- **Project drawer:** `Drawer.vue`, `HeroGallery.vue`, `OverviewTab.vue` and the other
  project-detail tabs exist. The full `ProjectDetail.vue` also owns layout, document
  title, country-dependent requests, unit selection and browser URL behavior. Its
  `?embed=1` mode is a review iframe treatment, not a reusable shell-free Vue component.
  A full drawer needs shared content/payload builders and explicit host context.
- **Project data loading:** `CatalogueDetailService` is the Malaysia detail contract;
  Hong Kong and the United Arab Emirates use separate builders/pages. Preserve lazy
  tab loading and existing locked data. Rendering every analysis tab on drawer open
  could trigger unrelated network requests or expensive work.
- **Panorama viewer:** `VrPanoramaViewer.vue` uses Photo Sphere Viewer core and compass,
  and already supports equirectangular images, yaw/pitch, zoom and fullscreen. It lacks
  saved project hotspots and an annotation editor. The installed core is 5.11.5;
  a compatible markers plugin or equivalent extension is needed for R8.
- **Existing VR (Virtual Reality) bake:** `CatalogVrBake` owns a queued Google 3D Tiles
  rendered view associated with one project. Its floor-plan alignment editor changes
  the image's downward view, not building hotspots. Retain this flow and its provenance;
  do not disguise a photo upload as a successful bake.
- **Storage:** `MediaService::storeUpload()` buffers the whole file; `storeFromPath()`
  streams an existing temporary file and has a cleanup outbox. The media handbook is
  less detailed than the current code here. Use dedicated validation and safe replacement.
  Existing catalogue uploads accept images/PDF (Portable Document Format), up to 20 MB
  (megabytes), and have no model kind. A new model workflow is required.
  MediaRepository and cleanup jobs currently use the default connection: a shared
  owner argument does not automatically move their records to the shared database.
  Add explicit server-controlled ownership context covering repositories, storage disk,
  outbox and retry jobs together, while retaining the default behavior of local consumers.
  Each site's worker must resolve the correct shared task and media record; colliding
  local integer IDs must never cause a different site's asset to be deleted.
- **Delivery:** `VrMediaController` demonstrates same-origin image streaming, avoiding
  CORS (Cross-Origin Resource Sharing) texture failures. It is tied to approved bakes
  and immutable caching; reuse the pattern with location-specific ownership and versions.
  Video needs byte ranges, as demonstrated by `ZoomRecordingStreamController`, or an
  explicitly tested alternative. A full-image response is not a video streaming solution.
- **Upload limits:** the generic upload request documents a 90 MB ceiling and an assumed
  100 MB upstream limit. Actual local/production limits have not been inspected here.
  The AI Video footage endpoint accepts larger files but starts transcription/generation
  work, so map videos must not be routed into it merely to reuse an upload form.

The user-supplied [Cochrane tour](https://storefrontcochranevrprod.z23.web.core.windows.net/)
was inspected as HTML: it identifies Panotour Pro and loads a krpano-based tour. Its
interactive behavior has not been browser-verified in this investigation. It is a
reference for the desired viewing experience, not a requirement to import its software,
copy its contents, or add every tour feature.

The [Mapbox custom-model example](https://docs.mapbox.com/mapbox-gl-js/example/add-3d-model/)
confirms the map/Three.js integration approach. The
[building-replacement example](https://docs.mapbox.com/mapbox-gl-js/example/clip-layer-building/)
demonstrates clipping competing basemap geometry, but the current example uses a newer
Mapbox version and labels the feature experimental. Test against this repository's pinned
version before choosing an upgrade or promising identical behavior.
The [Photo Sphere Viewer markers documentation](https://photo-sphere-viewer.js.org/plugins/markers)
supports point and polygon annotations; it does not supply this application's catalogue
association, publishing, or admin persistence logic.

### Database and access constraints

The earlier local-only inspection in this session found zero rows in `petav3`'s
`area_tutorials`, stops, episode cells, visits and local `catalog_projects`, while the
application was configured to read the remote `master_projects` catalogue. Those counts
do **not** mean the application has no catalogue projects. This enhancement discovery
did not query that remote database or re-count its published-coordinate coverage.

- The code supports live-master and local-copy catalogue modes, but the user's specified
  deployment for this enhancement is live-master on every participating site. Verify that
  the shared feature's connection points to that master; do not silently edit local copies.
  Copied-catalogue support is not a first-phase prerequisite. Do not change connection mode
  or mirror data as an incidental part of implementing a map.
- Existing Catalogue editing has a hostname gate, and some catalogue credentials may be
  read-only. Explicitly authorize the new shared-content module on participating sites,
  with only the required table access. Keep unrelated Catalogue write restrictions.
  Never switch the application's entire default connection to the master.
- Local user integer IDs can collide between sites. Shared audit records must identify the
  source site and administrator UUID, with a name snapshot, rather than assuming that a
  bare `created_by` value refers to the same person everywhere.
- Use published + per-site listing + allowed-country rules consistently for reader project
  markers and details. UUID identity survives changed integer IDs after distribution.
- Re-check visibility when a project is later unpublished, suppressed, deleted or no longer
  resolvable. Hide its model/project navigation as appropriate, invalidate stale reader
  data, and enforce the same rules on direct drawer requests. A still-published panorama
  may retain its photograph, but its markers must not disclose hidden project details.
- Shared project associations require durable UUID identity and master resolvability;
  a project existing only on one consumer site must not be accepted as globally resolvable.
  Local ID caches, if introduced, follow the reference-repair patterns. Do not copy
  AreaTutorialStop's current integer-only association.
- Resolve linked projects and current publication from the shared catalogue while applying
  the current site's viewer-specific state locally. Increment shared content revisions
  inside the save transaction; each site's cache must revalidate against that revision.
  Detect stale edit revisions before saving to prevent one administrator silently
  overwriting another site's newer changes. Test with separate site caches.
- `whereHas()` on the existing federated relation is not federated; the current approved
  panorama resolver has this known problem. New code must not reproduce it.
- Schema placement depends on model ownership: site tables use ordinary migrations;
  shared catalogue tables use `database/migrations/catalogue/`. A local development
  migration must not silently alter the remote master.
- The existing Guide page combines feature, training, and temporary operational locks;
  not every existing media/game endpoint checks the identical combination. Define a
  consistent access check for new reader endpoints and shareable media, without treating
  hidden navigation as an authorization rule.
  **Resolved (2026-09-14).** The consistent check exists and is named:
  [`AreaGuideViewerAccess::allowsGuide()`](/src/AreaGuide/Support/AreaGuideViewerAccess.php)
  is the tab's own combination (temporary lock with the admin `?preview=locked` escape,
  the five-day trainee lock, membership Function access), and `allows()` is that plus the
  catalogue-map capability. Narration, chat, walk stations, walk visits and the Area
  Tutorial player now each call `allowsGuide()`; the map, asset and media routes call
  `allows()`. A withheld page prop hides a control — it never authorizes an endpoint —
  so a locked reader now receives 403 from those routes rather than being served.
  The one remaining endpoint outside this rule is the Area Tutorial builder's own
  manage-side data routes, which are admin-gated separately.
- The `admin` middleware checks `isManageUser()`, which also admits sales roles. An
  admin-only panorama policy must explicitly check the intended administrator access;
  a successful visit to the Manage portal is not sufficient on its own. Keep protected
  panorama bytes behind that policy, not a public or immutable-cache delivery branch.

### Phased implementation and exit checks

**Current checkpoint:** the local registry, shared media ownership, project map/detail,
model editor, panorama building markers (polygons at the time) and location video are implemented. The coordinated
map HTTP selection passed 22 tests / 287 assertions; the existing Guide/tutorial/preview
selection passed 61 / 952. Real browser model/Overview and narrow-layout checks passed.
The [acceptance record](/docs/modules_handbook/shared/project-catalogue/area-guide-map/validation.md)
tracks the final panorama/video/browser/build results and pending representative-asset
and two-production-site checks. Only local databases/storage were changed.

1. **Shared data foundation and source migration.** Implement the current-site editor's
   shared connection, module permissions, media ownership, revision/audit contract and
   country-independent registry. Import the existing
   Area sources idempotently while preserving keys and historical references. Define
   image/model/video limits and sample files. Verify the 3D placement/hit-testing/clipping
   approach on the pinned renderer. This phase determines whether conversion or large-file
   transfer belongs in the first implementation.
2. **Published project map and reusable details.** Implement the bounded, federated project
   feed and markers, then the agreed drawer. Verify reader visibility, site/master separation,
   area/camera preservation and existing project-page parity before adding uploads.
3. **Project model assets and placement editor.** Add the chosen asset association,
   repositories/validation, model renderer, placement controls, persistence and publication.
   Verify scale/orientation/grounding, model selection and failure fallback with a real file.
4. **Location panoramas and building associations.** Add location/media records, validated
   panorama upload, stable viewing page, initial view settings and the agreed annotation
   editor. Verify permissions, replacement semantics and multiple project links.
5. **Location video and cover.** Implement the chosen upload/processing path, map thumbnail,
   playback and seeking. Verify large-file behavior against actual infrastructure limits.
6. **Integrated acceptance and documentation.** Exercise the whole admin → reader journey,
   mobile layouts and repeated opening/closing. Run the focused regression suite, migration
   replay in disposable databases, and production client/server builds. Reconcile R1–R14
   with evidence and leave any unfinished requirement explicitly pending. Include R15/R16
   source-migration and cross-site update checks in this acceptance pass.

### Foundation evidence retained from the earlier delivery

> **HISTORICAL — read the date, not the column heading.** This table records the
> state at the EARLIER delivery and is kept as the audit trail for it. Its
> "Current result" column is current only as of that delivery: the
> "Not implemented/tested" rows for reader visibility, upload lifecycle and the
> drawer/panorama/model/video frontend were superseded first by the
> *Current implementation acceptance checkpoint* below and then by the
> *2026-09-14 remediation checkpoint*. Do not quote a row from this table as
> today's status.

| Check | Planned evidence | Current result (at the earlier delivery) |
|---|---|---|
| Requested docs and integration paths | Read AGENTS/GUIDELINES, all 761 sales-project lines, all 244 catalogue-map lines, targeted linked docs and implementation files | Completed as source inspection; docs/code discrepancies recorded above |
| Requirement traceability | R1–R16 plus D1–D7 | Updated with confirmed Malaysia-first, shared content, complete tabs, admin-only panorama, manual building polygons and current-site editing against the same live master database |
| Reader visibility | Published/unpublished, listed/suppressed, country restrictions, own/shared duplicates and invalid coordinates | Not implemented/tested |
| Schema and two-connection behavior | Disposable site/master fixtures, shared media/outbox ownership, audit identity collisions, separate caches, edit conflicts and empty-database migration replay | Registry/repository isolation and fresh schema replay passed in disposable fixtures. Local development imported 33 records, repeat import preserved all. Shared media/outbox and real deployed two-site checks remain pending. |
| Upload and lifecycle | Valid/invalid/oversized models, images, videos; failed replacement; remove/publish/unpublish | Not implemented/tested |
| Frontend state | Drawer switching, local tab state, async races, media lifecycle, map/panorama selection | Registry, deep links, country camera, tutorial/Road consumers, hidden tab and admin modal tests passed. New drawer, panorama/model and video behavior not implemented/tested. |
| Browser acceptance | Desktop/mobile, real model render and hit tests, pano seams/hotspots, video seeking, keyboard/focus, console errors and resource cleanup | Blocked for this investigation: browser inventory returned no enabled browsers; no visual runtime claim made |
| Tests and Build | Focused PHP/Vitest suites, migration and suite-link guards, `npm run build` (client and server rendering) after relevant changes | Foundation feature regression: 58 tests / 958 assertions passed, with one existing PHPUnit configuration-schema deprecation. Pure unit/config/guard tests: 39 / 288 passed. Frontend: 16 files / 119 tests passed. Migration-constant and suite-link guards passed; final build status below. |

The local HTTP-kernel probe returned 200 for `Manage/AreaGuide/Index`, `ready=true`,
`canManage=true`, `localPreview=true`, and 3 countries / 6 regions / 24 areas. This checks
the actual local configuration and authorized route response, not browser rendering.
The first import also exposed an existing Inspector shutdown problem on Windows
(`CreateProcess` command line too long). Its idempotent rerun with process-only
`INSPECTOR_ENABLE=false` completed cleanly; no global Inspector behavior was changed.

The suite-link guard was corrected to normalize Windows separators and exclude exact
`.test.js` fixtures in both the PHP scanner and its mirrored Vitest check. A temporary
negative probe proved production `.js` violations still fail, with the existing bare-link
baseline unchanged at 71. Probe files were removed.

The earlier foundation-only `npm run build` passed for both client (54.36 seconds) and server rendering
(26.54 seconds). Vite still reports large output chunks; passing the build does not prove
graphics performance. The local log is `storage/app/area-guide-checks/build-final.log`
(ignored development output, not a repository artifact).

After the final local-preview prop and flag hardening, the Manage feature file was rerun:
8 tests / 69 assertions passed (the same existing PHPUnit schema deprecation). These are
part of the earlier 58-test feature selection, not eight additional distinct tests.

## Related files

**Module documentation**
- [Implemented registry and local setup](/docs/modules_handbook/shared/project-catalogue/area-guide-registry/readMe.md)
- [Area Guide](/docs/modules_handbook/main/area-guide/readMe.md)
- [Walkable session](/docs/modules_handbook/main/area-guide/walkable-session/readMe.md)
- [Area Tutorials](/docs/modules_handbook/manage/area-tutorials/readMe.md)
- [Route export audit](/docs/modules_handbook/manage/area-tutorials/route-export-schema-v2-audit.md)
- [Sales Projects](/docs/modules_handbook/manage/engagement/sales-projects.md)
- [Catalogue map](/docs/modules_handbook/shared/project-catalogue/start-here.md)
- [Catalogue databases](/docs/modules_handbook/shared/project-catalogue/databases-and-distribution.md)
- [Catalogue neighbours](/docs/modules_handbook/shared/project-catalogue/neighbours-and-legacy.md)
- [Project Detail](/docs/modules_handbook/main/project-detail/readMe.md)
- [VR360](/docs/modules_handbook/shared/project-catalogue/vr360/readMe.md)
- [Media](/docs/modules_handbook/shared/media/readMe.md)

**Backend and configuration**
- [Project](/src/Property/Project.php), [SalesProjectRepository](/src/Engagement/Repositories/SalesProjectRepository.php)
- [CatalogProject](/src/Analysis/Reference/CatalogProject.php), [CatalogueFederationService](/app/Services/Property/CatalogueFederationService.php)
- [BuildNewProjectListing](/app/Actions/BuildNewProjectListing.php), [MarketSiteResolver](/src/Common/Services/MarketSiteResolver.php)
- [CatalogueDetailService](/app/Services/Property/CatalogueDetailService.php), [ProjectDetailController](/app/Http/Controllers/Main/Site/ProjectDetailController.php)
- [AreaStationFinder](/src/AreaGuide/Services/AreaStationFinder.php), [AreaTutorialsController](/app/Http/Controllers/Manage/AreaGuide/AreaTutorialsController.php)
- [CatalogVrBake](/src/Analysis/Reference/CatalogVrBake.php), [VrMediaController](/app/Http/Controllers/Main/Site/VrMediaController.php)
- [MediaService](/src/Common/Services/MediaService.php), [ZoomRecordingStreamController](/app/Http/Controllers/Manage/Zoom/ZoomRecordingStreamController.php)
- [database.php](/config/database.php), [project_catalogue.php](/config/project_catalogue.php), [site.php](/config/site.php)
- [Main routes](/routes/main.php), [Manage routes](/routes/web.php), [test configuration](/phpunit.xml)

**Frontend**
- [AreaGuidePanel](/resources/js/Components/AreaGuide/AreaGuidePanel.vue), [MalaysiaGuide](/resources/js/Components/AreaGuide/MalaysiaGuide.vue), [MalaysiaMap](/resources/js/Components/AreaGuide/MalaysiaMap.vue)
- [Mapbox loader](/resources/js/utils/mapboxLoader.js), [avatar custom layer](/resources/js/utils/areaGuide/avatar.js)
- [ProjectDetail](/resources/js/Pages/Main/Site/ProjectDetail.vue), [OverviewTab](/resources/js/Components/ProjectDetail/OverviewTab.vue), [HeroGallery](/resources/js/Components/ProjectDetail/HeroGallery.vue)
- [ProjectDetailTabs](/resources/js/Components/ProjectDetail/ProjectDetailTabs.vue), [Drawer](/resources/js/Components/Drawer.vue)
- [VrPanoramaViewer](/resources/js/Components/Vr/VrPanoramaViewer.vue), [VrAlign](/resources/js/Pages/Manage/Property/Catalog/VrAlign.vue)
- [PortalEngagementTabs](/resources/js/Components/Portal/PortalEngagementTabs.vue), [ManageLayout](/resources/js/Layouts/ManageLayout.vue)

**Existing regression starting points**
- `tests/Feature/Main/Portal/AreaGuide/`, `tests/Feature/Manage/AreaGuide/AreaTutorialsTest.php`
- `tests/Feature/Main/NewProjectsTest.php`, `tests/Feature/Property/ProjectDetailUnpublishedPreviewTest.php`, `tests/Feature/Property/CatalogVrBakeTest.php`
- `resources/js/Components/AreaGuide/*.test.js`, `resources/js/Components/ProjectDetail/*.test.js`, `resources/js/Components/ShowTabs.test.js`

## Current implementation acceptance checkpoint

*The build-and-browser checkpoint that closed the implementation. It is still the most
recent VISUAL evidence — the [remediation checkpoint](#remediation-checkpoint--2026-09-14)
below is newer but automated only, and did not repeat these browser checks.*

The continuation added dedicated shared media and map-asset migrations to the isolated local
`petav3_area_guide` database. All existing local content was preserved (nine tables including
the migration ledger). Only the local map flag, private preview disk and ffprobe executable
were added to the local environment; master credentials were preserved. No real production
write credential was requested or used.

Final automated evidence: 22 map workflow tests / 287 assertions and 61 existing Guide,
registry/tutorial and project-preview regression tests / 952 assertions passed. Shared-media
isolation and spherical geometry selections passed, as did 236 distinct related frontend
tests across the non-guard and source-guard batches. PHP style, migration and suite-link
guards passed. Final client and server-rendering builds passed. One existing PHPUnit
configuration-schema deprecation remains; overlapping selections are not summed.

Actual local browser checks passed for published project/model selection, saved transforms,
the shared Overview drawer, manual panorama polygons and fullscreen selection, video cover,
native playback/seek/fullscreen, and 390-pixel layouts. The three synthetic test assets were
removed and their local cleanup checked. See the [acceptance record](/docs/modules_handbook/shared/project-catalogue/area-guide-map/validation.md)
for exact evidence, local environment issues and unverified external requirements.

Representative user model, original panorama and video files have not been supplied. Small
clearly labelled synthetic assets are reserved for functional checks; they do not demonstrate
real-project geometry, actual-source-image quality or large-file mobile performance.

## Remediation checkpoint — 2026-09-14

A review pass over the whole Area Guide surface (registry, reader backend, map backend,
shared media, manage frontend, reader frontend, project drawer, catalogue federation and
Area Tutorials) fixed the confirmed defects and re-ran the automated evidence below. This
is an AUTOMATED checkpoint only: no browser session, no production deployment, no
representative real asset and no physical device was exercised in it. The browser results
recorded in the section above remain the most recent visual evidence and were not repeated.

### What changed that this checklist tracks

- **Reader access is now one check (closes the D-constraint above).** Narration, chat, walk
  stations, walk visits and the Area Tutorial player call `AreaGuideViewerAccess::allowsGuide()`;
  map, asset and media routes call `allows()`. The temporary lock and the trainee lock are
  authorization now, not withheld props. The Learning Hub additionally withholds the Area
  Tutorial cards from a locked reader.
- **`AREA_GUIDE_LOCKED` is parsed as a boolean** (`filter_var` + `FILTER_NULL_ON_FAILURE`),
  so `false` / `0` / `off` / `no` / empty all OPEN the guide, an unset flag keeps it locked
  and anything unrecognised fails CLOSED. It previously required `=0`; **check the live
  `.env` before deploying.** `ANALYZE_PROPERTY_LOCKED` is deliberately unchanged and still
  requires `=0`.
- **Unsupported-country project details now answer with a destination.** A non-MY project
  returns HTTP 422 `{message, href}` — the standalone project page path — so the drawer
  links out instead of dead-ending (requirement R2's fallback path).
- **Walk answers are decided server-side.** The browser posts an option key; the server
  re-derives the question from the same presented figures and computes the verdict.
- **Panorama markers carry a live `href`**, their yaw/pitch are bounded by the angle rule
  (exactly ±π / ±π/2), and the video upload rule inspects the ftyp brand so a
  QuickTime or 3GP container can no longer be stored as `video/mp4`.
- **Shared media cleanup no longer runs storage I/O inside a transaction**: a short
  transaction claims the task, the bucket call runs unlocked, a second short transaction
  verifies the claim and writes.
- **`area_tutorial_stops` gained the `catalog_project_uuid` twin**, so the stop's project
  link survives a master re-key (the "do not copy AreaTutorialStop's integer-only
  association" constraint above). Its `market_catalyst_id` half is still integer-only and
  is listed as open below.

### Automated evidence, 2026-09-14

| Selection | Command | Result |
|---|---|---|
| Area Guide unit | `phpunit tests/Unit/AreaGuide` | 92 tests / 492 assertions passed |
| Area Guide reader (portal) | `phpunit tests/Feature/Main/Portal/AreaGuide` | 62 tests / 875 assertions passed |
| Map assets (manage HTTP) | `phpunit tests/Feature/Manage/AreaGuideMapAssetsTest.php` | 28 tests / 356 assertions passed |
| Registry editor | `phpunit tests/Feature/Manage/AreaGuideContentTest.php` | 12 tests / 135 assertions passed |
| Area Tutorials | `phpunit tests/Feature/Manage/AreaGuide` | 22 tests / 210 assertions passed |
| Shared media | `phpunit tests/Unit/Services/AreaGuideMediaTest.php`, then the media cleanup selection (`Feature/Common/{CatalogMediaDeleteCleanup,CleanupMediaObject,MediaCleanupOutbox}Test`, `Unit/Services/MediaServiceStreamingTest`, `Unit/Property/CatalogMediaTest`) | 23 / 128 and 42 / 239 passed |
| Catalogue / detail pages | `phpunit tests/Feature/Property` | 218 tests / 1072 assertions, **2 pre-existing failures** (see below) |
| Frontend | `vitest` over `Components/AreaGuide`, `Components/ProjectDetail`, `utils/areaGuide`, `Pages/Manage/AreaGuide`, `Pages/Main/Portal/{AreaTutorial,Lms,Road}`, `ShowTabs.test.js`, `composables` | 66 files / 951 tests passed |
| Migration-constant guard | `php scripts/check-migration-constants.php` | OK — 182 references / 692 migrations |
| Suite-link guard | `php scripts/check-suite-links.php` | OK — ratchet 71, baseline 71 (unchanged) |
| PHP style | `vendor/bin/pint --test` over the 103 changed PHP files | 0 failures |

Five failures remain across the wider suite and **all five are pre-existing** — each
reproduces in isolation against code byte-identical to the pre-remediation baseline:
`CatalogAttachConcurrencyTest` (expects 2 canonical locks, the merge service takes 3),
`CatalogueCombineReviewTest` (asserts a review tab by position; the strip gained a tab),
`Fpa\ConsentFormTest` (`Dompdf\Dompdf` is declared in `composer.json` but absent from this
machine's `vendor/`), `Whatsapp\FlowEngineTest`, and the `portalGuide` lesson-staleness
guard (14 lessons stale against screens edited 2026-09-04 → 09-09; the five this work
touched were re-read and bumped).

### Still open after this checkpoint

- **No browser, production, real-asset or device acceptance was added.** Representative user
  models, original panoramas and large video files are still not supplied.
- **Production `ffprobe` is still pending**, and the video rule now raises a floor on it: it
  passes `-show_entries …:format_tags=major_brand:…`, and an `ffprobe` that does not
  recognise that section exits non-zero, which makes the rule reject EVERY video (it fails
  closed). Install a current build before enabling uploads.
- **Site listing has no writing screen.** The MY/HK/UAE detail pages now enforce
  `site_catalog_projects` through `CatalogueDetailService::viewerMaySee()`, but
  `SiteCatalogProjectRepository` has no route or controller caller. A deployment switched to
  `Site::MODE_EXPLICIT` would 404 every project page until that screen exists. This site is
  seeded `inherit` with no suppressions, so nothing changes here today.
- **The walk badge is still brute-forceable, more expensively.** Posting `correct: true` is
  closed, but the verdict is a sticky OR, so enumerating a station's 2–3 option keys still
  lands the right one. Making the first answer final contradicts the documented product rule
  ("an answer, once right, stays right") and is an owner's decision, not a review's.
- **`area_tutorial_stops.market_catalyst_id`** has no uuid twin and is not in
  `RepairCatalogueReferences::TABLES`. Impact is nil today (`market_catalysts` is empty in
  the live catalogue), but it is the same bug class as the project link that was fixed.
- **The whole Area Guide registry/map backend is still UNTRACKED** and must land in one
  commit, including its five migrations (none of which declares a schema foreign key) and
  `package.json` + `package-lock.json` + `yarn.lock` together — the deploy's install misses
  `@photo-sphere-viewer/markers-plugin` if the lockfiles are split from it.
- **`@photo-sphere-viewer/markers-plugin` is pinned to exactly `5.11.5`** while its siblings
  use `^5.11.5`, and two `three.js` copies coexist with no dedupe. Harmless for the SVG
  markers in use; it would bite on an `npm update` to core 5.12.x or on a future 3D marker
  type. Not fixed, because every option needs an `npm install` / lockfile resync.
