Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,34 @@ launch).

## [Unreleased]

No unreleased changes yet.
### Added

- Self-contained recipe URL share links that import and select a recipe from
`?share=...#library`, excluding structured reasoning from the shared payload.
- Recipe genealogy display in the detail metadata for recipes derived from a
parent recipe.
- macOS WebUSB camera release runbook covering `ptpcamerad`, `icdd`, stale
Chromium WebUSB sessions, clean-profile recovery, restore commands, and
verification steps.

### Fixed

- macOS camera claim-collision recovery now points at both macOS camera daemons
and stale browser sessions instead of only Image Capture.
- macOS setup retry now reopens the WebUSB picker instead of silently reusing a
stale paired device.
- macOS setup copy now includes a camera power-cycle step, and advanced setup
suspends live camera daemons because `launchctl disable` can leave existing
processes running.
- Optional local macOS camera helper can expose daemon status plus one-click
release/restore actions to the setup wizard during hardware QA.
- When macOS camera services are left disabled/suspended, a compact restore
control remains available outside the setup wizard.
- Failed WebUSB connect attempts now clean up partially opened transports or raw
USB devices.
- Camera slot reads now emit partial results and slot-level failures, with a
timeout for stuck slots instead of leaving the UI indefinitely in the scanning
state.

## [0.1.0] - 2026-05-05

Expand Down
101 changes: 100 additions & 1 deletion PROGRESS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,109 @@
# Progress

> **Note:** This project was named *FilmFork* through 2026-05-04. References
> **Note:** This project was named _FilmFork_ through 2026-05-04. References
> to "FilmFork" or `@filmfork/*` in entries below describe the project's
> prior name; the current name is **Latent** and packages are `@latent/*`.
> See `CHANGELOG.md` for the rename entry.

## 2026-05-06 — Phase 6 polish pass

Phase 6 moved forward from general polish into concrete recipe portability
and genealogy features.

- Added self-contained recipe URL share via `?share=...#library`. Opening a
shared URL imports and selects the shared recipe locally.
- URL share excludes structured `reasoning` by default, preserving the V1
privacy rule that reasoning stays out of share links unless explicitly
exported elsewhere.
- Added recipe genealogy display in the detail metadata. Creator duplicates
already preserve `parentRecipeId`; the UI now shows the parent recipe name
when it is available locally, or a shortened parent id when it is not.
- Added targeted tests for share encode/decode, URL import, share-link copy,
and parent metadata display.
- Added a Phase 6 polish plan tracking remaining WCAG 2.2 AA and production
CSP validation.

Remaining Phase 6 work:

- Run the WCAG 2.2 AA audit on a production-like build.
- Validate final CSP headers once Phase 7 chooses the portal hosting target.
- Smoke test URL share on the deployed portal.

## 2026-05-06 — X-S20 macOS WebUSB release fix

Hardware validation found a macOS/WebUSB claim-collision path where the camera
appeared in the browser picker but Latent could not claim the PTP interface.
The original recovery path was too narrow: it implied Image Capture alone,
recommended only `killall ptpcamerad`, and could reuse a stale paired WebUSB
device after the setup flow.

- Confirmed the effective release path on X-S20 FW 3.30: handle both
`ptpcamerad` and `icdd`, reopen the WebUSB picker, and use a clean Chrome
profile when the normal profile holds stale WebUSB state.
- Reproduced the claim bug after re-enabling `ptpcamerad` and `icdd`.
`launchctl disable` marked the services disabled but left live processes
running; suspending the live daemons plus a full camera power-cycle/battery
reseat cleared the stale PTP session.
- Added an optional localhost macOS camera helper for hardware QA. When started
with `npm run macos-camera-helper`, the setup wizard can read daemon status
and run release/restore actions from the web UI.
- Moved the post-release restore affordance into a compact portal so users can
close the setup panel without losing the ability to restore macOS services.
- Updated the app-side macOS wizard and copy to cover macOS services and stale
browser sessions instead of blaming only Image Capture.
- Updated the connection manager so macOS setup retries force a picker reopen
with `autoSelectPaired: false`.
- Hardened failed WebUSB connection cleanup so partially opened transports/raw
devices are closed.
- Hardened preset reads so the UI receives partial results, slot-level
failures, and a timeout for stuck reads instead of staying indefinitely on
"Reading custom slots from the camera."
- Documented the incident, commands, restore path, app boundary, and test
checklist in
[`docs/qa/macos-webusb-camera-release.md`](./docs/qa/macos-webusb-camera-release.md).
- Follow-up: an X-T20 can now reach the connected/no-slots state, which should
be investigated as a legacy model preset-read capability issue rather than a
macOS release failure.

