# Area Guide map — local acceptance record

**Date:** 2026-09-13–14. This record covers the Malaysia-first implementation in
`C:\Users\zhish\work\petav3`. It is evidence for the
[requirements checklist](/docs/modules_handbook/main/area-guide/catalogue-map-requirements.md),
not a production deployment record. Local implementation checks are complete; the
external acceptance items below remain explicitly unverified.

A later audit-remediation pass re-ran the automated checks only; its results are in
[the 2026-09-14 remediation checkpoint](#remediation-checkpoint--2026-09-14) at the end of this
record. The browser evidence below is from the 2026-09-13–14 implementation run and was **not**
repeated.

> **2026-09-15:** the traced building boundaries this record verified (polygons, vertices,
> overlap priority, spherical-polygon validation) were **replaced** by click-placed building
> markers (`area_guide_hotspots`: a yaw/pitch point + size, ring + name), by user decision. The
> boundary rows below are history; the marker suites and their counts live in the
> [map readMe](/docs/modules_handbook/shared/project-catalogue/area-guide-map/readMe.md).

## What it does

The implemented paths are:

- Manage: `/manage/area-guide` for the country/region/area registry and
  `/manage/area-guide/map` for models, panoramas, video and placements.
- Reader: `/property/academy?tab=area-guide`, with the Learning Hub navigation
  button still hidden and the existing access rules retained.
- Panorama: `/manage/area-guide/panoramas/{uuid}`, generated after upload and
  restricted to authorized administrators, including its image and annotations.
- Malaysia project details: the standalone page and map drawer use the same full
  `ProjectDetailContent` component and payload builder. Country-specific Hong Kong
  and United Arab Emirates drawer adapters are future work; their standalone pages
  remain the fallback, and since the remediation checkpoint the endpoint's 422 refusal
  carries that standalone page's link so the drawer offers a real destination.

## How it works

### Test boundaries

The authoring database is the isolated, loopback-only `petav3_area_guide` with a
private development media disk. The site database remains `petav3`. Automated
database tests use the separate disposable `petav3_area_guide_testing`; master and
reference connections are forced to that scratch target during those tests. The
production master was neither migrated nor written.

The local schema has the registry, asset, placement, polygon, shared media and
cleanup tables. The idempotent initial import created 3 countries, 6 regions and
24 areas; repeating it preserved all 33 existing records.

Browser checks use actual installed Chrome through Playwright, a new local admin
session, the running Vite development server and Mapbox. Chrome runs headless with
software graphics. Desktop uses 1440 pixels width; the narrow layout check uses
390 pixels. This is a browser layout/interaction check, not physical-phone or
production graphics performance evidence.

Fixtures are explicitly synthetic: a blue 20 by 60 by 20 metre box GLB, a labelled
2048 by 1024 panorama, a labelled video cover and a six-second H.264/AAC video.
They are not replicas or photographs of a real project. Real Binastra Cochrane
catalogue identity and its existing Overview are used to verify project binding.
The original 12000 by 6000 user panorama and representative production models or
videos have not been exercised.

### Browser evidence

| Journey | Result |
|---|---|
| Open Manage map and read published project feed | Passed: actual Mapbox streets and published catalogue dots loaded. |
| Upload a model through the modal | Passed: a synthetic GLB was stored as a draft with its published catalogue association. |
| Save model transform and fetch it again | Passed: heading 30 degrees, ground offset 8 metres and scale 1.2 persisted; the blue building rendered. |
| Click the actual model mesh | Passed: ray intersection followed by a real canvas click opened Binastra Cochrane's shared Overview. |
| Drag across a model; close its drawer | Passed: dragging did not select; closing preserved camera coordinates, zoom and URL. |
| Narrow project drawer | Passed: Overview rendered in a 390-pixel drawer with reachable close control. |
| Reader Guide with a published model | Passed: published model rendered on the direct Guide page, its marker opened the shared Overview, and the hidden navigation button count was zero. |
| Panorama upload and building polygon | Passed: a draft panorama loaded; four manually drawn points were bound to published Binastra Cochrane and persisted through an authorized reload. Clicking the polygon interior opened the complete Overview and preserved the panorama URL. |
| Panorama fullscreen and narrow layout | Passed: actual fullscreen entered, selecting the polygon exited fullscreen and opened the drawer; the 390-pixel viewer and drawer remained usable. |
| Video upload, cover, play, seek and narrow layout | Passed: the uploaded cover rendered on the map, its marker opened the private video, and native user-initiated playback decoded 640 by 360 frames. A native timeline click sought to 4.56 of 6 seconds; native fullscreen entered. At 390 pixels, playback worked and the complete 310-pixel video fit inside its 358-pixel dialog. |

No Vue runtime errors occurred in the completed model/Overview journey. The local
site already reports an unavailable Reverb websocket and an avatar image blocked by
Chrome; these are not counted as clean application-wide console acceptance.
The Laravel debug toolbar is enabled locally and appears in screenshots.
Early attempts encountered Mapbox library-load failures and intermittent local HTTP
errors; retries used independent test sessions. A submitted video also received an
Inertia 409 version response and a document reload while build output was changing.
Final interaction checks ran after builds finished. The final panorama/video harnesses
suppressed only Vite HMR (Hot Module Replacement) reload/update notifications to keep
the current running code stable; real HTTP, authentication, media and rendering remained
in use. No development-server or production configuration was changed for that freeze.
An intermittent local bootstrap failure also logged `Unable to boot ApiServiceProvider,
configure an API domain or prefix`, before the Area Guide handler. This existing startup
failure was not diagnosed or fixed by the map change. These observations are retained as
test conditions, not evidence of production reliability.

Video fullscreen entry was verified; keyboard Escape exit was not. The harness explicitly
exited fullscreen for teardown. Mobile acceptance covers the visible dialog/player bounds,
not the underlying Manage page or debug toolbar's full document width.

All three synthetic acceptance assets were removed through their authorized delete paths.
Independent local checks confirmed archived assets, no active related placements/polygons,
and removal of their media records; the video's physical file and cover were absent too.
The registry import remains intact. No test geometry is presented as real project content.

### Automated checks

| Selection | Result |
|---|---|
| Existing Guide, Manage registry/tutorial and project preview regression selection | 61 tests / 952 assertions passed. |
| Earlier map HTTP selection plus existing media cleanup/outbox/streaming regressions | 44 tests / 355 assertions passed; its 14 map cases are superseded by the expanded map selection below. |
| Expanded map HTTP selection | 22 tests / 287 assertions passed, including full panorama privacy, Manage/portal separation and cross-site polygon preservation. Superseded by the 28 / 356 run in the remediation checkpoint below. |
| Shared media context isolation | 12 tests / 63 assertions passed, including independent database ownership and cleanup identity. |
| Spherical polygon validation | 24 tests / 38 assertions passed. |
| Frontend component, geometry and integration checks | All 216 non-guard tests passed in the final broad selection. Its repository-wide source scan timed out and was subsequently rerun separately after the I/O improvement below. |
| Suite-link behavior and source guards | All 20 tests passed with the default timeout after caching each source once during inventory preparation. Both regex rules, offending-file reporting and assertions are unchanged. The actual inventory still took 81.58 seconds on this Windows run; test execution took 132 milliseconds. Together the non-guard and guard selections cover 236 distinct tests. |
| Latest Map / Drawer fixes | 13 tests passed, including complete offscreen placements, stale responses and nested-dialog Escape behavior. |
| PHP style, migration constants, suite-link guard, diff whitespace | Passed. Existing bare Manage link baseline remains 71. |
| Final client and server-rendering build | Passed: `npm run build` produced client and server bundles; after the final marker accessibility fix, the client was rebuilt successfully (5 minutes 34 seconds). The final server bundle already included that fix (1 minute 35 seconds). |

These selections overlap and must not be added to claim a distinct total. PHP tests
report the existing PHPUnit configuration-schema deprecation. Build success does
not establish model alignment or large-asset performance.

### Remaining external acceptance

- Apply the reviewed, table-scoped master migrations/grants and configure one common
  private cloud bucket on participating deployments. Verify refresh propagation,
  replacement, unpublish, edit conflicts and cleanup on two real sites. The local
  checkout has only the existing read-only master credential.
- Supply representative self-contained GLB files, complete panoramas and videos for
  actual alignment, upload-limit, memory and device tests. Current default limits
  are configurable: models/panoramas 50 MiB (mebibytes), videos 80 MiB and covers
  10 MiB. Supported-format defaults are documented, not a claim that arbitrary
  administrator file formats can be converted automatically.
- Check on physical mobile devices and production upload/proxy/worker infrastructure.
- Add country-specific full detail adapters when expanding beyond the agreed first
  Malaysia release. The map/media registry itself accepts country profiles from data.

The installed Mapbox path provides a reversible basemap 3D-object toggle to inspect
overlaps. It does not clip individual existing basemap buildings around an uploaded
model. Real surveyed model alignment has not been inferred from the synthetic box.

## Video points checkpoint — 2026-09-15

A panorama marker can now open a **location video** (the same asset the map places), the video has
its own full-screen page, it can be uploaded from inside the panorama editor, and both editors
check what they link through one class. See the map handbook's *Video points* section. **Automated
evidence only**: no browser session, no `npm run build`, no production or master write, no
representative original video, no physical device.

| Selection | Result |
|---|---|
| `tests/Feature/Manage/AreaGuideVideoMarkersTest.php` (new) + `AreaGuideMapAssetsTest` + `AreaGuidePanoramaLinksTest` + `AreaGuideBuildingsTest` + `AreaGuideContentTest` + `tests/Feature/Main/Portal/AreaGuide` + `tests/Unit/AreaGuide` | 239 tests / 5,668 assertions passed (one run, one scratch database, `migrate:fresh` proving the edited migration) |
| The same plus `tests/Feature/Manage/AreaGuide` (Area Tutorials, untouched) | 261 tests / 5,878 assertions passed |
| Frontend: `Components/AreaGuide`, `Pages/Manage/AreaGuide`, `utils/areaGuide`, `Pages/Main` | 76 files / 999 tests passed (was 74 / 975) |
| `php scripts/check-suite-links.php` | OK — bare `/manage` ratchet unchanged at 71 |
| `php scripts/check-migration-constants.php` | OK — 182 constant references across 693 migrations |
| `vendor/bin/pint --test`, `php -l`, `git diff --check` over every changed file | Passed |

The PHPUnit configuration-schema deprecation still appears and is the project's own. Scratch
databases were dropped afterwards. Two pint failures elsewhere in the tree
(`InvestmentPromptController`, several Events/Leads/Zoom/Property tests) belong to other
uncommitted work and were left alone.

**Not covered by any of this:** watch progress and reader activity are deliberately not recorded
yet (one tracking module later, by the user's decision), and no real MP4 was played end to end
through the new page in a browser.

## Remediation checkpoint — 2026-09-14

A review of the whole Area Guide slice produced a list of defects and stale claims, which were
fixed and then re-verified. **This checkpoint is automated evidence only.** No browser session,
no `npm run build`, no production or master database migration or write, no representative
original assets and no physical device were involved; nothing in the browser evidence table
above was repeated.

### What the remediation changed

| Area | Change |
|---|---|
| Reader authorization | Narration, chat, walk stations, walk visits and the Area Tutorial player each call `AreaGuideViewerAccess::allowsGuide()` themselves, so the temporary lock and the trainee lock are authorization rather than a withheld page prop. `AREA_GUIDE_LOCKED` is now parsed as a boolean and fails closed on an unrecognised value. |
| Registry validation | Translation locale keys and `profile.states` entries are whitelisted; reader presentation filters translations again as defence in depth; a country's map camera is optional; `walkableAreas()` enforces the supported-country list and the 1200 m / 6-station defaults itself. |
| Registry operations | `area-guide:import-registry` prints a read-only preview in both modes, names every key/parent conflict, exits non-zero and refuses to write while one is listed. A broken legacy source raises `AreaGuideContentUnavailable` so readers degrade instead of 500-ing. The Manage editor receives a presented tree without integer ids, blame columns or other sites' administrator UUIDs. |
| Map backend | Unavailable project choices are 422 field errors instead of 404s; the non-Malaysian refusal returns the standalone page link; annotations carry a live `href`; panorama angles are bounded by the geometry check's own constants; the video rule verifies the ftyp major brand; `media_directory` is a declared setting; every map response carries `private, no-store`; a project-less model is hidden rather than a 500; polygon saves resolve projects in one batched query. |
| Map frontend | The outline editor settles each save explicitly instead of inferring success from a props refresh; revision conflicts raise a recoverable banner; canvas errors are split into fatal and transient; a created asset is matched and selected deterministically; overlap priority is authorable; drawer descriptors carry a standalone-page link and the drawer is a real modal dialog with a focus trap. |
| Shared media | Cleanup gained a `processing` claim with an owner token, configurable writing/claim leases, and storage I/O that runs outside every transaction; a queued task whose database identity no longer matches fails permanently instead of retrying. |

### Automated results

| Selection | Result |
|---|---|
| `tests/Feature/Manage/AreaGuideMapAssetsTest.php` | 28 tests / 356 assertions passed (was 22 / 287) |
| `tests/Unit/AreaGuide` (registry, config, station finder, spherical polygon, angle rule, station question, route export) | 92 tests / 492 assertions passed |
| `tests/Feature/Manage/AreaGuideContentTest.php` | 12 tests / 135 assertions passed |
| `tests/Feature/Manage/AreaGuide` (Area Tutorials, episodes, catalogue link) | 22 tests / 210 assertions passed |
| `tests/Feature/Main/Portal/AreaGuide` (guide, chat, content, endpoint gate, walk game, lockdown, narration) | 62 tests / 875 assertions passed |
| `tests/Unit/Services/AreaGuideMediaTest.php` | 23 tests / 128 assertions passed |
| `tests/Feature/Common` media selection + `MediaServiceStreamingTest` + `CatalogMediaTest` | 42 tests / 239 assertions passed |
| `tests/Feature/Property` (includes the new `ProjectDetailSiteListingTest`) | 218 tests / 1072 assertions, 2 failures — both pre-existing, see below |
| `tests/Feature/Main/CatalogueDetailTest.php` | 12 tests / 325 assertions passed |
| Frontend: Area Guide, project-detail, manage Area Guide pages, portal LMS/tutorial, `ShowTabs`, composables | 66 files / 951 tests passed |
| Frontend, whole `vitest` suite | 190 of 194 files and 2277 of 2305 tests passed; the 4 failing files are pre-existing, see below |
| `php scripts/check-migration-constants.php` | OK — 182 constant references across 692 migrations |
| `php scripts/check-suite-links.php` | OK — bare `/manage` ratchet unchanged at 71 |
| `vendor/bin/pint --test`, `php -l`, `git diff --check` across every changed PHP file | Passed (103 files) |

PHP runs used a disposable scratch database and the existing PHPUnit configuration-schema
deprecation still appears. These selections overlap and must not be summed.

### Failures that were left alone because they predate this work

- `tests/Feature/Property/CatalogAttachConcurrencyTest` (expects 2 canonical lock bindings where
  the merge service now takes 3) and `CatalogueCombineReviewTest` (asserts a review tab by
  position, and the strip gained a tab). Both reproduce in isolation against code identical to
  the pre-remediation snapshot.
- `tests/Feature/Manage/Setting/MarketsTest` (3 cases still expect two seeded markets; a
  committed migration added the UAE as a third).
- `tests/Feature/Fpa/ConsentFormTest::test_signing_produces_a_stored_pdf` — a local environment
  gap: `dompdf/dompdf` is declared in `composer.json` but absent from `vendor/`.
- `tests/Feature/Whatsapp/FlowEngineTest::test_start_proactive_flow_skips_opted_out_contact`.
- `resources/js/utils/portalGuide/portalGuide.test.js` — 14 lesson entries are stale against
  screens last edited between 2026-09-04 and 2026-09-09. The five lessons this work made stale
  were re-read and re-stamped; the other 14 were deliberately not stamped, because that guard
  asserts a human re-read the lesson beside its screen. Three further `vitest` files (Zoom
  evaluation, Property Match admin, AI prompt preview) fail on a `usePage()` mock without a
  `url`; their code is unchanged by this work.

### Still open after this checkpoint

- Everything under *Remaining external acceptance* above is unchanged: master migration and
  grants, a common private bucket, two-deployment propagation, representative assets and
  physical devices.
- `@photo-sphere-viewer/markers-plugin` is still pinned to exactly `5.11.5` beside `^5.11.5`
  siblings, and two three.js copies coexist. Fixing it needs an `npm install`, which this pass
  could not run. `package.json`, `package-lock.json` and `yarn.lock` must be committed together.
- The whole Area Guide backend (registry, map assets, shared media, five migrations, the JSON
  source and the new tests) is still untracked in git and must land in one commit.
- The registry's per-request memo is inert under PHPUnit (the suite always reports a console
  SAPI), so it has unit coverage only, never an end-to-end HTTP exercise.
- Deploy-time behaviour changes to check before release: `AREA_GUIDE_LOCKED=false` now **opens**
  the Guide where it previously kept it locked, and under the locked default a plain member now
  receives 403 from narration, chat, stations and visits. An iPhone/QuickTime `.mov` upload is
  now refused rather than stored as MP4, and the video rule requires an `ffprobe` that knows the
  `format_tags` section.
- The detail pages now enforce the site listing as well as publication, so a deployment switched
  to explicit site-listing mode would 404 every project until those listing rows exist — and no
  screen writes them yet.

## Related files

- [Map contracts and production runbook](/docs/modules_handbook/shared/project-catalogue/area-guide-map/readMe.md)
- [R1–R16 requirements and D1–D8 decisions](/docs/modules_handbook/main/area-guide/catalogue-map-requirements.md)
- [Catalogue documentation map](/docs/modules_handbook/shared/project-catalogue/start-here.md)
- [Shared project-detail component](/docs/modules_handbook/shared/project-detail/readMe.md)

Private local logs, browser scripts and screenshots live under the ignored
`storage/app/area-guide-checks/` directory. Authentication/session fixtures are private
development artifacts and must never be committed or copied into documentation.

Relevant run logs include `map-http-final.log`, `frontend-final-serial.log`,
`suite-test-cached-final.log`, `build-map-final.log` and
`build-client-marker-final.log`. Browser evidence includes `model-desktop.png`,
`model-project-drawer.png`, `model-project-mobile.png`, `guide-model-reader.png`
and `panorama-saved-outline.png`, `panorama-fullscreen-project-drawer.png`,
`panorama-mobile-project-drawer.png`, `video-cover-marker.png`,
`video-desktop-playing.png`, `video-fullscreen.png` and `video-mobile-playing.png`
under that directory's `browser/` folder. The model, panorama and video state/cleanup
records distinguish successful final journeys from earlier harness or environment failures.
