Compare commits
11 Commits
f72a828805
...
286adf2d6c
| Author | SHA1 | Date | |
|---|---|---|---|
| 286adf2d6c | |||
| bc467c8a8c | |||
| 12f3e74d39 | |||
| 767f97f3bb | |||
| b5c3ada57d | |||
| 97ef0be216 | |||
| e01b59c065 | |||
| 1c7e18d7c1 | |||
| 46c7ce2824 | |||
| 1382f1ac1a | |||
| 1c9b9fd51a |
@@ -22,4 +22,5 @@ Use this skill for standups, daily scrum updates, end-of-day summaries, and shor
|
||||
- Use explicit work-item ID and title when available.
|
||||
- Keep the report concise and ready to send.
|
||||
- Do not mention internal evidence sources unless the user asks.
|
||||
- For Mattermost-ready standups, include a visible blank line before section headers such as `Today:` and `Blockers:`. Always verify there is an empty line immediately above `Today:` before returning the draft.
|
||||
- Use `Blockers: None` only when no blocker is visible in current memory.
|
||||
|
||||
@@ -140,4 +140,5 @@ Return a standup that is:
|
||||
- uses `JIRA-ID - Title` or `JIRA-ID Title` formatting instead of comma-separated ID/title formatting
|
||||
- preserves chronological order within each Jira item's sub-bullets
|
||||
- omits future-sprint stories from `Today` unless they are real blockers
|
||||
- includes a visible blank line before `Today:` and before `Blockers:` when present; before returning, verify there is an empty line immediately above each section header so Mattermost renders it correctly
|
||||
- is ready to copy/paste into Mattermost as Markdown
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,6 +11,8 @@ __pycache__/
|
||||
ai/inbox/mattermost-latest.md
|
||||
ai/inbox/mattermost-*.md
|
||||
ai/inbox/mattermost-status.json
|
||||
ai/inbox/photos/*
|
||||
!ai/inbox/photos/.gitkeep
|
||||
|
||||
# Workspace-local Mattermost runtime artifacts
|
||||
scripts/mattermost/.env
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
type: agent-integration
|
||||
status: active
|
||||
updated: 2026-04-17
|
||||
updated: 2026-05-08
|
||||
tags:
|
||||
- communication
|
||||
- evidence
|
||||
@@ -27,6 +27,7 @@ Mattermost is the current live communication connector.
|
||||
- Treat document review, message polishing, translation, and "does this align with Jeff's expectations?" prompts as normal drafting tasks unless the user explicitly asks for the latest message or fresh Mattermost evidence.
|
||||
- The OpenCode plugin syncs automatically only for explicit latest-message requests by default.
|
||||
- Optional aggressive sync can be enabled with `AIW_MATTERMOST_SYNC_ON_SESSION=true` or `AIW_MATTERMOST_SYNC_ON_PROMPT=true`, but these should stay off for low-latency daily use.
|
||||
- When invoking Mattermost sync from OpenCode, do not use parameter expansion that places a command with spaces into a single shell word, such as `${VAR:-bash scripts/mattermost/sync.sh}`. Run configured command strings via `bash -lc "$AIW_MATTERMOST_SYNC_CMD"` / `bash -lc "$FIDELITY_MATTERMOST_SYNC_CMD"`, and run the fallback as separate words: `bash scripts/mattermost/sync.sh`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
77
agent-memory/integrations/mem9.md
Normal file
77
agent-memory/integrations/mem9.md
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
type: agent-integration
|
||||
status: proposed
|
||||
updated: 2026-05-05
|
||||
tags:
|
||||
- memory
|
||||
- integration
|
||||
- mem9
|
||||
---
|
||||
# mem9 Integration
|
||||
|
||||
## Goal
|
||||
|
||||
Use mem9 as a cross-session, cross-agent recall layer without replacing the workspace's auditable Markdown memory.
|
||||
|
||||
## Source Of Truth
|
||||
|
||||
`project-knowledge/` remains the canonical human-readable Fidelity project memory.
|
||||
|
||||
`agent-memory/` remains the canonical operating memory for agent behavior, workflows, promotion rules, and integration guidance.
|
||||
|
||||
mem9 should recall and surface relevant facts, but durable project updates must still be promoted into the correct Markdown file when they become canonical.
|
||||
|
||||
## Recommended Scope
|
||||
|
||||
Use a project-scoped mem9 profile for this workspace when possible so Fidelity context, agent behavior, and personal/global memories do not collapse into one undifferentiated pool.
|
||||
|
||||
Do not bulk-import raw inboxes, generated sync output, full chat transcripts, or broad project folders by default. Import only curated facts, stable preferences, and distilled summaries.
|
||||
|
||||
## What To Store
|
||||
|
||||
- Stable user/workspace preferences that should survive sessions.
|
||||
- Durable agent behavior rules that are useful across Codex/OpenCode sessions.
|
||||
- Verified project facts already present in canonical Markdown.
|
||||
- Short summaries of current work that help recall where to begin.
|
||||
|
||||
## What Not To Store
|
||||
|
||||
- Secrets, credentials, API keys, tokens, auth cookies, or private environment values.
|
||||
- Raw Mattermost/Slack inbox dumps.
|
||||
- Fidelity product source code or proprietary implementation snippets.
|
||||
- Unverified assumptions, temporary sync failures, or tooling chatter.
|
||||
- Duplicates of every daily note without curation.
|
||||
|
||||
## Efficient Use Pattern
|
||||
|
||||
1. Recall mem9 early for user preferences and relevant workspace history.
|
||||
2. Load hot Markdown context from `AGENTS.md` / `opencode.json` instructions.
|
||||
3. For current-work questions, still inspect `project-knowledge/01-current/current-work.md` and the latest relevant daily note.
|
||||
4. Answer or act from verified context.
|
||||
5. When the interaction adds durable knowledge, update canonical Markdown first; store a compact mem9 memory only if it improves future recall.
|
||||
|
||||
## Codex Setup Notes
|
||||
|
||||
The upstream mem9 Codex integration uses a marketplace plugin and managed hooks. `$mem9:setup` is the primary setup command. It manages shared profiles, credentials, user/project scope, and hook repair.
|
||||
|
||||
Expected local surfaces include:
|
||||
|
||||
- `$CODEX_HOME/hooks.json`
|
||||
- `$CODEX_HOME/config.toml`
|
||||
- `$CODEX_HOME/mem9/`
|
||||
- `$MEM9_HOME/.credentials.json`
|
||||
- `<project>/.codex/mem9/config.json` for project overrides
|
||||
|
||||
Keep `$MEM9_HOME/.credentials.json` outside the repository and never commit API keys.
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- Confirm Codex CLI is at least the version required by the mem9 plugin.
|
||||
- Confirm Node.js 22 or newer is available to the Codex runtime.
|
||||
- Install the mem9 Codex plugin from the mem9 marketplace.
|
||||
- Run `$mem9:setup` and choose hosted API or self-hosted API.
|
||||
- Apply project scope for this workspace if using a dedicated Fidelity profile.
|
||||
- Verify `$mem9:recall` returns relevant memories.
|
||||
- Verify `$mem9:store` stores one approved non-sensitive memory.
|
||||
- Inspect mem9 dashboard / memory list for accidental sensitive content.
|
||||
- Keep canonical Markdown updates auditable through git.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
type: agent-workflow
|
||||
status: active
|
||||
updated: 2026-04-17
|
||||
updated: 2026-05-12
|
||||
tags:
|
||||
- process
|
||||
- ai-prompting
|
||||
@@ -58,6 +58,10 @@ Use this structure by default:
|
||||
- Include "Do not assume REST is active by default" for REST migration tasks.
|
||||
- Include "Separate external issue from regression" for AO/Discourse issues.
|
||||
- Include "Validate against Fid4/consumer path when needed" for XFlow integration tasks.
|
||||
- For VS Code multi-root Copilot workflows, preserve repo-provided customizations such as `.github/prompts`, `.github/instructions`, `.github/agents`, `.github/skills`, and `AGENTS.md`. Shared `fidelity-ai-copilot` customizations should supplement these repo files, while repo-specific instructions should be treated as the practical authority when they conflict.
|
||||
- For Fidelity Jira/Confluence access from GitHub Copilot CLI or VS Code, do not assume the approved access method. First have the target AI read the current Fidelity-provided human instructions from Confluence or local exported docs, then configure the smallest matching workflow. If those instructions require terminal `curl` with environment variables such as `COPILOT_JIRA_URL` and `COPILOT_JIRA_TOKEN`, enforce that path; otherwise follow the documented Fidelity-approved method. Never print, persist, or hardcode tokens.
|
||||
- Treat `fidelity-ai-copilot` as a self-improving AI harness rather than a static prompt dump: the target AI should notice recurring useful workflows, newly discovered internal instructions, and tool changes, then propose small auditable updates to instructions, skills, prompts, agents, specs, or validation checklists. It should ask before making broad changes and keep product repos clean.
|
||||
- When the user says they will handle dependency alignment, registry configuration, or compile/test execution manually on the development machine, generated Copilot follow-ups should not ask Copilot to solve those dependency/tooling issues or run broad builds. Instead, ask Copilot for the smallest source-level fix for the specific compiler error the user provides, state that the user will rerun validation manually, and request a concise summary of changed files and expected validation impact.
|
||||
|
||||
---
|
||||
|
||||
|
||||
1
ai/inbox/photos/.gitkeep
Normal file
1
ai/inbox/photos/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
type: current
|
||||
project: fidelity
|
||||
status: active
|
||||
updated: 2026-05-05
|
||||
updated: 2026-05-13
|
||||
tags:
|
||||
- current-work
|
||||
- fidelity
|
||||
@@ -18,7 +18,15 @@ tags:
|
||||
- Follow up on active tickets through `project-knowledge/02-work-items/`, especially branch maintenance for `PDIAP-15838` and implementation planning for `PDIAP-15836` / `PDIAP-12284`
|
||||
- `PDIAP-15765` is done and `PDIAP-14859` is also done
|
||||
- `PDIAP-15838` is Done from a Jira/status perspective after external review feedback was addressed, but its draft PR must remain unmerged and kept current with `main` until REST backend production readiness and the required REST-toggle consumer validation window allow merge
|
||||
- `PDIAP-15836` is now paired with `PDIAP-12284` for the UIKit-removal / pure SwiftUI lifecycle implementation path; Quy already moved both into next sprint `26Q2.6`, David can start work now, but leave Jira status in To Do and do not move either story to In Progress until Thursday
|
||||
- `PDIAP-15836` has moved to In Progress. David found a possible minimal dismissal fix path that would not require removing `UIHostingController`, but Jeff directed David to do the `PDIAP-15836` dismissal/lifecycle work and `PDIAP-12284` UIKit-removal work in the same branch because both are disruptive enough to require consumer testing.
|
||||
- `PDIAP-12284` remains paired with `PDIAP-15836`; plan the branch as combined UIKit-removal / SwiftUI lifecycle work rather than splitting the dismissal sequencing into an independently merged path unless direction changes.
|
||||
- Current `PDIAP-12284` implementation direction is to explore XFlowViewMaker-owned global host-mode resolution rather than Fid4-owned per-flow host-mode mapping: SwiftUI host should remain the default, missing/unknown feature configuration should also default to SwiftUI, and `UIHostingController` should be selected only through an explicit temporary fallback flag.
|
||||
- Keep host-mode resolution decoupled from XFlowSDK and app-specific LaunchDarkly/Flagship clients; XFlowSDK should consume a resolved host-mode decision, while Copilot/code inspection should confirm whether XFlowViewMaker can use existing `FeatureEnabling` / `Featuring` abstractions or needs a small dependency-injection path.
|
||||
- May 12 implementation pass for `PDIAP-12284` / `PDIAP-15836` indicates the host-mode API is branch-local/new, keeps two enum types to preserve the XFlowViewMaker/XFlowSDK boundary, uses neutral `swiftUIHost` / `uiKitHost` cases, sets the feature flag key to `iOS-XflowUIKitHostEnabled`, defaults missing/false/unavailable values to SwiftUI, and removes `AnyView` from `buildFlow` internals while leaving it only at the existing public `FlowViewMaker` boundary.
|
||||
- Current compile validation blocker for that branch appears to be dependency alignment: XFlowViewMaker's podspec resolves `XFlowSDK 2.8.48`, which does not expose the new host-mode API; XFlowSDK SwiftPM validation is also blocked in the current environment by missing `fidelity-src` registry configuration.
|
||||
- After manual dependency/build handling, Fid4 now compiles with the latest `FlowViewBuilder` shape. Next validation should be runtime log evidence: confirm a representative flow selects the default SwiftUI host path, then simulate/force `iOS-XflowUIKitHostEnabled == true` to confirm the temporary UIKit host path and dismissal-completion behavior.
|
||||
- `PDIAP-12284` moved to In Progress on May 12. `PDIAP-15836` may become a subtask of `PDIAP-12284`; final Jira structure and point handling are pending Quy/Jeff direction.
|
||||
- Latest `PDIAP-12284` / `PDIAP-15836` simulator log review suggests the SwiftUI-host dismissal sequence is firing in the intended order for the tested run: host disappearance is confirmed before `fireEndActivityDelegates`, delegate callbacks, and `activitySession` cleanup. Treat this as a promising validation run, not final story closure.
|
||||
- Keep the separate `HybridBrokerageAccountOpening` / `JointIdentityCheck` scenario out of `PDIAP-15765` scope unless later evidence proves it belongs there
|
||||
- Include feature-flag planning for the broader UIKit-removal spike, including dismissal sequencing changes that affect consumers
|
||||
- Thoroughly verify current `ApexBridgingAddressComponent` / rule-loading usage before describing it as inactive or dead code
|
||||
@@ -42,6 +50,12 @@ tags:
|
||||
- Jeff suggested broadening the investigation support path while direct Flagship LaunchDarkly access is still missing: monitor the Tauf thread, follow up with Jeffrey O'Leary, package the scenario for the AI tool with build settings and tool details, and ask Aylwing for a quick perspective if available
|
||||
- The Fidelity-side AI tool Jeff referenced for this investigation is GitHub Copilot
|
||||
- Jeff later relayed that Adam says the latest build is now activating REST correctly, so David should switch back to the current Jira story work for removing GraphQL and related LaunchDarkly toggles
|
||||
- For the upcoming REST-layer validation meeting, David prepared Fid4 `4.32` in a separate folder on the release branch, aligned it with the published/internal build `Podfile.lock` from iOSInstaller, and verified the project builds. Current XQ1 behavior appears to have `iOS-XflowRestEnabled` already active, so Fid4 is loading REST; `Open an account` is the simplest non-authenticated entry point for quick XFlow validation.
|
||||
- Jeff confirmed May 11 that the REST back end has now been deployed, and a validation meeting is scheduled for May 12 with the team lead and Bruce. They will toggle the REST flag during the meeting and test both states. Jeff asked David to smoke-test Fid4 `4.32` non-REST flows ahead of the meeting.
|
||||
- REST detection for the meeting: use Charles Proxy to check `/xflow/api` endpoints and inspect `mobile.launchdarkly.com` bulk payloads for the raw evaluated flag value.
|
||||
- Production testing: Raj is trying to get approval to test in production, or they may flip the toggle directly in production. A production test account will be needed; currently only Bruce is known to have one. David found a way to force the production environment from the simulator if needed.
|
||||
- May 12 REST validation nuance: pointing Fid4 at production through the plist by itself still showed GraphQL, but the fuller meeting result was successful iOS REST validation after Raj enabled the production LaunchDarkly toggle with specific targeting for the production test user's context, specifically the MID for the account tested with Bruce. The accurate framing is not simply "production still used GraphQL"; the key difference was production LD targeting for the specific user context.
|
||||
- Jeff confirmed the feature flag strategy (host-mode resolution centralized in XFlowViewMaker) is in scope for the current `PDIAP-12284` / `PDIAP-15836` branch and asked David to implement it there.
|
||||
- `PDIAP-15838` draft PR has reached external review; Bruce left minor feedback, David addressed it, and Jeff directed David to move the ticket to Done while holding the merge
|
||||
- Do not merge the GraphQL/Apollo removal PR until REST backend is live in production and the previous REST-toggle implementation has been QA-tested and active in production with REST enabled for all consumers for at least 30 days without issues
|
||||
- Current `PDIAP-15838` follow-up centers on the `PicoSDK` update in `SampleApp`: the newer Pico path removes the remaining transitive Apollo dependency, re-enables FGO and FidFolios testing in `SampleApp`, and aligns the sample implementation with current Fid4 usage
|
||||
@@ -80,6 +94,7 @@ tags:
|
||||
- Adam reported the earlier REST activation problem, and he or his team validate behavior on real devices rather than simulator-only paths
|
||||
- Avoid treating GitHub Copilot or LaunchDarkly as story-specific tools; both are broader Fidelity workflow tools that happened to matter in this investigation
|
||||
- Defining a consumer rollout plan for UIKit-removal sequencing changes, including validation, communication, and feature-flag retirement
|
||||
- Current Fid4 validation logs include duplicate Objective-C class warnings for `Secure` / `DeviceRisk` symbols loaded from both `SecureDocV.framework` and `Fid4.debug.dylib`; treat this as environment/integration noise unless runtime behavior points to a validation blocker, and redact raw device-token prints before sharing logs.
|
||||
- Keep Apollo-removal / REST-migration cleanup grounded in production readiness: source-level cleanup can continue, but merge/release timing depends on the prior REST-toggle implementation being QA-tested and stable in production for the agreed window
|
||||
- Keep the GraphQL/Apollo removal branch and the future `PDIAP-15836` / `PDIAP-12284` branch current with `main`; if conflict resolution looks non-trivial, flag it so a 1-2 point branch-maintenance story can be created
|
||||
- Avoiding assumptions when comparing iOS and Android validation behavior; scenario-specific parity needs to be confirmed before reporting scope
|
||||
@@ -103,6 +118,7 @@ tags:
|
||||
- For standups that may also be sent to Teams, keep the update concise, omit internal review-feedback details, and avoid phrasing like `confirmed` when the audience lacks the internal context for what was confirmed
|
||||
- When a release item is waiting on approvals or pipeline work, make the parallel story work explicit instead of making the update sound blocked on waiting alone
|
||||
- Standups should omit side questions or manager-only context refreshes unless they materially changed story work
|
||||
- For `PDIAP-16167` standups after May 1, do not report story creation as previous-day work unless the previous-workday evidence explicitly says the story was created that day; focus on report publication, findings shared, closure, or remaining follow-up instead
|
||||
- For `PDIAP-15838` standups, focus on Apollo-removal progress and the `PicoSDK` transitive dependency work; omit extra exploratory asks unless they directly changed the story outcome or created a blocker
|
||||
- If a root cause document or other documentation update directly supports a story, it should be reported under that story instead of as a separate standalone item
|
||||
- Standups should omit items not tied to a story unless they are real blockers
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
type: current-work-items
|
||||
project: fidelity
|
||||
status: active
|
||||
updated: 2026-05-05
|
||||
updated: 2026-05-13
|
||||
tags:
|
||||
- current-work
|
||||
- work-item
|
||||
@@ -20,15 +20,15 @@ Update the per-ticket files first when scope, status, sequencing, or communicati
|
||||
|
||||
- `PDIAP-15838` - Remove Apollo for iOS
|
||||
Detail: `project-knowledge/02-work-items/pdiap-15838.md`
|
||||
Current note: ticket moved to Done after external review feedback was addressed, but the draft PR stays unmerged. Keep the branch up to date with `main` until REST backend is live in production and REST toggles have been enabled for consumers for the required validation window; expected merge timing is at least 30 days out.
|
||||
Current note: ticket moved to Done after external review feedback was addressed, but the draft PR stays unmerged. Keep the branch up to date with `main` until REST backend is live in production and REST toggles have been enabled for consumers for the required validation window; expected merge timing is at least 30 days out. May 13 follow-up validation succeeded on iOS after Raj enabled the production LaunchDarkly toggle for the specific production test user's MID.
|
||||
|
||||
- `PDIAP-15836` - Modernize dismissal delegate lifecycle sequencing for pure SwiftUI environment
|
||||
Detail: `project-knowledge/02-work-items/pdiap-15836.md`
|
||||
Current note: approved at `8` points and now paired with `PDIAP-12284` for the UIKit-removal implementation path. Quy already moved it into next sprint `26Q2.6`; leave it in To Do and do not move to In Progress until Thursday. Treat `PDIAP-15836` as blocked by `PDIAP-12284` unless validation proves the lifecycle fix can be implemented independently.
|
||||
Current note: moved to In Progress on May 7. David found a possible minimal dismissal fix path that would not require removing `UIHostingController`, but Jeff directed David to do the dismissal/lifecycle work and `PDIAP-12284` UIKit-removal work in the same branch because both changes require consumer testing. A May 11 simulator log review suggests the tested SwiftUI-host path fires delegate/session-clear callbacks only after host-disappearance confirmation, but broader branch and consumer validation are still needed.
|
||||
|
||||
- `PDIAP-12284` - Remove UIKit wrapping from XFlow
|
||||
Detail: `project-knowledge/02-work-items/pdiap-12284.md`
|
||||
Current note: reopened after rollback and should be handled with `PDIAP-15836`. Quy already moved it into next sprint `26Q2.6`; work can begin now, but Jira status stays To Do until Thursday and merge/release waits until after REST-transition consumer validation.
|
||||
Current note: moved to In Progress on May 12 and should be handled with `PDIAP-15836` in the same branch. Current implementation direction is to avoid Fid4 per-flow host-mode mapping and instead evaluate XFlowViewMaker-owned global host-mode resolution, with SwiftUI as default and `UIHostingController` only as an explicit temporary fallback. May 12 implementation pass resolved earlier shape concerns with neutral enum names, `iOS-XflowUIKitHostEnabled`, SwiftUI default semantics, and `AnyView` removed from builder internals. Fid4 now compiles after manual dependency/build handling; next validation is runtime log evidence for default SwiftUI host first, then forced UIKit-host flag behavior. `PDIAP-15836` may become a subtask pending final Jira/points direction.
|
||||
|
||||
## Backlog / Future Reference
|
||||
|
||||
|
||||
22
project-knowledge/02-work-items/IA improvements.md
Normal file
22
project-knowledge/02-work-items/IA improvements.md
Normal file
@@ -0,0 +1,22 @@
|
||||
- Experiment-driven investigation
|
||||
- Debug print auto added
|
||||
- Iteratively detect now possible debug prints
|
||||
- Structured ticket artifacts with jira, patch, prompts, sessions
|
||||
- Tolerant to pre-experiment fix steps to fix any build error between experiments
|
||||
- Swift skill, specialized to debug prints
|
||||
- Xcode logs analysis
|
||||
- Find and read the full Xcode artifacts
|
||||
- Extract relevant logs
|
||||
- Xcode Integration
|
||||
- Efficient use of xcode commands to build, test, contexted for tuist, cocoapods, sample projects
|
||||
- Execute unit test proficiently, like executing only new tests or related
|
||||
- Investigation: Differences from skills with cli commands vs mcps
|
||||
- Investigation: Differences of skills vs instructions from vscode copilot
|
||||
- Investigation: Differences from agents vs skills using agent, what is more general? correct relationship and use
|
||||
- Charles Proxy integration
|
||||
- LaunchDarkly integration
|
||||
- Teams integration
|
||||
- Photo uploader
|
||||
- Start as a service
|
||||
- Auto categorize by context
|
||||
- Multi photos session, copy multiples images in clipboard
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
type: work-item
|
||||
project: fidelity
|
||||
status: backlog-ready
|
||||
status: in-progress
|
||||
ticket: PDIAP-12284
|
||||
title: "Remove UIKit wrapping from XFlow"
|
||||
systems: [xflowsdk, xflowviewmaker]
|
||||
workstreams: [xflow-swiftui-migration, consumer-integration]
|
||||
people: [jeff-dewitte]
|
||||
related: [pdiap-15836, pdiap-15838]
|
||||
updated: 2026-05-05
|
||||
updated: 2026-05-13
|
||||
tags:
|
||||
- work-item
|
||||
- fidelity
|
||||
@@ -20,16 +20,27 @@ tags:
|
||||
|
||||
## Status
|
||||
|
||||
- Reopened after rollback.
|
||||
- Quy already moved this story into the next sprint (`26Q2.6`); leave it in To Do until the sprint starts on Thursday.
|
||||
- Jeff asked David to start working on this with `PDIAP-15836`, but not to move the active story to In Progress until Thursday.
|
||||
- Reopened after rollback and moved to In Progress on May 12.
|
||||
- Jeff directed David to do this UIKit-removal work and `PDIAP-15836` dismissal/lifecycle work in the same branch because both are disruptive enough to require consumer testing.
|
||||
- Current implementation direction is to avoid Fid4-owned per-flow host-mode mapping and evaluate XFlowViewMaker-owned global host-mode resolution.
|
||||
- David continued the implementation/validation loop on May 11; current simulator log review is promising for the SwiftUI-host dismissal sequencing in the tested run.
|
||||
- Jeff confirmed on May 11 that the feature flag strategy (host-mode resolution centralized in XFlowViewMaker) should be implemented as part of this branch's work.
|
||||
- May 12 implementation pass resolved several earlier concerns: host-mode API appears branch-local/new, enum cases were renamed to neutral `swiftUIHost` / `uiKitHost`, the final flag key is `iOS-XflowUIKitHostEnabled`, missing/false/unavailable flag values default to SwiftUI, and `AnyView` was removed from `buildFlow` internals while remaining only at the existing public `FlowViewMaker` boundary.
|
||||
- Current validation blocker appears to be dependency alignment rather than host-mode code correctness: XFlowViewMaker is resolving `XFlowSDK 2.8.48` from its podspec, which does not expose the new host-mode API needed by the branch.
|
||||
- After manual dependency/build handling, Fid4 now compiles with the latest `FlowViewBuilder` shape. Next validation should be runtime evidence: first verify a representative flow selects the default SwiftUI host path, then simulate/force `iOS-XflowUIKitHostEnabled == true` to verify the temporary UIKit host path and dismissal behavior.
|
||||
- `PDIAP-15836` may become a subtask of this story; final Jira structure and point handling are pending Quy/Jeff direction.
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
- This is the original story for removing the UIKit wrapping.
|
||||
- Current relationship to track: `PDIAP-15836` is blocked by this story because the lifecycle-sequencing fix depends on the UIKit removal landing, unless further validation proves part of `PDIAP-15836` can be implemented independently on the SwiftUI path.
|
||||
- Current relationship to track: `PDIAP-12284` should be handled with `PDIAP-15836` in the same implementation branch. David identified a possible minimal `PDIAP-15836` fix path that does not require removing `UIHostingController`, but Jeff prefers combined branch work because both changes require consumer testing.
|
||||
- Desired host-mode model: SwiftUI host is the default, missing or unknown feature configuration should also default to SwiftUI, and `UIHostingController` should remain only as an explicit temporary fallback while consumers validate the migration.
|
||||
- XFlowSDK should not be coupled directly to LaunchDarkly, Flagship, or app-specific feature-flag clients; it should receive an already-resolved host-mode decision.
|
||||
- If `hostMode` must be passed through `FlowConfig` or a similar object, keep it as adapter/internal plumbing rather than a new consumer-facing per-flow responsibility.
|
||||
- The latest tested run reported the expected dismissal order for the SwiftUI-host path, but the branch still needs review of the shared host-mode routing and any required broader validation before story closure.
|
||||
- Resolved implementation-shape decisions from the May 12 pass: keep two host-mode enum types to preserve the boundary where XFlowViewMaker owns host-selection policy and XFlowSDK owns presentation mechanics, keep the single conversion point in `FlowViewBuilder`, use neutral enum names, and keep SwiftUI as default unless `iOS-XflowUIKitHostEnabled` is explicitly true.
|
||||
|
||||
---
|
||||
|
||||
@@ -45,3 +56,4 @@ tags:
|
||||
|
||||
- Work can begin, but merge/release should wait until the REST-transition consumer-validation window has completed.
|
||||
- Keep the implementation branch up to date with `main` while waiting for approval to work with consumers and merge.
|
||||
- Before implementation is treated as settled, confirm through product-code inspection whether XFlowViewMaker can resolve host mode using existing `FeatureEnabling` / `Featuring` abstractions or whether a small dependency-injection path is needed.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
type: work-item
|
||||
project: fidelity
|
||||
status: backlog-ready
|
||||
status: in-progress
|
||||
ticket: PDIAP-15836
|
||||
title: "Modernize dismissal delegate lifecycle sequencing for pure SwiftUI environment"
|
||||
systems: [xflowsdk, xflowviewmaker, ftframeworks]
|
||||
workstreams: [xflow-swiftui-migration, consumer-integration]
|
||||
people: [jeff-dewitte]
|
||||
related: [pdiap-14859, pdiap-12284, pdiap-15838]
|
||||
updated: 2026-05-05
|
||||
updated: 2026-05-12
|
||||
tags:
|
||||
- work-item
|
||||
- fidelity
|
||||
@@ -19,9 +19,11 @@ tags:
|
||||
|
||||
## Status
|
||||
|
||||
- Approved, but should not be moved to In Progress until the next sprint starts
|
||||
- Quy already moved this story into the next sprint (`26Q2.6`); leave it in To Do until the sprint starts on Thursday
|
||||
- Jeff confirmed David can start the actual implementation work now, but must not move the story to In Progress until Thursday
|
||||
- Moved to In Progress on May 7.
|
||||
- David found a possible minimal dismissal fix path that would not require removing `UIHostingController`, and was validating it.
|
||||
- Jeff directed David to do this dismissal/lifecycle work together with `PDIAP-12284` in the same branch because both changes are disruptive enough to require consumer testing.
|
||||
- May 11 simulator log review suggests the tested SwiftUI-host path now fires in the intended order: host-disappearance confirmation precedes `fireEndActivityDelegates`, delegate callbacks, and `activitySession` cleanup. Treat this as promising validation evidence for the tested run, not final consumer validation.
|
||||
- May 12 branch validation reported that explicit UIKit host mode still preserves dismissal-completion behavior and delegate callbacks/session cleanup remain after confirmed dismissal. Broader compile validation still depends on resolving the XFlowViewMaker / XFlowSDK dependency mismatch.
|
||||
- Sequenced after `PDIAP-15838` source work, but merge/release is delayed until after REST-transition consumer validation
|
||||
- Sized at `8` points
|
||||
|
||||
@@ -40,6 +42,7 @@ tags:
|
||||
- Modernize dismissal delegate lifecycle sequencing for pure SwiftUI flows.
|
||||
- Cover the missing lifecycle contract where delegate callbacks can happen before the view is fully removed.
|
||||
- Validate the change across affected SwiftUI flows rather than only in one narrow reproduction.
|
||||
- Preserve a single canonical delegate/session-clear path if possible, and do not treat SwiftUI `onDisappear` as proof of dismissal completion unless simulator logs validate that lifecycle contract.
|
||||
|
||||
---
|
||||
|
||||
@@ -47,9 +50,11 @@ tags:
|
||||
|
||||
- This story should come after `PDIAP-15838`.
|
||||
- `PDIAP-12284` is the original UIKit-wrapping removal story and was reopened after rollback.
|
||||
- Current relationship to add/track in Jira: `PDIAP-15836` is blocked by `PDIAP-12284`, because the UIKit removal needs to land for the lifecycle fix to apply. David still needs to validate whether any part of `PDIAP-15836` can be implemented independently on the SwiftUI path.
|
||||
- Current working relationship: implement and validate `PDIAP-15836` together with `PDIAP-12284` in the same branch unless direction changes. David identified a possible isolated/minimal fix path, but Jeff prefers combined branch work because consumer testing is required either way.
|
||||
- It is aligned with epic `26Q2 - Updating XFlowSDK to Decouple and Fix ApexKit Dependencies (Split Part 2)`.
|
||||
- If possible, it should use the same consumer-impact feature flag strategy as the broader UIKit-removal rollout.
|
||||
- Current combined-branch planning should keep dismissal sequencing host-agnostic: the SwiftUI host path must prove dismissal completion before delegate callbacks, while the temporary `UIHostingController` fallback should preserve legacy dismiss-completion behavior.
|
||||
- Current validation logs did not show the key failure patterns for the tested run, such as delegate firing before host-disappearance confirmation or repeated host-disappearance confirmation for the same dismissal id.
|
||||
- Expect a long-lived branch: after implementation, maintain the branch until consumer-testing approval. Jeff expects the GraphQL-removal branch to merge first after the REST validation period, then that branch should be merged into the `PDIAP-15836` / `PDIAP-12284` branch. Current estimate is roughly 90-100 days from 2026-05-05 unless Fidelity shortens the review windows.
|
||||
|
||||
---
|
||||
|
||||
@@ -8,7 +8,7 @@ systems: [xflowsdk]
|
||||
workstreams: [rest-migration]
|
||||
people: [jeff-dewitte, bruce-meeks, adam-abdelhadi, tauf, jeffrey-oleary, aylwing-olivas]
|
||||
related: [launchdarkly, github-copilot]
|
||||
updated: 2026-05-05
|
||||
updated: 2026-05-13
|
||||
tags:
|
||||
- work-item
|
||||
- fidelity
|
||||
@@ -88,6 +88,11 @@ tags:
|
||||
- The native Swift domain enum file should remain as the canonical location for XFlow domain enums after removal of GraphQL-generated code.
|
||||
- Jeff directed David to move the ticket to Done, while keeping the PR unmerged for at least 30 days.
|
||||
- Branch maintenance is now part of the work: keep the GraphQL/Apollo-removal branch up to date with `main` until the REST backend/toggle validation window allows merge. If future `main` merges create conflicts that look non-trivial, raise it so a 1-2 point maintenance story can be created.
|
||||
- May 11 preparation for another REST-layer validation meeting: Fid4 `4.32` was checked out in a separate folder, aligned to the published/internal build `Podfile.lock` from iOSInstaller, and built successfully. Current XQ1 behavior appears to have `iOS-XflowRestEnabled` already active, so Fid4 is loading REST; `Open an account` is the simplest non-authenticated XFlow entry point to use for a quick readiness test.
|
||||
- Jeff confirmed May 11 that the REST back end has now been deployed, and a meeting is scheduled for May 12 with the team lead and Bruce to toggle the flag and test both REST and non-REST states.
|
||||
- REST detection for the meeting: Charles Proxy to verify `/xflow/api` endpoints and inspect `mobile.launchdarkly.com` bulk payloads for the raw evaluated flag value.
|
||||
- Production testing: Raj is trying to get approval to test in production. A production test account will be needed; currently only Bruce is known to have one. David found a way to force the production environment from the simulator if needed.
|
||||
- May 12 REST validation nuance: pointing Fid4 at production through the plist by itself still showed GraphQL, but the fuller meeting result was successful iOS REST validation after Raj enabled the production LaunchDarkly toggle with specific targeting for the production test user's context, specifically the MID for the account tested with Bruce. Treat this as evidence that the production REST path can activate on iOS when the LD targeting context matches.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ When the format fits, prefer:
|
||||
- On Mondays, use Friday's work context unless a later prior day has Mattermost activity.
|
||||
- If the previous calendar day has no project activity because of weekend, holiday, or OOO, use the latest prior day with Mattermost activity.
|
||||
- For standups, when a Jira item has multiple concrete updates, use one top-level `JIRA-ID - Title` bullet and indented markdown sub-bullets instead of repeating the same Jira line.
|
||||
- For Mattermost-ready standups, include a visible blank line before `Today:` and `Blockers:` section headers so copy/paste rendering is correct without manual edits.
|
||||
- When a flow/page shorthand could be ambiguous, prefer the real flow identifier and page name from `project-knowledge/03-context/workstreams/flow-page-references.md`.
|
||||
- For standups that may also be sent to Teams, prefer plain audience-friendly wording over internal implementation shorthand; avoid terms like `fallback` unless the audience already has the necessary context.
|
||||
- When a release is waiting on approvals or pipeline movement, make the concurrent work explicit so the update does not imply idle waiting.
|
||||
|
||||
@@ -3,8 +3,8 @@ type: system
|
||||
project: fidelity
|
||||
status: active
|
||||
workstreams: [consumer-integration, xflow-swiftui-migration]
|
||||
related: [xflowsdk, fid4, ftframeworks, consumer-integration, pdiap-14859, pdiap-15765, pdiap-15836]
|
||||
updated: 2026-04-17
|
||||
related: [xflowsdk, fid4, ftframeworks, consumer-integration, pdiap-14859, pdiap-15765, pdiap-15836, pdiap-12284]
|
||||
updated: 2026-05-08
|
||||
tags:
|
||||
- system
|
||||
- fidelity
|
||||
@@ -48,6 +48,8 @@ XFlowViewMaker is the adapter layer between XFlowSDK and consuming app/framework
|
||||
- If the issue involves version propagation into Fid4, treat XFlowViewMaker as part of the release path unless direct-consumption work has replaced it.
|
||||
- Current understanding is that Fid4 does not consume XFlowSDK directly; XFlowViewMaker remains the required integration layer for Fid4 and for other consumers such as `FTAccountOpen` and `FTTransfer`.
|
||||
- Questions about removing or collapsing the layer should be evaluated against current consumer integration patterns, not just local SDK behavior.
|
||||
- For the current `PDIAP-12284` / `PDIAP-15836` migration planning, XFlowViewMaker is the preferred candidate owner for global host-mode resolution if product-code inspection confirms it can access the existing feature-flag abstraction cleanly. This avoids Fid4-owned per-flow mapping and keeps XFlowSDK decoupled from app-specific LaunchDarkly/Flagship clients.
|
||||
- The desired host-mode behavior for that migration is SwiftUI host by default, SwiftUI host when feature configuration is missing or unknown, and `UIHostingController` only when an explicit temporary fallback flag requests it.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ type: workstream
|
||||
project: fidelity
|
||||
status: active
|
||||
systems: [xflowsdk, xflowviewmaker, ftframeworks, fid4]
|
||||
work-items: [pdiap-14859, pdiap-15836]
|
||||
work-items: [pdiap-14859, pdiap-15836, pdiap-12284]
|
||||
related: [consumer-integration, xflow-debugging]
|
||||
updated: 2026-04-16
|
||||
updated: 2026-05-08
|
||||
tags:
|
||||
- workstream
|
||||
- fidelity
|
||||
@@ -38,6 +38,9 @@ Track the durable behavior patterns introduced while moving XFlow from older ass
|
||||
- a lifecycle sequencing problem
|
||||
- a consumer presentation constraint in Fid4
|
||||
- Do not assume a visual issue is only cosmetic; several historical SwiftUI bugs changed flow behavior materially.
|
||||
- For UIKit-wrapping removal, prefer a host-mode design that keeps SwiftUI as the default and limits `UIHostingController` to an explicit temporary fallback. Missing or unknown rollout configuration should not silently restore the UIKit host.
|
||||
- Keep host-mode ownership at the shared integration layer when possible. A Fid4-only per-flow map is less reusable for XFlow's multiple consumers and creates cleanup work when the fallback is retired.
|
||||
- Dismissal sequencing changes must be validated as lifecycle contracts, not just visual symptom fixes: delegate/session-clear callbacks should fire after confirmed dismissal, and `onDisappear` should not be treated as sufficient proof without simulator-log evidence.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ updated: 2026-05-04
|
||||
|
||||
## Work Done
|
||||
|
||||
- Created `PDIAP-16167 - AccountLink - XFlow causing web view rewrites investigation`.
|
||||
- Prepared the root-cause report for the FTTransfer AccountLink issue.
|
||||
- Verified that XFlow eventing does not appear to be the direct cause of the reload.
|
||||
- Verified that the issue persists when rolling back `XFlowSDK` to `v0.1.0`, which predates REST and modern architecture changes.
|
||||
|
||||
19
project-knowledge/06-daily/2026-05-07.md
Normal file
19
project-knowledge/06-daily/2026-05-07.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
type: daily
|
||||
project: fidelity
|
||||
date: 2026-05-07
|
||||
updated: 2026-05-07
|
||||
focus: [pdiap-15836, pdiap-12284, rest-validation]
|
||||
work-items: [PDIAP-15836, PDIAP-12284]
|
||||
blockers: [production-test-account]
|
||||
tags:
|
||||
- daily
|
||||
- fidelity
|
||||
---
|
||||
|
||||
# 2026-05-07
|
||||
|
||||
## Notes
|
||||
|
||||
- Clarified REST validation context: forcing Fid4 production endpoints by editing `initializeAppEnvironment` is related to validating REST enablement, not to the Apollo-removal story. It was a point-in-time ask that may still be useful later, but the immediate need is access to a production test account to complete validation.
|
||||
- Clarified `PDIAP-15836` status: the dismissal approach David tested appears coupled to the `UIHostingController` removal path, but there may be a strategy to isolate the fix. David will investigate whether the lifecycle/dismissal fix can be applied independently.
|
||||
58
project-knowledge/06-daily/2026-05-08.md
Normal file
58
project-knowledge/06-daily/2026-05-08.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
type: daily
|
||||
project: fidelity
|
||||
date: 2026-05-08
|
||||
status: active
|
||||
focus: [pdiap-15836, pdiap-12284]
|
||||
work-items: [PDIAP-15836, PDIAP-12284]
|
||||
blockers: []
|
||||
tags:
|
||||
- daily
|
||||
- fidelity
|
||||
updated: 2026-05-08
|
||||
---
|
||||
|
||||
# 2026-05-08
|
||||
|
||||
## Focus
|
||||
|
||||
- Capture the May 7 Mattermost thread updates for `PDIAP-15836` / `PDIAP-12284` and keep current work memory aligned with Jeff's direction.
|
||||
|
||||
---
|
||||
|
||||
## Work Done
|
||||
|
||||
- Mattermost sync confirmed David moved `PDIAP-15836` to In Progress and found a possible minimal dismissal fix path that would not require removing `UIHostingController`.
|
||||
- Refined the Copilot implementation direction for the combined `PDIAP-15836` / `PDIAP-12284` branch: evaluate XFlowViewMaker-owned global host-mode resolution instead of Fid4-owned per-flow mapping.
|
||||
- Friday standup reported `PDIAP-15836` as In Progress and aligned with `PDIAP-12284` so dismissal/lifecycle changes and UIKit-removal work can be handled in the same branch and validated together.
|
||||
- Friday standup reported continued `PDIAP-12284` evaluation around a dual-path strategy: SwiftUI host should become the default while preserving the current `UIHostingController` path temporarily for validation.
|
||||
|
||||
---
|
||||
|
||||
## Findings
|
||||
|
||||
- David's early `PDIAP-15836` finding was that the tested dismissal approach appeared coupled to the `UIHostingController` removal path, but he was investigating whether the lifecycle/dismissal fix could be isolated.
|
||||
- Current working design preference is SwiftUI host by default, including missing or unknown feature configuration; `UIHostingController` should be selected only by an explicit temporary fallback flag.
|
||||
- XFlowSDK should consume a resolved host-mode decision rather than depend directly on LaunchDarkly, Flagship, or app-specific feature-flag clients.
|
||||
- The SwiftUI dismissal path still needs evidence that delegate/session-clear callbacks fire only after dismissal completion; do not rely on `onDisappear` alone unless simulator logs validate it.
|
||||
|
||||
---
|
||||
|
||||
## Communication
|
||||
|
||||
- Jeff directed David to do the `PDIAP-15836` dismissal/lifecycle work and `PDIAP-12284` UIKit-removal work in the same branch, because both changes are disruptive enough to require consumer testing.
|
||||
- Jeff approved the Friday standup wording for Teams, and David sent it.
|
||||
- Jeff said he was traveling to Managua and asked David to monitor and respond on Teams while he was gone, asking for help if a message came in that David was unsure how to answer.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Treat `PDIAP-15836` and `PDIAP-12284` as same-branch work unless later direction changes; keep consumer-testing impact explicit.
|
||||
- Ask Copilot to inspect whether XFlowViewMaker can use existing `FeatureEnabling` / `Featuring` abstractions for global host-mode resolution; if not, have it return the smallest dependency-injection option instead of pushing per-flow logic into Fid4.
|
||||
|
||||
---
|
||||
|
||||
## Blockers
|
||||
|
||||
- None currently.
|
||||
65
project-knowledge/06-daily/2026-05-11.md
Normal file
65
project-knowledge/06-daily/2026-05-11.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
type: daily
|
||||
project: fidelity
|
||||
date: 2026-05-11
|
||||
status: active
|
||||
focus: [pdiap-12284, pdiap-15836, pdiap-15838]
|
||||
work-items: [PDIAP-12284, PDIAP-15836, PDIAP-15838]
|
||||
blockers: [fid4-validation-environment-warnings]
|
||||
tags:
|
||||
- daily
|
||||
- fidelity
|
||||
updated: 2026-05-11
|
||||
---
|
||||
|
||||
# 2026-05-11
|
||||
|
||||
## Focus
|
||||
|
||||
- Continue the combined `PDIAP-12284` / `PDIAP-15836` work, with emphasis on the `PDIAP-12284` SwiftUI-host / UIKit-wrapper-removal path and dismissal-sequencing validation.
|
||||
- Prepare Fid4 `4.32` for a likely REST-layer validation meeting, including release-branch setup, published `Podfile.lock` alignment, and XFlow entry-point readiness.
|
||||
|
||||
---
|
||||
|
||||
## Work Done
|
||||
|
||||
- Continued the `PDIAP-12284` implementation/validation loop with Copilot using a simulator console log from the current session.
|
||||
- Prepared a separate Fid4 project folder on the `4.32` release branch and used the `Podfile.lock` from the published/internal `4.32` build downloaded through iOSInstaller.
|
||||
- Built the Fid4 `4.32` setup successfully to verify readiness for the upcoming REST-layer validation session.
|
||||
- Jeff confirmed the feature flag strategy (host-mode resolution centralized in XFlowViewMaker) should be implemented as part of this branch's work.
|
||||
- Submitted weekly hours for the week ending May 9.
|
||||
|
||||
---
|
||||
|
||||
## Findings
|
||||
|
||||
- The latest log review reported two dismissal sequences with the expected order: `endActivity` requested, dismiss requested, host received dismiss request, host disappearance confirmed, `fireEndActivityDelegates`, delegate event/exit callbacks, then `activitySession` cleared.
|
||||
- The log review did not surface the key failure patterns that would invalidate the dismissal sequencing, such as delegate firing before host-disappearance confirmation or multiple host-disappearance confirmations for the same dismissal id.
|
||||
- Remaining non-XFlow log noise appears to be environment/integration related: Objective-C duplicate class warnings for `Secure` / `DeviceRisk` symbols from both `SecureDocV.framework` and `Fid4.debug.dylib`, Firebase configuration warnings, simulator noise, and raw device-token prints.
|
||||
- Treat the dismissal sequencing result as a promising validation run, but avoid calling the full story complete until the branch is reviewed and any required consumer validation is done.
|
||||
- Current code-review concerns for `PDIAP-12284`: avoid duplicating host-mode enums across XFlowViewMaker `FlowConfig` and XFlowSDK if a single shared/public type can safely express the contract; reconsider names that include `Fallback` if they make the feature sound temporary or removal-specific; align the feature-flag name with existing Fidelity style such as the `iOS-` prefix; and review whether the new `AnyView` usage in `buildFlow` is necessary or can be replaced with a type-safer SwiftUI alternative.
|
||||
- Current XQ1 validation appears to have `iOS-XflowRestEnabled` already active, so Fid4 is loading REST even before the planned toggle-change meeting.
|
||||
- The simplest non-authenticated Fid4 entry point identified for quick XFlow validation is `Open an account`.
|
||||
- **REST backend deployed** (per Jeff): the REST back end has been deployed and a validation meeting is scheduled for May 12 with the team lead and Bruce. They will toggle the REST flag during the meeting and test both states.
|
||||
- Jeff asked David to smoke-test Fid4 `4.32` non-REST flows today ahead of the meeting — confirm XFlow comes up without issues while the flag is still in its current state.
|
||||
- **REST detection for the meeting**: use Charles Proxy to verify `/xflow/api` endpoints for REST path and inspect `mobile.launchdarkly.com` bulk payloads for the raw evaluated flag value.
|
||||
- **Production testing status**: Raj is trying to get approval to test in production. If approved, or if they simply flip the toggle in production, a production test account will be needed — currently only Bruce is known to have one. David found a way to force the production environment from the simulator.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Continue reviewing the `PDIAP-12284` implementation and confirm the SwiftUI-host default path and temporary UIKit-hosted path are both routed and validated as intended.
|
||||
- Ask Copilot to evaluate host-mode type ownership, naming, flag naming, and `AnyView` alternatives before accepting the current diff.
|
||||
- Decide whether the duplicate `Secure` / `DeviceRisk` class warnings are only known environment noise or need separate follow-up before relying on the validation session.
|
||||
- Avoid sharing raw logs without redacting device-token prints.
|
||||
- **Smoke-test Fid4 `4.32` non-REST flows today** before the May 12 meeting — open several XFlow entry points and confirm they load without issues in the current (likely REST, per XQ1 flag state) configuration.
|
||||
- Prepare for the May 12 REST-layer validation meeting: have Fid4 `4.32` running in simulator, Charles Proxy capturing, and the list of known Fid4 XFlow entry points ready for screen-share walkthrough with Jeff/the team lead.
|
||||
- Be ready to show both REST (`/xflow/api`) and non-REST (if the toggle is flipped) behavior during the meeting.
|
||||
|
||||
---
|
||||
|
||||
## Blockers
|
||||
|
||||
- No story blocker confirmed, but the Fid4 validation session includes duplicate-class warnings that may need triage if runtime casting/crash behavior appears during validation.
|
||||
- Production testing may require a test account that only Bruce currently has; coordination needed if the team decides to test in production during or after the May 12 meeting.
|
||||
51
project-knowledge/06-daily/2026-05-12.md
Normal file
51
project-knowledge/06-daily/2026-05-12.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
type: daily
|
||||
project: fidelity
|
||||
date: 2026-05-12
|
||||
status: active
|
||||
focus: [pdiap-12284, pdiap-15836, rest-validation]
|
||||
work-items: [PDIAP-12284, PDIAP-15836, PDIAP-15838]
|
||||
blockers: []
|
||||
tags:
|
||||
- daily
|
||||
- fidelity
|
||||
updated: 2026-05-13
|
||||
---
|
||||
|
||||
# 2026-05-12
|
||||
|
||||
## Focus
|
||||
|
||||
- Continue the combined `PDIAP-12284` / `PDIAP-15836` implementation path for SwiftUI host-mode routing and dismissal sequencing.
|
||||
|
||||
---
|
||||
|
||||
## Findings
|
||||
|
||||
- Copilot's inspection says the host-mode API appears new in the current branch and is not present on `main` in `XFlowSDK`; XFlowViewMaker's `FlowConfig` host-mode shape also differs from `main`. That makes neutral enum-case renaming safer within this branch, pending normal review.
|
||||
- The current implementation keeps two host-mode enum types to preserve the module boundary: XFlowViewMaker owns host-selection policy and XFlowSDK owns presentation mechanics. Conversion is intended to stay in one place in `FlowViewBuilder` with an explicit boundary comment.
|
||||
- Enum cases were renamed to neutral names: `swiftUIHost` and `uiKitHost`.
|
||||
- The final feature flag key is `iOS-XflowUIKitHostEnabled`. Semantics: `true` uses the temporary UIKit host path; `false`, missing, unavailable, or unknown uses the SwiftUI host.
|
||||
- Old host-flag key usage was removed rather than dual-supported because it was not found in current code search outside the branch context.
|
||||
- `AnyView` was removed from `buildFlow` internals by making the builder path type-safe with `ViewBuilder`; `AnyView` remains only at the existing `FlowViewMaker` public API boundary where `makeFlowView` currently returns `AnyView`.
|
||||
- Files reported changed: `FlowConfig.swift`, `FlowViewBuilder.swift`, `FlowViewMaker.swift`, and `XFlowManager.swift`.
|
||||
- Validation reported: SwiftUI remains the default host mode; explicit UIKit host path preserves dismissal-completion behavior; delegate callbacks/session cleanup still happen after confirmed dismissal; existing `FlowConfig` standard init call sites remain compatible; edited files had no file-level diagnostics.
|
||||
- Build validation is not green yet. XFlowViewMaker's module build failed because its podspec currently resolves `XFlowSDK 2.8.48`, which does not expose the new host-mode type/API. XFlowSDK SwiftPM validation in that environment is also blocked by registry configuration: no registry configured for the `fidelity-src` scope.
|
||||
- After manual dependency/build handling, the Fid4 compile path is now green for the latest `FlowViewBuilder` shape. The source-level fixes moved setup side effects out of the `@ViewBuilder`, kept `@ViewBuilder` confined to host-view construction, and preserved internal type safety without reintroducing `AnyView`.
|
||||
- `PDIAP-12284` was moved to In Progress. `PDIAP-15836` may become a subtask of `PDIAP-12284`; Jeff planned to ask Quy how to handle the Jira structure and story points.
|
||||
- David explained that "host-mode resolution" is shorthand for deciding whether XFlow builds through the SwiftUI host path or the temporary UIKit/`UIHostingController` path. XFlowViewMaker makes that decision before calling XFlowSDK; SwiftUI is the default and UIKit is selected only when the flag explicitly enables it.
|
||||
- For REST validation, David confirmed Fid4 can be pointed at production through a plist value, which is the preferred non-pushed path. Testing with the production plist still showed GraphQL being used in the tested case. Raj appears to have a production account for follow-up production validation.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Move to runtime validation now that compile is green: first run a representative Fid4/XFlow flow with the default configuration and confirm logs show the SwiftUI host path is selected.
|
||||
- Then run a controlled UIKit-host experiment by forcing/simulating `iOS-XflowUIKitHostEnabled == true`, and confirm logs show the UIKit host path while preserving dismissal-completion behavior.
|
||||
- Keep the evidence log-based and separate default SwiftUI behavior from the explicit UIKit-flag path.
|
||||
|
||||
---
|
||||
|
||||
## Blockers
|
||||
|
||||
- No current source-level compile blocker after manual dependency/build handling. Runtime host-path validation is still pending.
|
||||
49
project-knowledge/06-daily/2026-05-13.md
Normal file
49
project-knowledge/06-daily/2026-05-13.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
type: daily
|
||||
project: fidelity
|
||||
date: 2026-05-13
|
||||
status: active
|
||||
focus: [pdiap-12284, pdiap-15836]
|
||||
work-items: [PDIAP-15838, PDIAP-12284, PDIAP-15836]
|
||||
blockers: []
|
||||
tags:
|
||||
- daily
|
||||
- fidelity
|
||||
updated: 2026-05-13
|
||||
---
|
||||
|
||||
# 2026-05-13
|
||||
|
||||
## Focus
|
||||
|
||||
- Continue runtime validation for the combined `PDIAP-12284` / `PDIAP-15836` branch.
|
||||
|
||||
---
|
||||
|
||||
## Work Done
|
||||
|
||||
- In yesterday's REST validation meeting, the initial production-environment check showed GraphQL when Fid4 was only pointed at production. The fuller meeting result was that iOS successfully validated REST after Raj enabled the production LaunchDarkly toggle for the specific production test user context, targeting the MID for the account tested with Bruce.
|
||||
|
||||
---
|
||||
|
||||
## Findings
|
||||
|
||||
- Do not summarize yesterday's REST validation only as "production still used GraphQL." The accurate framing is that plist-only production environment selection was insufficient, but production LaunchDarkly targeting for the tested user's MID activated REST successfully on iOS.
|
||||
|
||||
---
|
||||
|
||||
## Communication
|
||||
|
||||
- Mattermost sync refreshed May 12 context around `PDIAP-12284` / `PDIAP-15836` Jira structure, host-mode wording, and production REST-validation setup.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Focus today on the combined `PDIAP-12284` / `PDIAP-15836` runtime validation: default SwiftUI host path first, then explicit UIKit-host flag path.
|
||||
|
||||
---
|
||||
|
||||
## Blockers
|
||||
|
||||
- None currently.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
type: daily-index
|
||||
project: fidelity
|
||||
updated: 2026-05-05
|
||||
updated: 2026-05-08
|
||||
tags:
|
||||
- daily
|
||||
- map
|
||||
@@ -30,6 +30,8 @@ Promote durable facts into `project-knowledge/01-current/`, `project-knowledge/0
|
||||
- [2026-04-29](2026-04-29.md)
|
||||
- [2026-04-30](2026-04-30.md)
|
||||
- [2026-05-05](2026-05-05.md)
|
||||
- [2026-05-07](2026-05-07.md)
|
||||
- [2026-05-08](2026-05-08.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Standup Prompt
|
||||
|
||||
> **Format note (2026-05-13):** Mattermost requires a visible blank line before section headers like `Today:` and `Blockers:` for proper rendering. Always emit two newline characters between the previous bullet/list item and the next section header; never place `Today:` immediately after a list item.
|
||||
|
||||
Use `project-knowledge/01-current/current-work.md`, `project-knowledge/01-current/work-items.md`, the detailed files referenced from that active-work summary, `project-knowledge/03-context/project.md`, `project-knowledge/03-context/workstreams/index.md`, `project-knowledge/03-context/process/communication.md`, `project-knowledge/04-people/manager.md`, the previous workday communication context, today's daily note if present, and the latest available communication context.
|
||||
|
||||
Generate a standup update for the active project profile.
|
||||
@@ -22,7 +24,7 @@ Generate a standup update for the active project profile.
|
||||
## Output contract
|
||||
|
||||
- Use the greeting required by the active profile or command. If no greeting is specified, omit the greeting and start with the previous-work section.
|
||||
- Return the greeting, a previous-work section, and `Today:`
|
||||
- Return the greeting, a previous-work section, and `Today:` with a visible blank line before `Today:`
|
||||
- Include `Blockers:` only when there is a real blocker to report; omit the entire section when there are no blockers
|
||||
- Use `Yesterday:` only when the previous-work section truly refers to yesterday; otherwise use a truthful label such as `Last workday:` or the weekday/date when clearer
|
||||
- Return Markdown that is ready to copy/paste directly into the configured team communication tool
|
||||
@@ -39,6 +41,7 @@ Generate a standup update for the active project profile.
|
||||
- On Mondays, use Friday's work context unless a later prior day has Mattermost activity
|
||||
- If the previous calendar day has no work activity or is OOO/weekend, use the latest prior day with Mattermost activity
|
||||
- Treat the previous workday communication context as the primary source for the previous-work section
|
||||
- When the previous-workday evidence is itself a standup, do not promote that standup's nested `Yesterday` lines as work performed on the previous workday. Use same-day follow-up evidence, same-day daily logs, or explicit user-provided context for what actually happened on that date.
|
||||
- If a latest daily log exists, use its `Work Done` and same-day findings as the primary source for `Yesterday`; use current memory only to disambiguate, not to backfill unrelated older events
|
||||
- Do not reuse older investigation context from `current-work.md` as `Yesterday` unless the latest daily log or previous-workday communication confirms it happened on that workday
|
||||
- Prefer updates directly tied to active work items over side questions, context refreshes, or manager-only reminders
|
||||
@@ -91,6 +94,7 @@ Yesterday:
|
||||
- Update 1
|
||||
- Update 2
|
||||
|
||||
|
||||
Today:
|
||||
- ITEM-#### - Title
|
||||
- Next action 1
|
||||
@@ -100,3 +104,5 @@ Blockers:
|
||||
- ...
|
||||
|
||||
If there are no blockers, omit `Blockers:` entirely.
|
||||
|
||||
**Mattermost rendering note:** use a visible blank line (two newline characters) before `Today:` and before `Blockers:` so Mattermost renders the section headers as proper headings rather than inline text. Before returning the final answer, visually verify there is an empty line immediately above `Today:`.
|
||||
|
||||
194
scripts/iphone-photo-inbox/README.md
Normal file
194
scripts/iphone-photo-inbox/README.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# iPhone Photo Inbox
|
||||
|
||||
Local HTTP receiver for sending JPEGs from iPhone Shortcuts into Mac inboxes.
|
||||
The Shortcut sends a `profile`, and the Mac decides the destination folder and
|
||||
clipboard behavior.
|
||||
|
||||
## Profiles
|
||||
|
||||
`opencode`
|
||||
|
||||
- Saves to `ai/inbox/photos/`
|
||||
- Copies a terminal-safe path to the clipboard
|
||||
- Best for pasting into OpenCode running in a terminal
|
||||
|
||||
`mattermost`
|
||||
|
||||
- Saves to `~/Pictures/iPhone Inbox`
|
||||
- Copies the image data to the clipboard
|
||||
- Best for pasting directly into Mattermost
|
||||
|
||||
`general`
|
||||
|
||||
- Saves to `~/Pictures/iPhone Inbox`
|
||||
- Does not modify the clipboard
|
||||
- Useful for plain capture
|
||||
|
||||
All profiles show a macOS notification by default.
|
||||
|
||||
## Start the receiver
|
||||
|
||||
Recommended:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_TOKEN="choose-a-token" python3 scripts/iphone-photo-inbox/receiver.py
|
||||
```
|
||||
|
||||
The receiver listens on:
|
||||
|
||||
```text
|
||||
http://MAC_IP:8787/upload
|
||||
```
|
||||
|
||||
Find the Mac IP address on the current network:
|
||||
|
||||
```bash
|
||||
ipconfig getifaddr en0
|
||||
```
|
||||
|
||||
If that does not return an IP, use:
|
||||
|
||||
```bash
|
||||
ifconfig
|
||||
```
|
||||
|
||||
## Shortcut config
|
||||
|
||||
Use a Dictionary near the top of the Shortcut:
|
||||
|
||||
```text
|
||||
mac_ip: 192.168.11.186
|
||||
port: 8787
|
||||
token: choose-a-token
|
||||
profile: opencode
|
||||
```
|
||||
|
||||
Build the URL from the dictionary:
|
||||
|
||||
```text
|
||||
http://[mac_ip]:[port]/upload?token=[token]&profile=[profile]
|
||||
```
|
||||
|
||||
Use `profile: opencode` when the next paste target is OpenCode. Use
|
||||
`profile: mattermost` when the next paste target is Mattermost.
|
||||
|
||||
## Camera shortcut
|
||||
|
||||
```text
|
||||
Dictionary
|
||||
mac_ip: 192.168.11.186
|
||||
port: 8787
|
||||
token: choose-a-token
|
||||
profile: opencode
|
||||
|
||||
Text
|
||||
http://[mac_ip]:[port]/upload?token=[token]&profile=[profile]
|
||||
|
||||
Take Photo
|
||||
Show Camera Preview: On
|
||||
|
||||
Get Contents of URL
|
||||
URL: Text
|
||||
Method: POST
|
||||
Request Body: File
|
||||
File: Photo
|
||||
|
||||
Show Notification
|
||||
Sent to [profile]
|
||||
```
|
||||
|
||||
On the tested iPhone flow, `Take Photo` already produces a JPEG, so no
|
||||
conversion step is needed.
|
||||
|
||||
## Existing photos shortcut
|
||||
|
||||
Use this when sending existing images from Photos:
|
||||
|
||||
```text
|
||||
Receive Images and Media from Share Sheet
|
||||
Repeat with Each Item in Shortcut Input
|
||||
Convert Image
|
||||
Image: Repeat Item
|
||||
Format: JPEG
|
||||
Get Contents of URL
|
||||
URL: http://[mac_ip]:[port]/upload?token=[token]&profile=[profile]
|
||||
Method: POST
|
||||
Request Body: File
|
||||
File: Converted Image
|
||||
End Repeat
|
||||
Show Notification
|
||||
Sent to [profile]
|
||||
```
|
||||
|
||||
## Overrides
|
||||
|
||||
Profile folders:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_OPENCODE_DIR="/path/to/opencode/photos"
|
||||
IPHONE_PHOTO_MATTERMOST_DIR="$HOME/Pictures/iPhone Inbox"
|
||||
IPHONE_PHOTO_GENERAL_DIR="$HOME/Pictures/iPhone Inbox"
|
||||
```
|
||||
|
||||
Global folder override for all profiles:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_OUTPUT_DIR="$HOME/Pictures/iPhone Inbox" \
|
||||
IPHONE_PHOTO_TOKEN="choose-a-token" \
|
||||
python3 scripts/iphone-photo-inbox/receiver.py
|
||||
```
|
||||
|
||||
Default profile when the URL does not include `profile=`:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_PROFILE=mattermost \
|
||||
IPHONE_PHOTO_TOKEN="choose-a-token" \
|
||||
python3 scripts/iphone-photo-inbox/receiver.py
|
||||
```
|
||||
|
||||
Clipboard override for all profiles:
|
||||
|
||||
```bash
|
||||
IPHONE_PHOTO_CLIPBOARD=image
|
||||
IPHONE_PHOTO_CLIPBOARD=terminal-path
|
||||
IPHONE_PHOTO_CLIPBOARD=path
|
||||
IPHONE_PHOTO_CLIPBOARD=file
|
||||
IPHONE_PHOTO_CLIPBOARD=none
|
||||
```
|
||||
|
||||
Other useful options:
|
||||
|
||||
```bash
|
||||
python3 scripts/iphone-photo-inbox/receiver.py --no-notify
|
||||
python3 scripts/iphone-photo-inbox/receiver.py --reveal
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Startup should print each active profile:
|
||||
|
||||
```text
|
||||
profile opencode: dir=... clipboard=terminal-path notify=True reveal=False
|
||||
profile mattermost: dir=... clipboard=image notify=True reveal=False
|
||||
```
|
||||
|
||||
After upload, expect:
|
||||
|
||||
```text
|
||||
notification sent
|
||||
clipboard mode applied: terminal-path
|
||||
saved ... profile=opencode
|
||||
```
|
||||
|
||||
For Mattermost, expect:
|
||||
|
||||
```text
|
||||
clipboard mode applied: image
|
||||
```
|
||||
|
||||
If files arrive but clipboard/notifications do not behave as expected, check:
|
||||
|
||||
- The Shortcut URL includes the intended `profile=`.
|
||||
- The receiver log shows the expected profile.
|
||||
- macOS Focus/Do Not Disturb is not hiding notifications.
|
||||
- Terminal/Codex has permission for AppleScript automation if macOS prompts.
|
||||
338
scripts/iphone-photo-inbox/receiver.py
Executable file
338
scripts/iphone-photo-inbox/receiver.py
Executable file
@@ -0,0 +1,338 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Receive JPEG uploads from iPhone Shortcuts into local Mac inboxes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from http import HTTPStatus
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
|
||||
WORKSPACE_DIR = Path(__file__).resolve().parents[2]
|
||||
DEFAULT_OPENCODE_DIR = WORKSPACE_DIR / "ai" / "inbox" / "photos"
|
||||
DEFAULT_MATTERMOST_DIR = Path.home() / "Pictures" / "iPhone Inbox"
|
||||
|
||||
CLIPBOARD_NONE = "none"
|
||||
CLIPBOARD_IMAGE = "image"
|
||||
CLIPBOARD_PATH = "path"
|
||||
CLIPBOARD_TERMINAL_PATH = "terminal-path"
|
||||
CLIPBOARD_FILE = "file"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Profile:
|
||||
name: str
|
||||
output_dir: Path
|
||||
clipboard: str
|
||||
notify: bool = True
|
||||
reveal: bool = False
|
||||
|
||||
|
||||
def env_flag(name: str, default: bool = False) -> bool:
|
||||
value = os.getenv(name)
|
||||
if value is None:
|
||||
return default
|
||||
return value.strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def env_path(name: str, default: Path) -> Path:
|
||||
value = os.getenv(name)
|
||||
return Path(value).expanduser() if value else default
|
||||
|
||||
|
||||
def profile_defaults() -> dict[str, Profile]:
|
||||
opencode_dir = env_path("IPHONE_PHOTO_OPENCODE_DIR", DEFAULT_OPENCODE_DIR)
|
||||
mattermost_dir = env_path("IPHONE_PHOTO_MATTERMOST_DIR", DEFAULT_MATTERMOST_DIR)
|
||||
general_dir = env_path("IPHONE_PHOTO_GENERAL_DIR", DEFAULT_MATTERMOST_DIR)
|
||||
return {
|
||||
"opencode": Profile("opencode", opencode_dir, CLIPBOARD_TERMINAL_PATH),
|
||||
"mattermost": Profile("mattermost", mattermost_dir, CLIPBOARD_IMAGE),
|
||||
"general": Profile("general", general_dir, CLIPBOARD_NONE),
|
||||
}
|
||||
|
||||
|
||||
def timestamp() -> str:
|
||||
return dt.datetime.now().strftime("%Y%m%d-%H%M%S-%f")
|
||||
|
||||
|
||||
def unique_path(output_dir: Path) -> Path:
|
||||
candidate = output_dir / f"iphone-{timestamp()}.jpg"
|
||||
counter = 1
|
||||
while candidate.exists():
|
||||
candidate = output_dir / f"iphone-{timestamp()}-{counter}.jpg"
|
||||
counter += 1
|
||||
return candidate
|
||||
|
||||
|
||||
def looks_like_jpeg(data: bytes) -> bool:
|
||||
return data.startswith(b"\xff\xd8") and data.endswith(b"\xff\xd9")
|
||||
|
||||
|
||||
def normalize_profile(value: str) -> str:
|
||||
return value.strip().lower() or "opencode"
|
||||
|
||||
|
||||
def run_macos_action(command: list[str]) -> bool:
|
||||
try:
|
||||
result = subprocess.run(command, check=False, capture_output=True, text=True)
|
||||
except OSError as error:
|
||||
print(f"macOS action failed: {error}", flush=True)
|
||||
return False
|
||||
|
||||
if result.returncode != 0:
|
||||
error = result.stderr.strip() or result.stdout.strip() or f"exit {result.returncode}"
|
||||
print(f"macOS action failed: {error}", flush=True)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def notify(title: str, message: str) -> bool:
|
||||
script = f"display notification {json.dumps(message)} with title {json.dumps(title)}"
|
||||
return run_macos_action(["osascript", "-e", script])
|
||||
|
||||
|
||||
def reveal_in_finder(path: Path) -> bool:
|
||||
return run_macos_action(["open", "-R", str(path)])
|
||||
|
||||
|
||||
def copy_image_to_clipboard(path: Path) -> bool:
|
||||
script = f"set the clipboard to (read (POSIX file {json.dumps(str(path))}) as JPEG picture)"
|
||||
return run_macos_action(["osascript", "-e", script])
|
||||
|
||||
|
||||
def copy_file_to_clipboard(path: Path) -> bool:
|
||||
script = f"""
|
||||
set theFile to POSIX file {json.dumps(str(path))} as alias
|
||||
tell application "Finder"
|
||||
set the clipboard to {{theFile}}
|
||||
end tell
|
||||
"""
|
||||
return run_macos_action(["osascript", "-e", script])
|
||||
|
||||
|
||||
def copy_text_to_clipboard(text: str) -> bool:
|
||||
script = f"set the clipboard to {json.dumps(text)}"
|
||||
return run_macos_action(["osascript", "-e", script])
|
||||
|
||||
|
||||
def terminal_path_reference(path: Path) -> str:
|
||||
return shlex.quote(str(path))
|
||||
|
||||
|
||||
def apply_clipboard_mode(mode: str, path: Path) -> bool:
|
||||
if mode == CLIPBOARD_NONE:
|
||||
return True
|
||||
if mode == CLIPBOARD_IMAGE:
|
||||
return copy_image_to_clipboard(path)
|
||||
if mode == CLIPBOARD_PATH:
|
||||
return copy_text_to_clipboard(str(path))
|
||||
if mode == CLIPBOARD_TERMINAL_PATH:
|
||||
return copy_text_to_clipboard(terminal_path_reference(path))
|
||||
if mode == CLIPBOARD_FILE:
|
||||
return copy_file_to_clipboard(path)
|
||||
print(f"unsupported clipboard mode: {mode}", flush=True)
|
||||
return False
|
||||
|
||||
|
||||
class UploadHandler(BaseHTTPRequestHandler):
|
||||
server_version = "iPhonePhotoInbox/2.0"
|
||||
|
||||
def do_GET(self) -> None:
|
||||
if self.path == "/health":
|
||||
self.send_text(HTTPStatus.OK, "ok\n")
|
||||
return
|
||||
self.send_text(HTTPStatus.NOT_FOUND, "not found\n")
|
||||
|
||||
def do_POST(self) -> None:
|
||||
parsed = urlparse(self.path)
|
||||
if parsed.path != "/upload":
|
||||
self.send_text(HTTPStatus.NOT_FOUND, "not found\n")
|
||||
return
|
||||
|
||||
query = parse_qs(parsed.query)
|
||||
expected_token = self.server.upload_token
|
||||
supplied_token = query.get("token", [""])[0]
|
||||
if expected_token and supplied_token != expected_token:
|
||||
self.send_text(HTTPStatus.UNAUTHORIZED, "bad token\n")
|
||||
return
|
||||
|
||||
profile_name = normalize_profile(query.get("profile", [self.server.default_profile])[0])
|
||||
profile = self.server.profiles.get(profile_name)
|
||||
if profile is None:
|
||||
known = ", ".join(sorted(self.server.profiles))
|
||||
self.send_text(HTTPStatus.BAD_REQUEST, f"unknown profile: {profile_name}; expected one of {known}\n")
|
||||
return
|
||||
|
||||
content_length = self.headers.get("Content-Length")
|
||||
if content_length is None:
|
||||
self.send_text(HTTPStatus.LENGTH_REQUIRED, "missing content length\n")
|
||||
return
|
||||
|
||||
try:
|
||||
size = int(content_length)
|
||||
except ValueError:
|
||||
self.send_text(HTTPStatus.BAD_REQUEST, "invalid content length\n")
|
||||
return
|
||||
|
||||
if size <= 0 or size > self.server.max_bytes:
|
||||
self.send_text(HTTPStatus.REQUEST_ENTITY_TOO_LARGE, "invalid upload size\n")
|
||||
return
|
||||
|
||||
data = self.rfile.read(size)
|
||||
if not looks_like_jpeg(data):
|
||||
self.send_text(HTTPStatus.UNSUPPORTED_MEDIA_TYPE, "expected jpeg\n")
|
||||
return
|
||||
|
||||
output_dir = self.server.output_dir_override or profile.output_dir
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
target = unique_path(output_dir)
|
||||
with NamedTemporaryFile(dir=output_dir, delete=False) as tmp:
|
||||
tmp.write(data)
|
||||
temp_path = Path(tmp.name)
|
||||
temp_path.replace(target)
|
||||
|
||||
if profile.notify:
|
||||
if notify("iPhone Photo Inbox", f"{profile.name}: {target.name}"):
|
||||
print("notification sent", flush=True)
|
||||
if profile.reveal:
|
||||
if reveal_in_finder(target):
|
||||
print("revealed in Finder", flush=True)
|
||||
if apply_clipboard_mode(profile.clipboard, target):
|
||||
print(f"clipboard mode applied: {profile.clipboard}", flush=True)
|
||||
|
||||
self.send_text(HTTPStatus.CREATED, f"{target}\n")
|
||||
print(f"saved {target} profile={profile.name}", flush=True)
|
||||
|
||||
def log_message(self, format: str, *args: object) -> None:
|
||||
print(f"{self.address_string()} - {format % args}", flush=True)
|
||||
|
||||
def send_text(self, status: HTTPStatus, body: str) -> None:
|
||||
encoded = body.encode("utf-8")
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(encoded)))
|
||||
self.end_headers()
|
||||
self.wfile.write(encoded)
|
||||
|
||||
|
||||
class UploadServer(ThreadingHTTPServer):
|
||||
def __init__(
|
||||
self,
|
||||
server_address: tuple[str, int],
|
||||
handler_class: type[BaseHTTPRequestHandler],
|
||||
profiles: dict[str, Profile],
|
||||
default_profile: str,
|
||||
output_dir_override: Path | None,
|
||||
upload_token: str,
|
||||
max_bytes: int,
|
||||
) -> None:
|
||||
super().__init__(server_address, handler_class)
|
||||
self.profiles = profiles
|
||||
self.default_profile = default_profile
|
||||
self.output_dir_override = output_dir_override
|
||||
self.upload_token = upload_token
|
||||
self.max_bytes = max_bytes
|
||||
|
||||
|
||||
def apply_legacy_clipboard_overrides(profile: Profile) -> Profile:
|
||||
clipboard = profile.clipboard
|
||||
if env_flag("IPHONE_PHOTO_COPY"):
|
||||
clipboard = CLIPBOARD_IMAGE
|
||||
if env_flag("IPHONE_PHOTO_COPY_FILE"):
|
||||
clipboard = CLIPBOARD_FILE
|
||||
if env_flag("IPHONE_PHOTO_COPY_PATH"):
|
||||
clipboard = CLIPBOARD_PATH
|
||||
if env_flag("IPHONE_PHOTO_COPY_TERMINAL_PATH"):
|
||||
clipboard = CLIPBOARD_TERMINAL_PATH
|
||||
|
||||
notify_enabled = env_flag("IPHONE_PHOTO_NOTIFY", profile.notify)
|
||||
reveal_enabled = env_flag("IPHONE_PHOTO_REVEAL", profile.reveal)
|
||||
return Profile(profile.name, profile.output_dir, clipboard, notify_enabled, reveal_enabled)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--host", default=os.getenv("IPHONE_PHOTO_HOST", "0.0.0.0"))
|
||||
parser.add_argument("--port", type=int, default=int(os.getenv("IPHONE_PHOTO_PORT", "8787")))
|
||||
parser.add_argument("--profile", default=os.getenv("IPHONE_PHOTO_PROFILE", "opencode"))
|
||||
parser.add_argument("--output-dir", type=Path, default=os.getenv("IPHONE_PHOTO_OUTPUT_DIR"))
|
||||
parser.add_argument("--token", default=os.getenv("IPHONE_PHOTO_TOKEN", ""))
|
||||
parser.add_argument("--max-mb", type=int, default=int(os.getenv("IPHONE_PHOTO_MAX_MB", "30")))
|
||||
parser.add_argument(
|
||||
"--clipboard",
|
||||
choices=[CLIPBOARD_NONE, CLIPBOARD_IMAGE, CLIPBOARD_PATH, CLIPBOARD_TERMINAL_PATH, CLIPBOARD_FILE],
|
||||
default=os.getenv("IPHONE_PHOTO_CLIPBOARD"),
|
||||
)
|
||||
parser.add_argument("--no-notify", action="store_true", default=env_flag("IPHONE_PHOTO_NO_NOTIFY"))
|
||||
parser.add_argument("--reveal", action="store_true", default=env_flag("IPHONE_PHOTO_REVEAL"))
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def build_profiles(args: argparse.Namespace) -> dict[str, Profile]:
|
||||
profiles = {
|
||||
name: apply_legacy_clipboard_overrides(profile)
|
||||
for name, profile in profile_defaults().items()
|
||||
}
|
||||
|
||||
if args.clipboard:
|
||||
profiles = {
|
||||
name: Profile(profile.name, profile.output_dir, args.clipboard, profile.notify, profile.reveal)
|
||||
for name, profile in profiles.items()
|
||||
}
|
||||
if args.no_notify:
|
||||
profiles = {
|
||||
name: Profile(profile.name, profile.output_dir, profile.clipboard, False, profile.reveal)
|
||||
for name, profile in profiles.items()
|
||||
}
|
||||
if args.reveal:
|
||||
profiles = {
|
||||
name: Profile(profile.name, profile.output_dir, profile.clipboard, profile.notify, True)
|
||||
for name, profile in profiles.items()
|
||||
}
|
||||
return profiles
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
profiles = build_profiles(args)
|
||||
default_profile = normalize_profile(args.profile)
|
||||
if default_profile not in profiles:
|
||||
known = ", ".join(sorted(profiles))
|
||||
raise SystemExit(f"unknown default profile: {default_profile}; expected one of {known}")
|
||||
|
||||
output_dir_override = Path(args.output_dir).expanduser().resolve() if args.output_dir else None
|
||||
server = UploadServer(
|
||||
(args.host, args.port),
|
||||
UploadHandler,
|
||||
profiles,
|
||||
default_profile,
|
||||
output_dir_override,
|
||||
args.token,
|
||||
args.max_mb * 1024 * 1024,
|
||||
)
|
||||
print(f"listening on http://{args.host}:{args.port}/upload", flush=True)
|
||||
print(f"default profile: {default_profile}", flush=True)
|
||||
for profile in profiles.values():
|
||||
output_dir = output_dir_override or profile.output_dir
|
||||
print(
|
||||
f"profile {profile.name}: dir={output_dir.expanduser().resolve()} "
|
||||
f"clipboard={profile.clipboard} notify={profile.notify} reveal={profile.reveal}",
|
||||
flush=True,
|
||||
)
|
||||
if not args.token:
|
||||
print("warning: no token configured; anyone on this network can upload", flush=True)
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user