## 2026-05-05 — Latent 0.1.0 hardware-backed alpha

Latent `0.1.0` was tagged after the rename, repo flattening, camera-flow
work, RAF preview work, launch documentation, and UI polish. This makes Phase
4 alpha-complete, advances Phase 6, and starts Phase 7 launch work.

- Camera connection stability moved into `@latent/camera-connection`, with
explicit connection states, structured error classification, stale-session
cleanup, reconnect handling, and macOS PTP claim-collision guidance.
- Recipe library expanded beyond the Phase 3 shell: import/export, delete,
factory default restore, camera imports, deduplication, rename persistence,
and local-only storage behavior are implemented and tested.
- Recipe creator is implemented in the web app: users can start from
photographic intents, duplicate an existing look, edit schema-backed Fuji
settings, save into the local library, and export validated JSON without
connecting hardware.
- Custom-slot flows are implemented for the verified field set: read camera
C1-C4 presets, import them as recipes/backups, write recipes to selected
slots, verify writes, and restore previously imported backups.
- RAF preview workspace is implemented: local RAF files can be rendered
through the connected camera, with diagnostic parameter-group renders for
investigating camera-output mismatches.
- Public OSS launch materials are in place: README/README.it, screenshots,
onboarding, use cases, hardware test plan, launch checklist, governance
files, GitHub templates, funding metadata, and FilmKit relationship docs.
- Hands-on hardware validation has focused on X-S20 and X-M5. Other Fujifilm
bodies remain community-report territory until the hardware checklist is
run against them.

Remaining V1 work after `0.1.0`:

- Phase 5: replace the `@latent/ai-agent` stub with the real AI helper, or
explicitly defer it from the public V1 launch.
- Phase 6: finish WCAG 2.2 AA audit and production CSP validation.
- Phase 7: finish ADRs, trademark review, final seed list, release checklist,
and deploy path.
- Hardware QA: collect repeatable reports beyond X-S20/X-M5 and keep the
write whitelist narrow until fields are proven on real bodies.

## 2026-05-04 — Rename to Latent + repo restructure

End-of-session bookkeeping: project renamed from FilmFork to Latent,
Expand Down
36 changes: 30 additions & 6 deletions README.it.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ nelle ricette sono marchi dei rispettivi proprietari.

## Cosa fa oggi

- **Libreria ricette:** cerca, importa, esporta, elimina, ripristina i default
e conserva tutto localmente nel browser.
- **Libreria ricette:** cerca, importa, esporta, condivide link, elimina,
ripristina i default e conserva tutto localmente nel browser.
- **Creator ricette:** parte da intenti fotografici, duplica look esistenti
mantenendo metadati di parentela, modifica controlli Fujifilm validati dallo
schema ed esporta JSON senza hardware collegato.
- **Connessione camera:** si collega via WebUSB su browser Chromium, recupera
da refresh/unplug/sleep e guida l'utente quando macOS prende il controllo
dell'interfaccia PTP.
Expand Down Expand Up @@ -90,14 +93,33 @@ Dove Latent deve andare:
nvm use # Node 22, pinnato dal repo
npm install
npm run validate # typecheck + lint + test + licenze + lockstep
cd apps/web
npm run dev # Vite su http://localhost:5173
npm run dev:macos # Web app + helper locale macOS per la camera
```

Apri `http://localhost:5173` in Chrome, Edge o Arc. WebUSB funziona solo su
Apri `http://127.0.0.1:5173/` in Chrome, Edge o Arc. WebUSB funziona solo su
`localhost` o HTTPS. La libreria funziona senza hardware; i flussi camera
richiedono un corpo Fujifilm in modalita' USB/PTP.

`npm run dev:macos` avvia i due processi locali necessari per lo sviluppo
hardware su macOS:

- `http://127.0.0.1:5173/` - app web Vite.
- `http://127.0.0.1:5174/` - helper locale macOS per la camera.

L'helper e' volutamente locale. Il browser non puo' eseguire `launchctl`,
sospendere `ptpcamerad`/`icdd` o ripristinare i servizi camera di macOS da
solo, quindi Latent usa un helper su localhost per esporre nell'app azioni
esplicite di Release e Restore. Ferma lo stack con `Ctrl+C`; se durante il
test hai rilasciato i servizi camera macOS, usa il controllo Restore nell'app
prima di scollegare tutto.

