chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
# Amazon Bedrock provider
|
||||
|
||||
Presenton can use **Amazon Bedrock** as the text LLM provider for presentation generation, editing, and chat. Configure Bedrock in the app UI (**Settings → Text provider**) or with environment variables / `userConfig.json` (Docker and desktop).
|
||||
|
||||
The backend sends your configured **Model** value to the Bedrock **Converse** API as `modelId`. That means you can use:
|
||||
|
||||
- Standard on-demand **model IDs** (for example `us.anthropic.claude-3-5-haiku-20241022-v1:0`)
|
||||
- **Inference profile ARNs** (required for some newer models such as Claude Sonnet 4.6)
|
||||
|
||||
Region and credentials must match the model or profile you choose.
|
||||
|
||||
---
|
||||
|
||||
## Required configuration
|
||||
|
||||
| Field (UI) | Environment variable | Description |
|
||||
| --- | --- | --- |
|
||||
| **Provider** | `LLM=bedrock` | Select **Amazon Bedrock**. |
|
||||
| **Region** | `BEDROCK_REGION` | AWS region for Bedrock (default: `us-east-1`). Must match where the model or inference profile is available. |
|
||||
| **Model** | `BEDROCK_MODEL` | Model ID or inference profile ARN (see below). |
|
||||
| **AWS Access Key ID** | `BEDROCK_AWS_ACCESS_KEY_ID` | IAM access key with Bedrock invoke permissions. |
|
||||
| **AWS Secret Access Key** | `BEDROCK_AWS_SECRET_ACCESS_KEY` | Secret for the access key above. |
|
||||
|
||||
### Authentication
|
||||
|
||||
Use **one** of these patterns:
|
||||
|
||||
1. **AWS access key pair** (most common): Access Key ID + Secret Access Key (required fields above).
|
||||
2. **Bedrock API key** (optional UI field): `BEDROCK_API_KEY` — if set, you do not need the access key pair.
|
||||
|
||||
Do not mix Bedrock API key auth with explicit AWS access key fields; the client accepts one method only.
|
||||
|
||||
---
|
||||
|
||||
## Optional (advanced) fields
|
||||
|
||||
Under **Advanced settings** in the Bedrock form (or via env):
|
||||
|
||||
| Field (UI) | Environment variable | When to use |
|
||||
| --- | --- | --- |
|
||||
| **AWS Session Token** | `BEDROCK_AWS_SESSION_TOKEN` | Temporary credentials (STS, assumed role, SSO session). |
|
||||
| **AWS Profile Name** | `BEDROCK_PROFILE_NAME` | Use a named profile from `~/.aws/credentials` instead of inline keys (desktop/local dev). |
|
||||
|
||||
These are optional. Most Docker and server deployments only need region, model, and access key + secret.
|
||||
|
||||
---
|
||||
|
||||
## Model ID vs inference profile ARN
|
||||
|
||||
Many Bedrock models support **on-demand** invocation with a model ID like:
|
||||
|
||||
```text
|
||||
us.anthropic.claude-3-5-haiku-20241022-v1:0
|
||||
```
|
||||
|
||||
Some **newer** models (for example **Claude Sonnet 4.6**) do **not** support on-demand throughput with a plain model ID. For those you must use an **inference profile** ARN in the **Model** field.
|
||||
|
||||
In the AWS console: **Bedrock → Inference profiles** (or cross-region inference profiles), copy the full ARN, and paste it into Presenton’s **Model** field unchanged.
|
||||
|
||||
Presenton passes that string directly to Converse as `modelId`, so both formats work as long as AWS accepts them for your account and region.
|
||||
|
||||
---
|
||||
|
||||
## Common error: on-demand throughput not supported
|
||||
|
||||
You may see an error similar to:
|
||||
|
||||
```text
|
||||
Invocation of model ID anthropic.claude-sonnet-4-6 with on-demand throughput isn't supported.
|
||||
A model ID is currently required in model invocation requests for on-demand throughput.
|
||||
A foundation model ARN or inference profile ARN is currently required in model invocation requests.
|
||||
```
|
||||
|
||||
**What it means**
|
||||
|
||||
- The model ID you entered is valid in Bedrock, but that model **only** supports invocation through a **foundation model ARN** or **inference profile ARN**, not classic on-demand model IDs.
|
||||
- This often applies to newer Anthropic models and **cross-region inference** setups.
|
||||
|
||||
**What to do**
|
||||
|
||||
1. Open **Amazon Bedrock** in the same **region** as `BEDROCK_REGION`.
|
||||
2. Find the **inference profile** for the model (for example `us.anthropic.claude-sonnet-4-6`).
|
||||
3. Copy the full **inference profile ARN**.
|
||||
4. Paste the ARN into Presenton’s **Model** field (not the short model ID).
|
||||
5. Ensure IAM allows `bedrock:InvokeModel` / Converse on that profile in that region.
|
||||
|
||||
---
|
||||
|
||||
## Example: working configuration (Claude Sonnet 4.6)
|
||||
|
||||
Use an inference profile ARN in **Model**, with region **us-east-1**:
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| Provider | `bedrock` |
|
||||
| Region | `us-east-1` |
|
||||
| Model | `arn:aws:bedrock:us-east-1:471112542209:inference-profile/us.anthropic.claude-sonnet-4-6` |
|
||||
| AWS Access Key ID | Your IAM user or role access key |
|
||||
| AWS Secret Access Key | Matching secret |
|
||||
|
||||
Replace `471112542209` with **your** AWS account ID. The ARN must come from **your** Bedrock console for the profile you enabled.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
docker run -it --name presenton -p 5001:80 \
|
||||
-e LLM="bedrock" \
|
||||
-e BEDROCK_REGION="us-east-1" \
|
||||
-e BEDROCK_AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY_ID" \
|
||||
-e BEDROCK_AWS_SECRET_ACCESS_KEY="YOUR_SECRET_ACCESS_KEY" \
|
||||
-e BEDROCK_MODEL="arn:aws:bedrock:us-east-1:471112542209:inference-profile/us.anthropic.claude-sonnet-4-6" \
|
||||
-e IMAGE_PROVIDER="pexels" \
|
||||
-e PEXELS_API_KEY="YOUR_PEXELS_KEY" \
|
||||
-e CAN_CHANGE_KEYS="false" \
|
||||
-v "./app_data:/app_data" \
|
||||
ghcr.io/presenton/presenton:latest
|
||||
```
|
||||
|
||||
### On-demand model example (Haiku)
|
||||
|
||||
For models that support on-demand IDs:
|
||||
|
||||
```bash
|
||||
-e BEDROCK_MODEL="us.anthropic.claude-3-5-haiku-20241022-v1:0"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## IAM permissions
|
||||
|
||||
The IAM principal (user or role) behind your keys needs permission to call Bedrock in the configured region, for example:
|
||||
|
||||
- `bedrock:InvokeModel`
|
||||
- `bedrock:InvokeModelWithResponseStream`
|
||||
|
||||
Scope policies to the model IDs or inference profile ARNs you use. If you use inference profiles, include the profile ARN (or a wildcard your org allows) in the resource list.
|
||||
|
||||
Enable model access in the Bedrock console (**Model access**) for the foundation models tied to your profile.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Invalid model identifier
|
||||
|
||||
- **Symptom:** `ValidationException`, unknown model, or model not found.
|
||||
- **Checks:**
|
||||
- **Model** matches exactly what Bedrock shows (ID or full ARN, no extra spaces).
|
||||
- For Sonnet 4.6–class models, use an **inference profile ARN**, not only `anthropic.claude-…` without the profile path.
|
||||
- Model access is **enabled** for your account in that region.
|
||||
|
||||
### Missing Bedrock permissions
|
||||
|
||||
- **Symptom:** `AccessDeniedException`, not authorized to perform `bedrock:InvokeModel`.
|
||||
- **Checks:**
|
||||
- IAM policy allows invoke on the model or inference profile.
|
||||
- Keys belong to the intended account; no typo in access key or secret.
|
||||
- If using `BEDROCK_PROFILE_NAME`, the profile’s role has Bedrock permissions.
|
||||
|
||||
### Region mismatch
|
||||
|
||||
- **Symptom:** Model not found, or profile ARN rejected.
|
||||
- **Checks:**
|
||||
- `BEDROCK_REGION` is the region where the model/profile was created (for example `us-east-1` in the ARN path must match `BEDROCK_REGION`).
|
||||
- Cross-region inference profiles still use a home region in the ARN; configure Presenton’s region to match AWS guidance for that profile.
|
||||
|
||||
### Unsupported on-demand invocation
|
||||
|
||||
- **Symptom:** Error text mentions *on-demand throughput isn’t supported* or requires *inference profile ARN*.
|
||||
- **Fix:** Switch **Model** from a plain model ID to the **inference profile ARN** (see [Common error](#common-error-on-demand-throughput-not-supported) above).
|
||||
|
||||
### Auth configuration errors at startup
|
||||
|
||||
- **Symptom:** “Bedrock auth is incomplete” or “Provide either api_key or AWS credentials”.
|
||||
- **Fix:** Set `BEDROCK_API_KEY`, **or** both `BEDROCK_AWS_ACCESS_KEY_ID` and `BEDROCK_AWS_SECRET_ACCESS_KEY`. Do not leave all three empty.
|
||||
|
||||
---
|
||||
|
||||
## Environment variable reference
|
||||
|
||||
| Variable | Required | Notes |
|
||||
| --- | --- | --- |
|
||||
| `LLM` | Yes | Must be `bedrock`. |
|
||||
| `BEDROCK_REGION` | Recommended | Default `us-east-1` if unset. |
|
||||
| `BEDROCK_MODEL` | Yes | Model ID or inference profile ARN. |
|
||||
| `BEDROCK_AWS_ACCESS_KEY_ID` | If not using API key | Pair with secret. |
|
||||
| `BEDROCK_AWS_SECRET_ACCESS_KEY` | If not using API key | Pair with access key ID. |
|
||||
| `BEDROCK_API_KEY` | Optional | Alternative to access key pair. |
|
||||
| `BEDROCK_AWS_SESSION_TOKEN` | Optional | Temporary credentials. |
|
||||
| `BEDROCK_PROFILE_NAME` | Optional | Named AWS profile. |
|
||||
|
||||
See also the [Deployment configurations](../README.md#️-deployment-configurations) section in the main README.
|
||||
@@ -0,0 +1,110 @@
|
||||
# Electron Dependency Strategy
|
||||
|
||||
This is the working strategy for reducing first-run dependency installers in
|
||||
the Electron app while keeping Presenton Apache-2.0.
|
||||
|
||||
## Recommendation
|
||||
|
||||
- Bundle Chrome for Testing with the Electron package (including Microsoft APPX)
|
||||
for export rendering.
|
||||
- Bundle ImageMagick under `resources/imagemagick/` for each platform build; the
|
||||
packaged app validates that bundle during `afterPack`.
|
||||
- Bundle the presentation export runtime for PPTX-to-HTML conversion and use
|
||||
Chromium to render custom template previews.
|
||||
- Extract modern OOXML/OpenDocument text directly without an office engine.
|
||||
|
||||
## Licensing Notes
|
||||
|
||||
This is engineering guidance, not legal advice.
|
||||
|
||||
ImageMagick is practical to bundle. The official license permits personal,
|
||||
internal, and commercial use, and its terms are close to Apache-2.0. Keep the
|
||||
ImageMagick license and notices in the distributed app.
|
||||
Source: https://imagemagick.org/license/
|
||||
|
||||
Chromium/Chrome for Testing can be bundled, but the notices matter. Puppeteer
|
||||
now targets Chrome for Testing for supported automation, and Chromium source is
|
||||
BSD-style plus third-party licenses. Keep generated browser credits/notices with
|
||||
the shipped runtime.
|
||||
Sources:
|
||||
- https://pptr.dev/supported-browsers
|
||||
- https://chromium.googlesource.com/chromium/src/+/main/LICENSE
|
||||
- https://www.chromium.org/chromium-os/licensing/
|
||||
|
||||
## Runtime Layout
|
||||
|
||||
Bundled Chromium:
|
||||
|
||||
```text
|
||||
electron/resources/chromium/
|
||||
presenton-runtime.json
|
||||
chrome/<platform-build-id>/...
|
||||
```
|
||||
|
||||
Populate it with:
|
||||
|
||||
```bash
|
||||
cd electron
|
||||
npm run prepare:export-chromium
|
||||
```
|
||||
|
||||
Set `SKIP_BUNDLED_CHROMIUM=1` to keep the old first-run download behavior.
|
||||
|
||||
Bundled ImageMagick:
|
||||
|
||||
```text
|
||||
electron/resources/imagemagick/<platform>-<arch>/
|
||||
presenton-runtime.json
|
||||
...
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
electron/resources/imagemagick/win32-x64/magick.exe
|
||||
electron/resources/imagemagick/darwin-arm64/bin/magick
|
||||
electron/resources/imagemagick/linux-x64/bin/magick
|
||||
```
|
||||
|
||||
Populate it with:
|
||||
|
||||
```bash
|
||||
cd electron
|
||||
npm run prepare:imagemagick
|
||||
```
|
||||
|
||||
Platform behavior:
|
||||
|
||||
- Windows downloads and validates the official portable `.7z` runtime.
|
||||
- Linux downloads and validates the official AppImage, then writes a `bin/magick`
|
||||
wrapper with `APPIMAGE_EXTRACT_AND_RUN=1` so it works without a host FUSE setup.
|
||||
- macOS vendors a build-host ImageMagick prefix (`magick` on PATH, or
|
||||
`IMAGEMAGICK_VENDOR_DIR`) and rewrites non-system dylib references into the
|
||||
packaged runtime with `otool` and `install_name_tool`.
|
||||
|
||||
The app checks the manifest-backed bundle before PATH, Homebrew, MacPorts, or
|
||||
other system installs.
|
||||
|
||||
## Current Behavior
|
||||
|
||||
- FastAPI receives `IMAGEMAGICK_BINARY`, `MAGICK_HOME`, and
|
||||
`MAGICK_CONFIGURE_PATH` when the bundled or system ImageMagick runtime is
|
||||
detected at startup.
|
||||
- PPTX previews use the bundled PPTX-to-HTML converter and Chromium renderer.
|
||||
- Modern OOXML/OpenDocument text extraction uses the bundled Python parser.
|
||||
- Export Chromium and ImageMagick resolution check manifest-backed bundled app
|
||||
runtimes before user or system locations.
|
||||
|
||||
## APPX / Store builds
|
||||
|
||||
Before `npm run build:electron`:
|
||||
|
||||
1. Run `npm run prepare:export-chromium` so Chromium is under `resources/chromium/`.
|
||||
2. Run `npm run prepare:imagemagick` so ImageMagick is under
|
||||
`resources/imagemagick/<platform>-<arch>/`.
|
||||
3. The bundled export runtime and Chromium handle Template Studio previews.
|
||||
|
||||
Microsoft Store (MSIX/APPX) packages install under `Program Files\WindowsApps`.
|
||||
Bundled Chrome cannot be launched in place from that folder; on first export the app
|
||||
copies the browser tree to `%LOCALAPPDATA%\…\Cache\msix-export-chromium\` (same pattern
|
||||
as the MSIX export runtime for Sharp). The portable EXE install does not need this copy.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Custom Template Import Getting Started
|
||||
|
||||
This document explains the current PPTX import behavior from the custom
|
||||
template page.
|
||||
|
||||
## What This Flow Does
|
||||
|
||||
The `Select a PPTX file` action on:
|
||||
|
||||
```txt
|
||||
/custom-template
|
||||
```
|
||||
|
||||
uses the original Template Studio UX. The slide-editor import backend is always
|
||||
used.
|
||||
|
||||
The flow is:
|
||||
|
||||
1. Select a PPTX file.
|
||||
2. Click `Check Fonts`.
|
||||
3. Review available and missing fonts in the inline font management step.
|
||||
4. Upload any missing fonts you want to preserve.
|
||||
5. Continue to generate the old `Slide Preview` screen.
|
||||
6. Click `Generate Template` to create the reusable template.
|
||||
|
||||
Template Studio still uses the same old UX: inline font management, slide
|
||||
preview, progress, and generated preview cards. Only the Generate backend
|
||||
changes. It sends the preview data to the Templates V2 API:
|
||||
|
||||
```txt
|
||||
POST /api/v2/templates
|
||||
```
|
||||
|
||||
That v2 request creates and saves the template, while the page stays on the
|
||||
old `/custom-template` preview experience. The generated preview cards render
|
||||
the raw Templates V2 `layouts.layouts[*]` JSON directly; they do not stage or
|
||||
open a slide-editor deck.
|
||||
|
||||
Template Studio does not open the `Prepare fonts` dialog, create a slide-editor
|
||||
deck, stage anything in IndexedDB, or redirect to `/slide-editor`.
|
||||
|
||||
## Verify The Flow
|
||||
|
||||
1. Start the app normally.
|
||||
2. Open `/custom-template`.
|
||||
3. Click `Select a PPTX file`.
|
||||
4. Choose a `.pptx` file under 100 MB.
|
||||
5. Confirm the selected file appears in the upload card.
|
||||
6. Click `Check Fonts`.
|
||||
7. Confirm the inline font management step appears.
|
||||
8. Continue to preview.
|
||||
9. Click `Generate Template`.
|
||||
10. Confirm the page stays on `/custom-template`.
|
||||
11. Confirm Generate creates the template with `POST /api/v2/templates`.
|
||||
@@ -0,0 +1,56 @@
|
||||
# macOS Development & Distribution
|
||||
|
||||
Guides for building and signing the Presenton Electron app on macOS.
|
||||
|
||||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [Direct distribution](./direct-distribution.md) | Developer ID signing, notarization, DMG verification, and release commands for distribution outside the Mac App Store |
|
||||
| [Mac App Store setup](./mac-app-store-setup.md) | Certificates, provisioning profiles, MAS build commands, and submission notes |
|
||||
|
||||
## Quick reference
|
||||
|
||||
All commands below run from the `electron/` directory on a Mac.
|
||||
|
||||
**First-time setup**
|
||||
|
||||
```bash
|
||||
cd electron
|
||||
npm run setup:env
|
||||
```
|
||||
|
||||
**Run locally (development)**
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**Build a DMG (local distribution, not App Store)**
|
||||
|
||||
```bash
|
||||
npm run build:all
|
||||
```
|
||||
|
||||
Output is written to `electron/dist/`. The default macOS target is a DMG built via `electron/build.js`.
|
||||
|
||||
For public releases outside the Mac App Store, use the signed and notarized direct distribution flow:
|
||||
|
||||
```bash
|
||||
export APPLE_KEYCHAIN_PROFILE="presenton-notary"
|
||||
npm run build:all:mac:signed
|
||||
```
|
||||
|
||||
That assumes the release Mac already has a **Developer ID Application** certificate and a stored `notarytool` profile named `presenton-notary`. See [Direct distribution](./direct-distribution.md) for the exact one-time setup and verification commands.
|
||||
|
||||
**Build for the Mac App Store**
|
||||
|
||||
See [Mac App Store setup](./mac-app-store-setup.md) for certificates, provisioning profiles, and signing. Summary:
|
||||
|
||||
```bash
|
||||
npm run build:all:mas-dev # development / TestFlight-style testing
|
||||
npm run build:all:mas # distribution / App Store submission
|
||||
```
|
||||
|
||||
## Related docs
|
||||
|
||||
- [Electron dependency strategy](../../electron-dependency-strategy.md) — bundled Chromium, ImageMagick, and export runtime
|
||||
- [Project README — Electron section](../../../README.md) — prerequisites and high-level build steps
|
||||
@@ -0,0 +1,300 @@
|
||||
# macOS Direct Distribution
|
||||
|
||||
Use this guide to ship Presenton as a signed and notarized macOS app outside the Mac App Store. This is the correct path for a downloadable DMG from GitHub Releases or presenton.ai.
|
||||
|
||||
This is not a Mac App Store build. Do not use MAS provisioning profiles, App Store Connect upload, or App Review for this flow.
|
||||
|
||||
If you are building from a Mac that is already registered in Apple Developer, that is fine, but the registration is not what makes the public DMG trusted. Registered devices are for development provisioning and MAS-style testing. Public direct distribution requires a Developer ID Application certificate and Apple notarization.
|
||||
|
||||
## What This Produces
|
||||
|
||||
The signed release build creates:
|
||||
|
||||
```text
|
||||
electron/dist/
|
||||
Presenton-<version>.dmg
|
||||
```
|
||||
|
||||
Users should be able to open the DMG and launch Presenton without macOS warning that the app is from an unidentified developer.
|
||||
|
||||
## How This Differs From MAS
|
||||
|
||||
| Area | Direct distribution | Mac App Store |
|
||||
|------|---------------------|---------------|
|
||||
| Certificate | Developer ID Application | Apple Distribution / 3rd Party Mac Developer |
|
||||
| Provisioning profile | Not used | Required |
|
||||
| App Sandbox | Not required by this build | Required |
|
||||
| Notarization | Required | App Store processing handles distribution review |
|
||||
| Output | DMG for download | PKG for App Store Connect |
|
||||
|
||||
The MAS guide in the referenced gist is still useful for the Apple Developer account and signing concepts, but this repo's direct distribution flow deliberately stops before MAS provisioning and App Store submission.
|
||||
|
||||
## Repository Configuration
|
||||
|
||||
Direct macOS distribution is configured in `electron/build.js`:
|
||||
|
||||
- `mac.hardenedRuntime` is enabled for non-MAS macOS builds.
|
||||
- `mac.entitlements` uses `electron/build/entitlements.mac.plist`.
|
||||
- `mac.entitlementsInherit` uses `electron/build/entitlements.mac.inherit.plist`.
|
||||
- `mac.notarize` is enabled unless `PRESENTON_SKIP_NOTARIZATION=1`.
|
||||
- `dmg.sign` is disabled because the app bundle is signed and notarized; signing the DMG itself is not required.
|
||||
|
||||
Use the release scripts in `electron/package.json`:
|
||||
|
||||
```bash
|
||||
npm run build:all:mac:signed
|
||||
npm run build:electron:mac:signed
|
||||
npm run dist:mac:signed
|
||||
```
|
||||
|
||||
The `:mac:signed` scripts set `PRESENTON_REQUIRE_MAC_SIGNING=1`, so they fail before packaging if a Developer ID certificate or notarization credentials are missing.
|
||||
|
||||
## Exact Setup For A Release Mac
|
||||
|
||||
Run this once on the Mac that will build releases.
|
||||
|
||||
### 1. Install Apple Command Line Tools
|
||||
|
||||
```bash
|
||||
xcode-select --install
|
||||
xcrun notarytool --version
|
||||
```
|
||||
|
||||
`notarytool` must be available. If it is missing, install or update Xcode.
|
||||
|
||||
### 2. Install The Developer ID Certificate
|
||||
|
||||
In Xcode:
|
||||
|
||||
1. Open **Xcode** -> **Settings** -> **Accounts**.
|
||||
2. Select the Apple Developer team.
|
||||
3. Click **Manage Certificates**.
|
||||
4. Click **+**.
|
||||
5. Select **Developer ID Application**.
|
||||
|
||||
Do not choose **Apple Development** for public distribution. Do not choose **Apple Distribution** unless you are building for the Mac App Store. For a downloadable DMG, the certificate must be **Developer ID Application**.
|
||||
|
||||
Confirm the certificate is visible to `codesign`:
|
||||
|
||||
```bash
|
||||
security find-identity -v -p codesigning | grep "Developer ID Application"
|
||||
```
|
||||
|
||||
Expected shape:
|
||||
|
||||
```text
|
||||
Developer ID Application: Your Company Name (TEAMID)
|
||||
```
|
||||
|
||||
Most release Macs only have one Developer ID Application certificate, so you usually do not need to export a signing identity. If multiple Developer ID certificates are installed, set the exact identity before building:
|
||||
|
||||
```bash
|
||||
export PRESENTON_MAC_SIGN_IDENTITY="Developer ID Application: Your Company Name (TEAMID)"
|
||||
```
|
||||
|
||||
### 3. Store Notarization Credentials Once
|
||||
|
||||
Create an app-specific password for the Apple ID, then store notarization credentials in the local Keychain:
|
||||
|
||||
```bash
|
||||
xcrun notarytool store-credentials "presenton-notary" \
|
||||
--apple-id "apple-id@example.com" \
|
||||
--team-id "TEAMID" \
|
||||
--password "app-specific-password"
|
||||
```
|
||||
|
||||
After that, the only notarization environment variable needed for normal local release builds is:
|
||||
|
||||
```bash
|
||||
export APPLE_KEYCHAIN_PROFILE="presenton-notary"
|
||||
```
|
||||
|
||||
To avoid exporting it manually every shell session, add it to your shell profile:
|
||||
|
||||
```bash
|
||||
echo 'export APPLE_KEYCHAIN_PROFILE="presenton-notary"' >> ~/.zshrc
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
If you store the profile in a non-default keychain, also set:
|
||||
|
||||
```bash
|
||||
export APPLE_KEYCHAIN="/path/to/keychain"
|
||||
```
|
||||
|
||||
Do not commit notarization credentials, app-specific passwords, `.p8` keys, or real certificates to the repo.
|
||||
|
||||
## Build A Signed DMG
|
||||
|
||||
After the one-time setup, a normal release build is:
|
||||
|
||||
```bash
|
||||
cd electron
|
||||
npm run build:all:mac:signed
|
||||
```
|
||||
|
||||
If `APPLE_KEYCHAIN_PROFILE` is not in your shell profile, run it inline:
|
||||
|
||||
```bash
|
||||
cd electron
|
||||
APPLE_KEYCHAIN_PROFILE="presenton-notary" npm run build:all:mac:signed
|
||||
```
|
||||
|
||||
For the very first build on a fresh checkout, run setup first:
|
||||
|
||||
```bash
|
||||
cd electron
|
||||
npm run setup:env
|
||||
npm run build:all:mac:signed
|
||||
```
|
||||
|
||||
If the app resources are already built and you only need to re-run Electron packaging:
|
||||
|
||||
```bash
|
||||
cd electron
|
||||
npm run dist:mac:signed
|
||||
```
|
||||
|
||||
The signed DMG is written to `electron/dist/`.
|
||||
|
||||
## What You Do Not Need
|
||||
|
||||
For public direct distribution, you do not need:
|
||||
|
||||
- A registered test device.
|
||||
- A `.provisionprofile` file.
|
||||
- `PRESENTON_MAS_DEV_IDENTITY`.
|
||||
- `PRESENTON_MAS_DISTRIBUTION_IDENTITY`.
|
||||
- `PRESENTON_APP_STORE_VERSION`.
|
||||
- App Store Connect upload.
|
||||
- App Review approval.
|
||||
|
||||
Those are MAS or development-provisioning concerns. The public DMG path is Developer ID signing plus notarization.
|
||||
|
||||
## Other Credential Options
|
||||
|
||||
The local Keychain profile above is the recommended flow for a human-operated release Mac. CI can use App Store Connect API keys instead:
|
||||
|
||||
```bash
|
||||
export APPLE_API_KEY="/secure/path/AuthKey_XXXXXXXXXX.p8"
|
||||
export APPLE_API_KEY_ID="XXXXXXXXXX"
|
||||
export APPLE_API_ISSUER="00000000-0000-0000-0000-000000000000"
|
||||
npm run build:all:mac:signed
|
||||
```
|
||||
|
||||
You can also pass Apple ID credentials directly, but this is less convenient than a stored Keychain profile:
|
||||
|
||||
```bash
|
||||
export APPLE_ID="apple-id@example.com"
|
||||
export APPLE_APP_SPECIFIC_PASSWORD="app-specific-password"
|
||||
export APPLE_TEAM_ID="TEAMID"
|
||||
npm run build:all:mac:signed
|
||||
```
|
||||
|
||||
## Verify The Release
|
||||
|
||||
Run these checks before publishing the DMG.
|
||||
|
||||
### 1. Check Code Signature
|
||||
|
||||
Replace the app path if the architecture-specific output folder differs.
|
||||
|
||||
```bash
|
||||
codesign --verify --deep --strict --verbose=2 "dist/mac/Presenton.app"
|
||||
codesign -dv --verbose=4 "dist/mac/Presenton.app" 2>&1 | grep -E "Authority|TeamIdentifier|Runtime"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `Authority=Developer ID Application: ...`
|
||||
- `TeamIdentifier=S6W5C54KL6`
|
||||
- Hardened Runtime is present.
|
||||
|
||||
### 2. Check Notarization Stapling
|
||||
|
||||
```bash
|
||||
xcrun stapler validate "dist/mac/Presenton.app"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
The validate action worked!
|
||||
```
|
||||
|
||||
### 3. Check Gatekeeper
|
||||
|
||||
```bash
|
||||
spctl --assess --type execute --verbose=4 "dist/mac/Presenton.app"
|
||||
spctl --assess --type open --verbose=4 "dist/Presenton-0.8.8-beta.dmg"
|
||||
```
|
||||
|
||||
Expected shape:
|
||||
|
||||
```text
|
||||
accepted
|
||||
source=Notarized Developer ID
|
||||
```
|
||||
|
||||
### 4. Test On Another Mac
|
||||
|
||||
Download the DMG on a Mac that did not build it, mount it, drag Presenton to `/Applications`, and launch it normally. This catches quarantine and Gatekeeper behavior that local build machines can hide.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**The signed release build says the Developer ID identity is missing**
|
||||
|
||||
Install a Developer ID Application certificate in Keychain Access, or set:
|
||||
|
||||
```bash
|
||||
export PRESENTON_MAC_SIGN_IDENTITY="Developer ID Application: Your Company Name (TEAMID)"
|
||||
```
|
||||
|
||||
**The build says notarization credentials are missing**
|
||||
|
||||
Set one complete credential group:
|
||||
|
||||
```bash
|
||||
export APPLE_KEYCHAIN_PROFILE="presenton-notary"
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
export APPLE_ID="apple-id@example.com"
|
||||
export APPLE_APP_SPECIFIC_PASSWORD="app-specific-password"
|
||||
export APPLE_TEAM_ID="TEAMID"
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
export APPLE_API_KEY="/secure/path/AuthKey_XXXXXXXXXX.p8"
|
||||
export APPLE_API_KEY_ID="XXXXXXXXXX"
|
||||
export APPLE_API_ISSUER="00000000-0000-0000-0000-000000000000"
|
||||
```
|
||||
|
||||
**macOS still says the app is damaged or cannot be opened**
|
||||
|
||||
Run the verification commands above. If stapling fails, rebuild with valid notarization credentials and do not publish the DMG until `spctl` reports `source=Notarized Developer ID`.
|
||||
|
||||
**You need a local unsigned build**
|
||||
|
||||
Use the generic build script instead of the signed release script:
|
||||
|
||||
```bash
|
||||
npm run build:all
|
||||
```
|
||||
|
||||
For release artifacts, always use:
|
||||
|
||||
```bash
|
||||
npm run build:all:mac:signed
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- Steve Crickmore's Electron MAS release gist: https://gist.github.com/steve981cr/def310670dfd9ed1439bf31cc734f941
|
||||
- Electron signing and notarization docs: https://www.electronjs.org/docs/latest/tutorial/code-signing
|
||||
- electron-builder notarization docs: https://www.electron.build/code-signing-mac.html#notarize
|
||||
- Apple notarization docs: https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution
|
||||
@@ -0,0 +1,201 @@
|
||||
# Mac App Store Setup
|
||||
|
||||
Presenton’s Electron app is configured for Mac App Store (MAS) distribution with App Sandbox. MAS builds must be produced on macOS because Apple’s signing tools are required.
|
||||
|
||||
This guide covers Apple Developer setup, local provisioning files, and the npm scripts in `electron/package.json` on `main`.
|
||||
|
||||
## Overview
|
||||
|
||||
| Target | Purpose | npm script |
|
||||
|--------|---------|------------|
|
||||
| `dmg` | Direct download outside the App Store | `npm run build:all:mac:signed` |
|
||||
| `mas-dev` | Sandbox testing on registered Macs | `npm run build:all:mas-dev` |
|
||||
| `mas` | App Store submission | `npm run build:all:mas` |
|
||||
|
||||
Build configuration lives in `electron/build.js`. Key identifiers:
|
||||
|
||||
- **Bundle ID:** `com.presenton.presenton`
|
||||
- **Team ID:** `S6W5C54KL6` (set in `extendInfo.ElectronTeamID`)
|
||||
|
||||
## MAS files in the repo
|
||||
|
||||
These are checked in under `electron/build/`:
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `entitlements.mas.plist` | Main app entitlements (sandbox, network, JIT, user-selected files) |
|
||||
| `entitlements.mas.inherit.plist` | Inherited entitlements for helper processes and bundled binaries |
|
||||
| `MacAppStore.provisionprofile.replace_me` | Placeholder — replace with your real App Store profile (see below) |
|
||||
|
||||
`build.js` validates provisioning profiles with `security cms -D` before building. Invalid or placeholder files will fail the build with a clear error.
|
||||
|
||||
## What you need from Apple
|
||||
|
||||
### 1. Apple Development certificate
|
||||
|
||||
Xcode → **Settings** → **Accounts** → your team → **Manage Certificates** → **+** → **Apple Development**.
|
||||
|
||||
Used for `mas-dev` builds and local sandbox testing.
|
||||
|
||||
### 2. Apple Distribution certificate
|
||||
|
||||
Same path as above, but choose **Apple Distribution**.
|
||||
|
||||
Used for `mas` (App Store submission) builds.
|
||||
|
||||
### 3. App ID (Bundle ID)
|
||||
|
||||
[Apple Developer Portal](https://developer.apple.com/account) → **Certificates, Identifiers & Profiles** → **Identifiers** → **App IDs**.
|
||||
|
||||
Create a macOS App ID that matches `com.presenton.presenton` (or update `APP_ID` in `electron/build.js` if you use a different ID).
|
||||
|
||||
### 4. Provisioning profiles
|
||||
|
||||
Create **two** macOS profiles and download each `.provisionprofile` file into `electron/build/`:
|
||||
|
||||
#### AppleDevelopment (MAS development)
|
||||
|
||||
- Portal → **Profiles** → **+**
|
||||
- Type: **macOS App Development**
|
||||
- App ID: your Presenton app ID
|
||||
- Certificate: **Apple Development**
|
||||
- Save as `electron/build/AppleDevelopment.provisionprofile`
|
||||
|
||||
`build.js` also accepts `AppleDev.provisionprofile` or `AppDev.provisionprofile` as fallbacks.
|
||||
|
||||
#### MacAppStore (MAS distribution)
|
||||
|
||||
- Portal → **Profiles** → **+**
|
||||
- Type: **Mac App Store**
|
||||
- App ID: your Presenton app ID
|
||||
- Certificate: **Apple Distribution**
|
||||
- Save as `electron/build/MacAppStore.provisionprofile`
|
||||
|
||||
Verify profiles decode correctly:
|
||||
|
||||
```bash
|
||||
cd electron
|
||||
security cms -D -i build/AppleDevelopment.provisionprofile
|
||||
security cms -D -i build/MacAppStore.provisionprofile
|
||||
```
|
||||
|
||||
Provisioning profiles expire (typically yearly). Regenerate and replace the local files when they do.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Used for | Description |
|
||||
|----------|----------|-------------|
|
||||
| `PRESENTON_MAC_TARGET` | Set by npm scripts | `mas-dev` or `mas` (do not set manually when using the scripts below) |
|
||||
| `PRESENTON_MAS_DEV_IDENTITY` | `mas-dev` | Signing identity, e.g. `Apple Development: Your Name (TEAMID)` |
|
||||
| `PRESENTON_MAS_DISTRIBUTION_IDENTITY` | `mas` | Distribution identity, e.g. `Apple Distribution: Your Org (TEAMID)` |
|
||||
| `PRESENTON_MAS_IDENTITY` | `mas` | Alias for distribution identity |
|
||||
| `CSC_NAME` | Either target | Fallback if the target-specific identity vars are unset |
|
||||
| `PRESENTON_APP_STORE_VERSION` | `mas` | App Store **version** (`x.y.z`). Required when `package.json` version contains a suffix like `-beta` |
|
||||
| `PRESENTON_APP_STORE_BUILD` | `mas` | App Store **build** number. Defaults to the short version if unset |
|
||||
|
||||
Example for a beta package version:
|
||||
|
||||
```bash
|
||||
export PRESENTON_APP_STORE_VERSION=1.0.0
|
||||
export PRESENTON_APP_STORE_BUILD=42
|
||||
```
|
||||
|
||||
## Build commands
|
||||
|
||||
Run from `electron/` after `npm run setup:env` (first time only).
|
||||
|
||||
### Full build (recommended)
|
||||
|
||||
Rebuilds Next.js, FastAPI, export runtime, and packages the app:
|
||||
|
||||
```bash
|
||||
# MAS development — runs on Macs in your dev provisioning profile
|
||||
PRESENTON_MAS_DEV_IDENTITY="Apple Development: Your Name (TEAMID)" \
|
||||
npm run build:all:mas-dev
|
||||
|
||||
# MAS distribution — for App Store Connect upload
|
||||
PRESENTON_MAS_DISTRIBUTION_IDENTITY="Apple Distribution: Your Org (TEAMID)" \
|
||||
PRESENTON_APP_STORE_VERSION=1.0.0 \
|
||||
npm run build:all:mas
|
||||
```
|
||||
|
||||
### Package only (resources already built)
|
||||
|
||||
If you already ran `build:all` and only need to re-run the Electron packager:
|
||||
|
||||
```bash
|
||||
PRESENTON_MAS_DEV_IDENTITY="Apple Development: Your Name (TEAMID)" \
|
||||
npm run dist:mac:mas-dev
|
||||
|
||||
PRESENTON_MAS_DISTRIBUTION_IDENTITY="Apple Distribution: Your Org (TEAMID)" \
|
||||
PRESENTON_APP_STORE_VERSION=1.0.0 \
|
||||
npm run dist:mac:mas
|
||||
```
|
||||
|
||||
### Direct DMG (not App Store)
|
||||
|
||||
For a signed and notarized macOS disk image that does not go through App Store approval:
|
||||
|
||||
```bash
|
||||
npm run build:all:mac:signed
|
||||
```
|
||||
|
||||
See [Direct distribution](./direct-distribution.md) for the Developer ID certificate, notarization, and verification flow.
|
||||
|
||||
Artifacts land in `electron/dist/`.
|
||||
|
||||
## Output locations
|
||||
|
||||
Exact paths depend on architecture (`arm64` vs `x64`). Typical layouts:
|
||||
|
||||
```text
|
||||
electron/dist/
|
||||
mas-dev-arm64/ # unsigned or dev-signed .app (mas-dev)
|
||||
mas-arm64/ # distribution-signed .app (mas)
|
||||
Presenton-<version>.pkg # MAS installer for upload
|
||||
Presenton-<version>.dmg # signed and notarized direct distribution DMG
|
||||
```
|
||||
|
||||
Upload the `.pkg` from the `mas` build to [App Store Connect](https://appstoreconnect.apple.com).
|
||||
|
||||
## Icons
|
||||
|
||||
The default macOS icon is `electron/resources/ui/assets/images/presenton_short_filled.png`.
|
||||
|
||||
For App Store packaging you can provide a proper `.icns` at `electron/build/icon.icns` and update the `mac.icon` field in `electron/build.js` if you want a custom store icon.
|
||||
|
||||
Source PNGs for generating an icon set are in `electron/build/icon.iconset/`.
|
||||
|
||||
## Testing notes
|
||||
|
||||
- **MAS-signed apps** require the MAS Electron build (`mas` or `mas-dev` targets). A normal DMG build is not sandbox-compatible in the same way.
|
||||
- **`mas-dev` builds** run only on Macs registered in your development provisioning profile.
|
||||
- **`mas` (distribution) builds** usually do not launch locally; they are meant for App Store Connect processing and release.
|
||||
- **`mas-dev` signing** uses `--timestamp=none` in `build.js` so local signing works without a network timestamp server.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**“Missing MAS development/distribution provisioning profile”**
|
||||
|
||||
Place the correct `.provisionprofile` in `electron/build/` (see [Provisioning profiles](#4-provisioning-profiles)).
|
||||
|
||||
**“macOS could not decode” a profile**
|
||||
|
||||
Re-download the profile from the Developer Portal. Do not commit real profiles to git.
|
||||
|
||||
**“Cannot derive an App Store version from package version”**
|
||||
|
||||
Set `PRESENTON_APP_STORE_VERSION` to three integers, e.g. `1.0.0`.
|
||||
|
||||
**“MAS builds must be run on macOS”**
|
||||
|
||||
Run the build on a Mac with Xcode command-line tools installed (`xcode-select --install`).
|
||||
|
||||
**FastAPI or export binary not executable inside the .app**
|
||||
|
||||
`build.js` runs an `afterPack` hook that sets executable permissions on bundled binaries. If signing fails afterward, check that entitlements allow the sandboxed app to execute helpers in `Contents/Resources/app/resources/`.
|
||||
|
||||
## See also
|
||||
|
||||
- [macOS dev README](./README.md) — quick reference for all macOS build targets
|
||||
- [Electron dependency strategy](../../electron-dependency-strategy.md) — what gets bundled in the app
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
## 2. Remove the runtime adapter path
|
||||
|
||||
From the Template V2 presentation flow, remove calls to:
|
||||
|
||||
- adaptTemplateV2LayoutToSlide
|
||||
- normalizeTemplateV2Slide
|
||||
- serializeTemplateV2LayoutFromSlide
|
||||
- serializeTemplateV2ContentFromSlide
|
||||
- applyGeneratedSlideContentToLayout
|
||||
- old normalized slide layout resolver
|
||||
|
||||
Changes in editor, to adpot the slide.ui /components/elements.
|
||||
@@ -0,0 +1,295 @@
|
||||
# Template V2 direct component rendering
|
||||
|
||||
This document describes the Template V2 presentation runtime on branch
|
||||
`dipesh/pre-159-move-new-editor-code-to-main-codebase-for-proper-integration`.
|
||||
|
||||
The governing rule is:
|
||||
|
||||
> Select a layout → write the complete layout to `slide.ui` → render `slide.ui`
|
||||
> directly → edit `slide.ui` → persist `slide.ui`.
|
||||
|
||||
`slide.ui` is the only frontend source of truth for Template V2 layout and
|
||||
content. The presentation renderer does not read Template V2 content from
|
||||
`slide.content` and does not maintain a second canonical `Slide` copy.
|
||||
|
||||
## Active flow
|
||||
|
||||
```text
|
||||
selected Template V2 layout
|
||||
|
|
||||
v
|
||||
slide.ui = raw 1280 x 720 layout JSON
|
||||
|
|
||||
v
|
||||
V1ContentRender
|
||||
|
|
||||
v
|
||||
TemplateV2KonvaSlide
|
||||
|
|
||||
+-- render raw ui.components with react-konva
|
||||
+-- edit an immutable, structurally shared raw UI tree
|
||||
+-- dispatch updateSlideUi({ index, ui })
|
||||
|
|
||||
v
|
||||
Redux presentationData.slides[index].ui
|
||||
|
|
||||
v
|
||||
debounced presentation autosave
|
||||
```
|
||||
|
||||
The presentation runtime no longer performs this conversion loop:
|
||||
|
||||
```text
|
||||
raw slide.ui
|
||||
-> canonical editor Slide
|
||||
-> layout resolver / SlideSurface
|
||||
-> reverse serialization
|
||||
-> slide.ui plus hidden slide.content state
|
||||
```
|
||||
|
||||
This removes redundant object construction, schema translation, Jotai editor
|
||||
state, layout resolution, and reverse serialization from the render/edit path.
|
||||
|
||||
## Primary implementation files
|
||||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| [`TemplateV2KonvaSlide.tsx`](../servers/nextjs/components/slide-editor/surface/TemplateV2KonvaSlide.tsx) | The preserved Dipesh component, now responsible for raw UI rendering, selection, transforms, existing element toolbars, inline editing, insertions, chart/image integration, history, and UI commits. |
|
||||
| [`V1ContentRender.tsx`](../servers/nextjs/app/(presentation-generator)/components/V1ContentRender.tsx) | Routes Template V2 presentation slides directly to the raw renderer. |
|
||||
| [`events.ts`](../servers/nextjs/components/slide-editor/events/events.ts) | Shared insert, chart, and active-surface event contracts. |
|
||||
| [`presentationGeneration.ts`](../servers/nextjs/store/slices/presentationGeneration.ts) | Owns `updateSlideUi`, which updates only the selected slide's `ui`. |
|
||||
| [`NewSlide.tsx`](../servers/nextjs/app/(presentation-generator)/presentation/components/NewSlide.tsx) | Writes a selected Template V2 layout directly into a new slide's `ui`. |
|
||||
| [`blank-slide.ts`](../servers/nextjs/app/(presentation-generator)/_shared/blank-slide.ts) | Supplies an empty raw UI with `components: []`. |
|
||||
| [`useAutoSave.tsx`](../servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx) | Persists the Redux presentation after its debounce. |
|
||||
|
||||
There are two `NewSlide.tsx` entry points in the presentation-generator tree.
|
||||
Both now use the same `slide.ui` behavior.
|
||||
|
||||
## Raw UI contract
|
||||
|
||||
The renderer consumes the backend Template V2 shape without converting units:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "layout_id",
|
||||
"background": "#FFFFFF",
|
||||
"components": [
|
||||
{
|
||||
"id": "hero",
|
||||
"position": { "x": 80, "y": 80 },
|
||||
"size": { "width": 1120, "height": 560 },
|
||||
"elements": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "title",
|
||||
"position": { "x": 0, "y": 0 },
|
||||
"size": { "width": 900, "height": 140 },
|
||||
"runs": [{ "text": "Title" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Geometry is interpreted in the native `1280 x 720` stage coordinate system:
|
||||
|
||||
- component positions are slide-relative;
|
||||
- top-level element positions are component-relative;
|
||||
- nested element positions are parent-relative;
|
||||
- positions, sizes, font sizes, gaps, padding, strokes, and radii remain raw
|
||||
UI values.
|
||||
|
||||
The loose raw boundary is intentional. Alias readers handle known snake-case
|
||||
and camel-case variants without rewriting the entire tree.
|
||||
|
||||
## Rendering and editing
|
||||
|
||||
`TemplateV2KonvaSlide` creates one fixed-size Konva Stage and renders
|
||||
each raw component as a clipped Group. Raw elements are traversed recursively
|
||||
through `children`, `elements`, `child`, or repeated `item` structures.
|
||||
|
||||
Supported visuals include:
|
||||
|
||||
- text and text lists;
|
||||
- images and SVG;
|
||||
- rectangles, ellipses, lines, groups, containers, flex, and grid;
|
||||
- tables;
|
||||
- bar, line, area, and pie-style charts;
|
||||
- infographic/progress primitives.
|
||||
|
||||
Interaction behavior:
|
||||
|
||||
- components can be selected, dragged, resized, and rotated;
|
||||
- a selected element can be dragged, resized, and rotated;
|
||||
- the first pointer interaction on an unselected child continues to select or
|
||||
move its component; a selected child receives its own drag;
|
||||
- layout-managed children receive `__presenton_manual_position` after a manual
|
||||
transform so the layout engine does not immediately overwrite the edit;
|
||||
- text, text-list, table, and SVG values use inline overlays;
|
||||
- images use the upload API and save the returned source in the raw element;
|
||||
- charts use the shared chart-panel event bridge;
|
||||
- the existing `ElementToolbar` controls are retained as a selected-element
|
||||
editor boundary; only that selected element is projected into editor units,
|
||||
while rendering and persistence remain raw `slide.ui` operations;
|
||||
- component-level design-variable controls are retained through the same
|
||||
editor boundary and write their changes back into the raw component tree;
|
||||
- undo and redo store raw UI snapshots, not canonical editor Slides.
|
||||
|
||||
## Backend-shape compatibility
|
||||
|
||||
The renderer reads and preserves the backend's current data shapes:
|
||||
|
||||
- TextList items may be arrays of text runs, direct text records, or legacy
|
||||
strings.
|
||||
- Table cells may contain `runs`, a direct `text`, and either `color` or `fill`.
|
||||
Editing preserves the existing cell and first-run style metadata.
|
||||
- Chart series read `values`, with `data` accepted as a compatibility alias.
|
||||
|
||||
This matters because a direct renderer must not silently depend on the canonical
|
||||
editor adapter to normalize these values first.
|
||||
|
||||
## Commit and persistence behavior
|
||||
|
||||
Every edit ends at one mutation boundary:
|
||||
|
||||
```ts
|
||||
updateSlideUi({
|
||||
index: slideIndex,
|
||||
ui: nextUi,
|
||||
})
|
||||
```
|
||||
|
||||
The reducer changes only:
|
||||
|
||||
```text
|
||||
presentationData.slides[slideIndex].ui
|
||||
```
|
||||
|
||||
It does not replace the whole slide and does not write an editor snapshot into
|
||||
`slide.content`. The existing autosave observes the Redux presentation object
|
||||
and persists the updated `ui` after its debounce.
|
||||
|
||||
New Template V2 slides are initialized as follows:
|
||||
|
||||
```text
|
||||
layout selection -> newSlide.ui = selected raw layout
|
||||
empty selection -> newSlide.ui = { background: "#FFFFFF", components: [] }
|
||||
```
|
||||
|
||||
`content: {}` is currently retained on newly created slides for the shared API
|
||||
shape, but it is not a Template V2 rendering or editing source.
|
||||
|
||||
## Adapter cleanup status
|
||||
|
||||
The active Template V2 presentation runtime has no calls to:
|
||||
|
||||
- `adaptTemplateV2LayoutToSlide`;
|
||||
- `normalizeTemplateV2Slide`;
|
||||
- `serializeTemplateV2LayoutFromSlide`;
|
||||
- `serializeTemplateV2ContentFromSlide`;
|
||||
- `applyGeneratedSlideContentToLayout`;
|
||||
- the old normalized slide layout resolver.
|
||||
|
||||
`TemplateV2KonvaSlide` was preserved and changed in place. Its old whole-slide
|
||||
adapter, Jotai deck hydration, `SlideSurface` route, reverse serializers, and
|
||||
hidden `__template_v2_konva_slide__` content fallback were removed.
|
||||
|
||||
Some similarly named utilities remain for separate consumers:
|
||||
|
||||
- `TemplateV2LayoutPreview` still uses `adaptTemplateV2LayoutToSlide` to feed
|
||||
the shared preview surface.
|
||||
- The reusable slide editor still uses `normalizeTemplateV2Slide` for canonical
|
||||
`Slide` model conversion where needed.
|
||||
- The Blocks/palette boundary can still convert a selected library component
|
||||
into an insertion payload. This conversion occurs only on insertion, not on
|
||||
every render or edit.
|
||||
|
||||
Deleting these shared utilities globally would break template previews and the
|
||||
standalone canonical editor without improving presentation-render performance.
|
||||
They are not mounted in the direct presentation path.
|
||||
|
||||
## Backend generation boundary
|
||||
|
||||
The FastAPI generator currently creates semantic `slide.content` and then calls
|
||||
`_apply_template_v2_content_to_ui()` during generation/streaming. The result is
|
||||
a hydrated `slide.ui`, which is what the frontend renders and persists.
|
||||
|
||||
That server operation is generation-time work, not a browser render adapter.
|
||||
The stricter final architecture should generate named element values directly
|
||||
into a copied UI tree and keep `slide.content` empty for Template V2. Removing
|
||||
the server hydrator before replacing the generator output would produce blank
|
||||
template values, so it is intentionally outside this frontend runtime cleanup.
|
||||
|
||||
## Performance analysis
|
||||
|
||||
The direct path is structurally cheaper than the old presentation path because
|
||||
it removes full-tree model conversion and reverse conversion from mounts and
|
||||
edits. It also avoids initializing the standalone slide editor's state graph for
|
||||
each presentation surface.
|
||||
|
||||
The editor hot path is optimized as follows:
|
||||
|
||||
- the incoming immutable UI becomes the initial draft by reference; no
|
||||
full-tree startup clone is performed;
|
||||
- edits use structural sharing and copy only the UI/component/element path that
|
||||
changed;
|
||||
- unchanged components, nested elements, and visual nodes are memoized;
|
||||
- undo/redo stores immutable UI references in a bounded 50-entry history
|
||||
instead of deep-cloning the complete UI for every edit;
|
||||
- inline typing keeps its draft inside the textarea component, so keystrokes do
|
||||
not rerender the Stage;
|
||||
- repeated `item` rendering reuses the immutable source object rather than
|
||||
cloning it per occurrence and render;
|
||||
- view-only Layers disable Konva hit testing;
|
||||
- global deck history runs at the debounced autosave boundary and stores
|
||||
immutable structurally shared snapshots instead of cloning/stringifying the
|
||||
complete deck on every editor commit;
|
||||
- autosave change detection uses Redux object identity rather than serializing
|
||||
the complete presentation merely to compare it.
|
||||
|
||||
Costs that remain include redraw of the affected Konva Layer,
|
||||
selected-element toolbar projection, and request serialization performed by the
|
||||
API client when autosave executes.
|
||||
|
||||
The next performance work should be evidence-driven:
|
||||
|
||||
1. Profile initial render, drag-end commit, inline-edit commit, and autosave on a
|
||||
large real deck.
|
||||
2. Split unusually large slides across carefully measured Konva Layers if canvas
|
||||
redraw is still a bottleneck after React subtree memoization.
|
||||
3. Move autosave comparison/persistence to slide-level patches if full-deck
|
||||
JSON serialization is measurable.
|
||||
4. Add browser performance tests before claiming a specific speedup.
|
||||
|
||||
The architecture eliminates known redundant work, but “blazing fast” still
|
||||
requires measurements on representative decks.
|
||||
|
||||
## Verification checklist
|
||||
|
||||
For a Template V2 slide:
|
||||
|
||||
1. Confirm selecting a normal or blank layout immediately creates `slide.ui`.
|
||||
2. Confirm `V1ContentRender` passes only `slide.ui` plus slide identity to the
|
||||
direct renderer.
|
||||
3. Render text, TextList, table, image, chart, flex, grid, and nested elements.
|
||||
4. Drag, resize, and rotate both a component and a selected element.
|
||||
5. Edit text/list/table values and verify their raw source shape is preserved.
|
||||
6. Insert a palette element and a reusable block.
|
||||
7. Replace an image and update a chart.
|
||||
8. Undo/redo, reload, and confirm the saved values remain in `slide.ui`.
|
||||
9. Confirm the presentation runtime does not create
|
||||
`__template_v2_konva_slide__` in `slide.content`.
|
||||
10. Confirm template previews and the standalone slide editor still work,
|
||||
because their adapters were deliberately retained.
|
||||
|
||||
## Rules to preserve
|
||||
|
||||
1. Treat `slide.ui` as the only Template V2 frontend source of truth.
|
||||
2. Do not add a renderer-side merge from `slide.content`.
|
||||
3. Do not convert the whole UI to a canonical `Slide` for presentation display.
|
||||
4. Preserve unknown raw fields while editing known fields.
|
||||
5. Keep raw geometry in one coordinate system.
|
||||
6. Keep preview/editor compatibility code outside the presentation runtime.
|
||||
7. Persist through `updateSlideUi` and the existing autosave boundary.
|
||||
8. Benchmark before adding caching complexity.
|
||||
Reference in New Issue
Block a user