# Deploying the Bangsar South Area Guide to production

**Written for:** whoever is running the deploy — not necessarily the person who wrote the content.

Everything below was built and verified on a developer machine against a READ-ONLY copy of the
shared catalogue. Production is where it gets written, and this is the order.

---

## What is actually changing

The panoramas and the 3D building models are **already on production** (7 panoramas, 4 models) and
nothing here touches them. What is new is the authored content around them:

| | Production today | After this |
|---|---|---|
| Bangsar South story | 2 paragraphs | **6 chapters**, EN + 中文 |
| Chapter cameras | none | 6 — this is what makes the map move as you read |
| Chapter pictures | none | 4 |
| Traced boundary | none | 8 points |
| `profile.video` (retired) | **still present** | removed |
| Avatar library | **table does not exist** | 5 avatars |
| The Vertical / The Horizon / VE Hotel | 3 rows, **empty** | summaries + 14 tabs |
| Location video | none | 1, playing from a Vimeo link |
| Narration | none | 12 files (6 chapters × EN/中文) |

**~4.5 MB of images travel.** No panorama and no model is uploaded, re-uploaded or moved.

---

## Before you start

- [ ] The content bundle: `storage/app/area-guide-export/bangsar-south/` (4.4 MB). Copy it to the
      server — it is NOT in git, because it carries image files.
- [ ] **`ffmpeg` installed on the server.** Only needed for video UPLOADS, which now convert. The
      Bangsar South video is a link, so nothing here needs it — but the next uploaded video will.
- [ ] **A queue worker running.** Same reason: without one, an uploaded video sits on `queued`
      for ever. Check with `php artisan queue:work --once` or your supervisor config.
- [ ] A Gemini API key on **Manage → AI Providers** (not `.env` — see step 6).

---

## The steps

### 1 · Deploy the code

```bash
sudo bash scripts/deploy-update.sh
```

⚠️ **Do not `git pull` first** — the deploy script does it.

### 2 · Run the catalogue migrations

These add the avatar library and the two new asset columns. They are on the **catalogue**
connection, which is a separate migration directory.

```bash
php artisan migrate --path=database/migrations/catalogue --database=catalogue --force
```

Expect four: `create_area_guide_avatars`, `add_avatar_id_to_area_guide_assets`,
`add_transcode_to_area_guide_assets`, `add_video_link_to_area_guide_assets`.

### 3 · Rebuild the config cache

```bash
php artisan config:cache
```

⚠️ **This is not optional.** `config/area_guide_content.php` gained
`video_source_max_kb`, `ffmpeg`, `video_height`, `video_crf`. On a stale cache
`video_source_max_kb` reads NULL, `(int) NULL` is `0`, and `max:0` **refuses every video upload**
with a size message that is true of no file anyone owns. (A floor was added so this can no longer
be silent, but the real ceiling still needs the rebuild.)

### 4 · Clear the retired keys

```bash
php artisan area-guide:strip-area-copy --apply
```

Removes `tagline`, `facts`, `video` and `video_media` from stored areas. Without it, an area that
still holds one **cannot be saved from its own editor** — Laravel's `array:` rule fails the whole
attribute on a single unlisted key.

### 5 · Import the content

**Dry run first. It writes nothing and prints exactly what it would change.**

```bash
php artisan area-guide:import-area /path/to/bangsar-south
```

Read the plan. Then:

```bash
php artisan area-guide:import-area /path/to/bangsar-south --apply
```

What it does: uploads the 5 avatar images, replaces the area's story / chapters / boundary,
fills the three buildings and their tabs, and creates the location video. It is **re-runnable** —
everything is matched by name and updated in place.

It is also **safe about ids**: a chapter's pictures and a tab's `<img>` both address an avatar by
uuid, and production mints its own. The bundle carries `{{avatar:…}}` tokens instead, and the
import substitutes production's real ids *and* the `?v=` cache buster. (Carrying ours would 404
every tab picture on production, silently.)

### 6 · Render the narration

```bash
php artisan area-guide:narrate --area=bangsar-south --force
```

12 files, ~1,400 characters, billed by Google. `--force` matters: the old whole-area audio reads
a script that opened with the `tagline` the guide no longer has, so it is deliberately invisible
until re-rendered.

⚠️ **The key comes from Manage → AI Providers**, not `.env`. This was broken until 2026-09-19 —
the TTS client read the env only, so a verified key on that page produced an empty key and an HTTP
error from Google with nothing naming the cause. Fixed, but confirm the key is saved there.

### 7 · The rollout switches

These are **temporary decisions**, not defaults. Set them in `.env`, then `config:cache` again:

```env
AREA_GUIDE_PINNED_AREA=bangsar-south   # every reader opens on this one area
AREA_GUIDE_CHAT_ENABLED=false          # the area chatbot stays off
```

---

## Check it worked

1. Open `/property/academy?tab=area-guide` as an admin.
2. The story is **6 chapters** with pictures, and a **play button** under "THE STORY".
3. Press play — the voice reads, and at the end it scrolls to the next chapter by itself.
4. Scrolling the story **moves the map**.
5. The area has a **dimmed boundary** around it.
6. A **round avatar** marker waves on the map. Pressing it opens the video **at the top** and the
   page scrolls to it.
7. Open a building marker — The Vertical, The Horizon or VE Hotel — and its tabs have content.

---

## If something is wrong

| Symptom | Cause |
|---|---|
| Import stops naming missing tables/columns | Step 2 did not run. It checks the schema before writing anything. |
| No play button under the story | No narration rendered for that chapter — step 6. The control is absent by design, never broken. |
| Story shows but the map does not move | The chapters have no cameras. Check the import applied `profile.chapters`. |
| A tab picture is missing | The `?v=` did not match. Re-run the import; do not hand-edit the HTML. |
| Every video upload refused on size | Stale config cache — step 3. |
| An uploaded video sits on "Waiting to convert" | No queue worker, or no `ffmpeg`. |
| An area cannot be saved from the editor | Step 4 did not run. |

---

## Rolling back

The import only **adds and updates**; it deletes nothing except an avatar image it has just
replaced. There is no "undo" command — restore from a database backup if the content itself needs
reverting. The code side rolls back with the normal deploy rollback, but ⚠️ **leave the catalogue
migrations in place**: the columns are nullable and harmless to older code, and dropping them
would take the content with them.