Per lavoro solo browser, senza controllo dei servizi camera, puoi ancora
avviare direttamente l'app:

```bash
npm --workspace @latent/web run dev -- --host 127.0.0.1
```

## Note hardware

Durante lo sviluppo sono stati provati Fujifilm X-S20 e X-M5. Altri corpi
Expand All @@ -114,7 +136,9 @@ Prima di scrivere sulla camera:
prendere l'interfaccia PTP.

La checklist manuale e' in
[`docs/qa/hardware-test-plan.md`](./docs/qa/hardware-test-plan.md).
[`docs/qa/hardware-test-plan.md`](./docs/qa/hardware-test-plan.md). Le note
per sbloccare collisioni macOS/Image Capture/WebUSB sono in
[`docs/qa/macos-webusb-camera-release.md`](./docs/qa/macos-webusb-camera-release.md).

## Casi d'uso

Expand Down
36 changes: 28 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ trademarks of their respective owners.

## What Latent Does Today

- **Recipe library:** browse, search, import, export, delete, restore factory
defaults, and keep everything local in the browser.
- **Recipe library:** browse, search, import, export, share links, delete,
restore factory defaults, and keep everything local in the browser.
- **Recipe creator:** start from photographic intents, duplicate an existing
look, edit schema-backed Fujifilm settings, and export a validated JSON
recipe without connecting hardware.
look with parent metadata, edit schema-backed Fujifilm settings, and export a
validated JSON recipe without connecting hardware.
- **Camera connection:** connect over WebUSB in Chromium browsers, recover
from refresh/unplug/camera sleep, and surface macOS claim-collision guidance.
- **Custom slots:** read camera-side C1-C4 recipes, import them into the
Expand Down Expand Up @@ -105,14 +105,32 @@ Where Latent should go next:
nvm use # Node 22, pinned by the repo
npm install
npm run validate # typecheck + lint + tests + license + lockstep
cd apps/web
npm run dev # Vite at http://localhost:5173
npm run dev:macos # Web app + local macOS camera helper
```

Open `http://localhost:5173` in Chrome, Edge, or Arc. WebUSB works only on
Open `http://127.0.0.1:5173/` in Chrome, Edge, or Arc. WebUSB works only on
`localhost` or HTTPS. The recipe library works without hardware; camera flows
need a Fujifilm body set to USB/PTP mode.

`npm run dev:macos` starts the two local processes needed for hardware-backed
macOS development:

- `http://127.0.0.1:5173/` - Vite web app.
- `http://127.0.0.1:5174/` - local macOS camera helper.

The helper is intentionally local. Browsers cannot run `launchctl`, suspend
`ptpcamerad`/`icdd`, or restore macOS camera services on their own, so Latent
uses a localhost helper to expose explicit Release and Restore actions in the
app. Stop the stack with `Ctrl+C`; if you released macOS camera services during
testing, use the in-app Restore control before disconnecting.

For browser-only work that does not need camera service control, you can still
run the app directly:

```bash
npm --workspace @latent/web run dev -- --host 127.0.0.1
```

## Hardware Notes

Development has been exercised with Fujifilm X-S20 and X-M5 bodies. Other
Expand All @@ -129,7 +147,9 @@ Before writing to a camera:
claim the PTP interface.

Manual release checks live in
[`docs/qa/hardware-test-plan.md`](./docs/qa/hardware-test-plan.md).
[`docs/qa/hardware-test-plan.md`](./docs/qa/hardware-test-plan.md). macOS
Image Capture/WebUSB release notes live in
[`docs/qa/macos-webusb-camera-release.md`](./docs/qa/macos-webusb-camera-release.md).

## Use Cases

Expand Down
50 changes: 35 additions & 15 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Latent V1 Roadmap

| Phase | Output | Status |
| ------ | --------------------------------------------------------------- | ----------- |
| 1 | Foundation & core packages | ✅ Complete |
| 2-min | WebUSB transport (no hardware) | ✅ Complete |
| 2-full | X-S20 validation rig + first real round-trip | ✅ Complete |
| 3-base | Web app shell + recipe library + connect button | ✅ Complete |
| 4 | Camera flows (read/write slots, backup restore, RAF preview) | In progress |
| 5 | AI agent (5 modes, iteration loop, EXIF strip) | Planned |
| 6 | Polish (responsive UI, URL share, genealogy, export, WCAG, CSP) | In progress |
| 7 | Launch (ADRs, TM search, seed list, deploy) | Planned |
| Phase | Output | Status |
| ------ | --------------------------------------------------------------- | ----------------- |
| 1 | Foundation & core packages | ✅ Complete |
| 2-min | WebUSB transport (no hardware) | ✅ Complete |
| 2-full | X-S20 validation rig + first real round-trip | ✅ Complete |
| 3-base | Web app shell + recipe library + connect button | ✅ Complete |
| 4 | Camera flows (read/write slots, backup restore, RAF preview) | ✅ Alpha complete |
| 5 | AI agent (5 modes, iteration loop, EXIF strip) | Planned |
| 6 | Polish (responsive UI, URL share, genealogy, export, WCAG, CSP) | In progress |
| 7 | Launch (ADRs, TM search, seed list, online portal, deploy) | In progress |

The full V1 design lives in
[`docs/specs/2026-05-03-fujicomp-v1-design.md`](./docs/specs/2026-05-03-fujicomp-v1-design.md)
Expand All @@ -18,8 +18,28 @@ authoritative). Phase plans are in [`docs/plans/`](./docs/plans/).

## Current Launch Focus

- Stabilize the X-S20 and X-M5 WebUSB workflows with real hardware.
- Keep custom-slot writes limited to verified fields.
- Improve RAF preview parity by validating parameter groups against camera
output.
- Prepare public OSS onboarding, use cases, and hardware-report paths.
- Keep X-S20 and X-M5 WebUSB workflows stable while collecting more hardware
reports from other Fujifilm bodies.
- Keep custom-slot writes limited to verified fields, with backup, read-back,
and restore paths treated as part of the write workflow.
- Improve RAF preview parity by validating remaining parameter groups against
camera output, especially known Kelvin/white-balance limitations.
- Finish Phase 6 polish items that are not yet complete: WCAG 2.2 AA audit and
production CSP validation.
- Finish Phase 7 launch items: ADRs, trademark review, final seed list,
dedicated online portal, release checklist, and deploy path.
- Decide the portal hosting target. Vercel is likely the fastest static/PWA
launch path; GCP is better if Latent needs tighter infrastructure control,
custom observability, or future server-side services.
- Decide whether Phase 5 AI agent remains in V1 scope or moves behind the
post-alpha launch line.

## Known Hardware Follow-Ups

- macOS release can be automated locally for `ptpcamerad`/`icdd`, but a stale
camera-side PTP session may still require full body power-cycle or battery
reseat. A future native transport/helper should investigate whether a lower
level USB reset can replace the physical battery step.
- X-T20 currently reaches the connected/no-readable-slots state; investigate
whether legacy custom settings expose different PTP operations, slot counts,
or property mappings before widening model support.
15 changes: 15 additions & 0 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import { CameraRecipesPanel } from "./components/camera/CameraRecipesPanel";
import { RawPreviewPanel } from "./components/camera/RawPreviewPanel";
import { useCameraStore } from "./stores/camera";
import { useT } from "./i18n";
import { decodeRecipeShareFromLocation } from "./lib/recipe-share";

type Workspace = "camera" | "raf" | "library" | "create";

export function App(): JSX.Element {
const t = useT();
const loadSeedRecipes = useRecipesStore((s) => s.loadSeedRecipes);
const importRecipe = useRecipesStore((s) => s.importRecipe);
const loaded = useRecipesStore((s) => s.loaded);
const selectedId = useRecipesStore((s) => s.selectedRecipeId);
const recipes = useRecipesStore((s) => s.recipes);
const cameraState = useCameraStore((s) => s.state);
Expand All @@ -24,11 +27,23 @@ export function App(): JSX.Element {
const [workspace, setWorkspace] = useState<Workspace>(() => initialWorkspace());
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mobileOverviewOpen, setMobileOverviewOpen] = useState(false);
const [shareImportHandled, setShareImportHandled] = useState(false);

useEffect(() => {
void loadSeedRecipes();
}, [loadSeedRecipes]);

useEffect(() => {
if (!loaded || shareImportHandled) return;
setShareImportHandled(true);
try {
const sharedRecipe = decodeRecipeShareFromLocation(window.location);
if (sharedRecipe) importRecipe(sharedRecipe);
} catch {
// Invalid shared URLs should never block the local library.
}
}, [importRecipe, loaded, shareImportHandled]);

useEffect(() => {
localStorage.setItem("latent-theme-v1", theme);
}, [theme]);
Expand Down
Loading
Loading