Journey 3 — FE-BE Integration Happy Path
Purpose: Wire the Customers list page (shipped on mock data in Journey 2) to a live
GET /api/customersendpoint via a separate Integration Work Item, command by command. Reading time: ~12 minutes · Audience: a developer running their first integration on Swifter · This journey assumes Journey 1 and Journey 2 are both done.
Integration is the journey that fails most expensively when it's done wrong — usually because someone tried to do it inside the original page WI. The discipline here is simple: the OpenAPI / Swagger contract and the reusable validation contract are fixed first, the FE and BE develop in parallel against them on their own work items, and when both are ready a third work item — the Integration WI — wires them together. The integration is small and almost mechanical when its inputs are right; every step below is one exact command or one exact click.
Worked example we will walk through
We continue the acme-crm example.
| Aspect | Value |
|---|---|
| Project | acme-crm |
| Modules | crm-fe (page on mocks from Journey 2) · crm-be (endpoint already shipped) |
| Page already shipped | 0042 Customers list, running on a mock service that returns the contract-shaped JSON |
| Backend endpoint already shipped | 0050 GET /api/customers — endpoint live, OpenAPI document available |
| Integration WI for this journey | 0051 Integration — wire Customers list to GET /api/customers |
| Frontend module API library | CustomerApi (consumed type) at .swifter/specs/crm-fe/api_library/CustomerApi/ |
Work-item codes are zero-padded numbers assigned by Swifter;
0042is the page WI from Journey 2. Your project's codes will differ.
By the end of this journey: 0051 is Done, the PR is merged, and Preview renders the Customers list from the live endpoint — no mocks in the page's dependency graph.
The Work Item text — paste this into your tracker
This is what 0051 actually contains. Copy it, replace the placeholders, run it.
## Integration target
Wire 0042 (Customers list, already shipped on mock data) to the endpoint below.
## Endpoint
GET /api/customers
## Swagger reference
See 0050 *(GET /api/customers backend endpoint)* — the Swagger document is attached there and has already been imported into the FE module as the `CustomerApi` API library entry.
## UI ↔ API mapping
Already defined in 0042 — the mapping table covers every visible value on the page, including computed fields and empty-state behaviour. Reuse as-is; no mapping changes in this WI.
## Scope-out
Page authoring is out of scope (already shipped in 0042). Backend endpoint authoring is out of scope (already shipped in 0050). This WI is **only** the wiring.
## Implementation Hints (optional)
- Function to update on the FE side: `fetchCustomers()` (currently calls the mock) → call `CustomerApi.GetCustomers`, unwrap `items[]`, return the list of Customer rows.
- API integration: source the backend base URL from the consumed API library configuration and externalise it through the environment config. All API calls must reference the environment variable — no relative paths, hardcoded URLs, localhost placeholders, or mock fallbacks when a consumed API is defined.
- API service wrappers are shared across multiple pages; locate them separately from page components.
- Storybook linkage: stories must import and render the real customers-list page component, providing mock API responses via HTTP interceptors at the application config level. Do not create inline wrapper components that duplicate the page's logic.
- Decommission the 0042 mock service: relocate to `__fixtures__/` per project guidelines (it stays available for Storybook + tests; the page must no longer reference it).
Because the page (0042) and the endpoint (0050) are already shipped, the mapping table, the example responses, and the Swagger document all exist in those WIs — the Integration WI only references them and adds the one fact that is new: which endpoint to wire (GET /api/customers). Three things to notice about the WI:
- The mapping table is the entire spec the developer agent uses — what's not in the table, the agent invents or skips. It is authored in the page WI (
0042) and reused as-is; computed fields (Remaining) and edge-case behaviour (emptypayments→"never") must already appear there explicitly. Without these lines the agent will mock the value. - The API library entry must already exist at
.swifter/specs/crm-fe/api_library/CustomerApi/before this journey runs. It is produced upstream as part of the BE WI's deliverables (0050); if it is missing, import it manually from the Swagger document before Step 3 below — there is no in-line agent command for that import yet. - The scope-out holds the line that this WI is only the wiring. Without it, the agent helpfully rebuilds the page on real data and the WI bloats past the canonical arc.
Acceptance criteria are not authored in the WI body — for an integration that doesn't change page behaviour, this WI inherits 0042's criteria; where new criteria are needed, /swifter-frontend-analyst:generate-acceptance-criteria derives them. The Implementation Hints block carries the non-functional rules (env-var externalisation, service-wrapper placement, Storybook linkage, mock relocation to __fixtures__/) drawn from common project conventions, which the developer agent also reads on every run.
Journey at a glance
Seventeen numbered steps follow. Same shape as Journey 2 — one action per step, inline verification, resume rule on failure.
Step 1 — Open WI 0051 in the Reqs tab
Action. In the Swifter sidebar, open project acme-crm → click Reqs. Find 0051 Integration — wire Customers list to GET /api/customers. Click it.
Why this step exists. The Integration WI is the only place the FE-to-real-API wiring is described.
Verification.
- URL ends with
/projects/acme-crm/work-items/0051(or the equivalent for your project). - WI header shows status BACKLOG (or READY) and a Start button.
- WI body contains the endpoint (
GET /api/customers), the Swagger reference (0050), the## UI ↔ API mappingtable, the scope-out, and the## Implementation Hintsblock above (including the FE function-update note). - WI links back to
0042(the page) and0050(the backend endpoint).
If a check fails: the WI code is wrong, or the WI body is missing the mapping table or one of the references. Fix the WI text in the Reqs tab before pressing Start — the mapping table is the entire spec the integration runs against.
Step 2 — Confirm the work item is ready before pressing Start
Action. Walk the readiness checklist below. Do not press Start until every box is checked.
Why this step exists. A row missing from the mapping table is a row Swifter will mock or skip — a UI value visible on the page but absent from the mapping table is the classic integration miss.
Verification.
- Endpoint line names the HTTP method and the path (
GET /api/customers, not just/api/customers). - Swagger reference points at the BE WI or attaches the OpenAPI document directly.
- UI ↔ API mapping table in the referenced
0042lists every UI element the page renders, including computed fields and empty-state values. - Computed fields in that table state the formula (
items[].principal − items[].paid) in the Transformation column — not the bare word "computed". - Scope-out explicitly says page authoring and BE endpoint authoring are out of scope.
- Acceptance criteria — either this WI carries its own (numbered, observable, covering happy path + empty/error states) or it inherits
0042's unchanged page behaviour.
If a check fails: edit the WI body in the Reqs tab. For a missing computed-field formula, name it explicitly (Remaining = principal − paid) — do not paste "computed" without the formula.
Step 3 — Confirm the API Library entry exists for CustomerApi
Action. Open the Dev tab and confirm these files exist:
.swifter/specs/crm-fe/api_library/CustomerApi/_library.yaml— hastype: consumedand at least one configuration entry withactive: trueand abaseUrl..swifter/specs/crm-fe/api_library/CustomerApi/_schemas.yaml.swifter/specs/crm-fe/api_library/CustomerApi/GetCustomers.yaml(one file per operation; the OpenAPI fragment forGET /api/customers).
Why this step exists. The Frontend Analyst's discover-and-read-api-libraries skill reads only what's on disk under api_library/. If the library is missing, the analyst can't map fetchCustomers() to a real operation. The library is produced upstream — typically as part of the backend work item's deliverables (0050), or imported from the BE's Swagger output — not by a command in this flow.
Verification.
- All three files above are present.
-
_library.yamlhastype: consumed(notproduced). -
_library.yaml's active configuration hasbaseUrlset to a usable URL (e.g.http://localhost:8080for local dev, or the project's dev environment). - The operation file
GetCustomers.yamldeclaresmethod: GET,path: /api/customers, and a response schema matching the contract's example JSON (attached to0050).
If a check fails: import the API library before continuing — from the BE WI (0050), place the operation YAML files under api_library/CustomerApi/. The agent will not proceed without the files in place.
Step 4 — Press Start to activate the session
Action. In the WI header, press Start.
Why this step exists. The Origin session and its chat already exist (created with the WI). Start activates the session and turns the App / Dev tabs from greyed-out to live. The session flips to IN_PROGRESS on the first agent message, and a session branch feature/0051_Origin is created once the agent pushes its first commit.
Verification.
- WI status moves from BACKLOG/READY to IN_PROGRESS.
- The session header shows the
Originchat. - App tab shows the existing customers-list page from
0042(unchanged base). - A branch
feature/0051_Originwill appear in the Dev tab header once the agent has pushed a commit.
If a check fails: Start may have been pressed already in a previous session. Look at the Origin chat for existing messages. If you genuinely need to restart, see Troubleshooting → Session-Naming Artefact.
Step 5 — Pick the Frontend Analyst in the agent picker
Action. In the Origin chat, open the Select agent picker, choose Frontend Analyst, then Confirm.
Why this step exists. The integration starts with a spec update — the analyst rewrites the page's functions.yaml so the fetchCustomers() function points at the API library operation instead of the mock. The developer agent will read the updated function spec in Step 10.
Verification.
- Chat header shows
Frontend Analystas the active agent. - Typing
/in the chat input surfaces the analyst's commands under the/swifter-frontend-analyst:namespace.
If a check fails: reopen the Select agent picker from the chat header.
Step 6 — Run /swifter-frontend-analyst:work-on-component-logic
Action. In the Origin chat:
/swifter-frontend-analyst:work-on-component-logic
Press Enter. No arguments — the command reads the integration target from the WI body (the 0042 Customers list) and identifies the mock-backed function to rewire (fetchCustomers) from the page's function spec. Wait for the explicit Approve gate.
When the agent invokes its discover-and-read-api-libraries skill, it lists the libraries it found under api_library/ and asks which one to map fetchCustomers() against. Pick CustomerApi.
Why this step exists. This is the one analyst-side change for an integration. The page's data model stays unchanged; the display rules stay unchanged; the function specification is updated so fetchCustomers() declares its dependency on CustomerApi.GetCustomers and applies the items[] unwrap transformation.
Verification. Read via the work-with-logic-specifications-toolset skill — never raw Read on the YAMLs:
- The
fetchCustomersfunction infunctions/functions.yaml(under.swifter/specs/crm-fe/components/customers-list/) now declares a dependency onCustomerApi.GetCustomers— recorded under the function'smeta.dependencies.apiswithapiLibraryName: CustomerApi,operationId: GetCustomers,method: GET,path: /api/customers. - The function describes the
items[]unwrap as part of its return-value transformation. - The page's
data_model.yamlis unchanged — the domain entities the page consumes are the same; only the data source changed. - No fabricated values: every field referenced in the function exists in
CustomerApi/_schemas.yaml.
If a check fails:
- If the agent reports the API library was empty: return to Step 3 and import the library.
- If the agent invented an operation ID not present in the library: the WI body's mapping table did not match the library's operation contract. Reconcile them in the WI body, then re-run the same command.
- For any other error, re-run
/swifter-frontend-analyst:work-on-component-logicin the same chat. Do not raw-editfunctions.yaml.
Step 7 — Review the updated function spec in the App tab
Action. Open the App tab → the Logic sub-view, and open the customers-list function fetchCustomers.
Why this step exists. This is the spec-level review before the developer agent rewrites the function's source code — the place to catch a wrong transformation before it becomes code.
Verification.
- The function's API dependency shows
CustomerApi.GetCustomers, matching the0042mapping table. - The function's return value describes the list of Customer rows (post-unwrap of
items[]). - The transformations listed for
status,payments[-1].date, andprincipal − paidmatch the0042mapping table verbatim — no paraphrasing. - The function's error behaviour states the snackbar message —
"Could not load customers."per the WI's user-facing copy.
If a check fails: type a refinement in the same chat (e.g. the status transformation should map CLOSED to grey "Closed", not just "Closed"). The analyst regenerates the affected slice of functions.yaml and re-surfaces the Approve gate.
Step 8 — Approve the spec and open a development chat
Action. Type Approve in the Origin chat. Then open a new chat: in the chat sidebar of the WI header, click + New chat → name it development (or accept Swifter's auto-generated name).
Why this step exists. Same rationale as Journey 2 — the developer benefits from a clean chat free of the analyst's spec-discovery context.
Verification.
- The
Originchat shows the analyst's final report with paths to the updatedfunctions.yaml. - A new chat appears in the chat sidebar, attached to the same WI
0051.
If a check fails: the + New chat button is sometimes hidden behind the session-status panel — collapse the panel and try again.
Step 9 — Switch the new chat to Frontend Developer
Action. In the development chat, click the agent picker → select Frontend Developer → Confirm.
Why this step exists. The developer agent has the toolsets to read the analyst's updated functions.yaml, the framework knowledge to emit the correct API-client wiring, and access to the externalize-consumed-api-config skill — none of which Frontend Analyst runs.
Verification.
- Chat header shows
Frontend Developer. - Typing
/surfaces the developer's commands under the/swifter-frontend-developer:namespace.
If a check fails: reopen the picker.
Step 10 — Run /swifter-frontend-developer:work-on-component-logic customers-list functions
Action. In the development chat:
/swifter-frontend-developer:work-on-component-logic customers-list functions
Press Enter. The first argument is the page name (kebab-case). The second argument functions is the narrow scope — regenerate only the functions module, leaving types / validators / display-rules untouched.
Why this step exists. The analyst rewrote the function spec in Step 6; this command rewrites the function's source module (customers-list.logic.ts or framework-equivalent) to match — replacing the mock-data call with a call to the API library client. Narrow scope avoids regenerating types and display rules that haven't changed.
Verification. Under PROJECT_GIT_ROOT/<logic-modules-dir>/customers-list/:
-
customers-list.logic.tsexists and was rewritten in this run (generation-metadata comment timestamp updated). - The implementation of
fetchCustomers()calls the API library client (it imports from the project's API-client module — not from the mock service file). - The
items[]unwrap is applied insidefetchCustomers()before returning the list. - The function returns the same type (
Customer[]) the page already consumes — confirmcustomers-list.types.tsis unchanged. -
customers-list.display-rules.tsandcustomers-list.validation.tsare unchanged.
Under .swifter/specs/crm-fe/components/customers-list/:
-
logic-mapping.jsonupdated withscope: functionsfor this run and an entry underspec_artifacts_applied.functions.
Build: the command runs the project build and reports green (up to three fix-and-retry cycles on failure).
If a check fails: the agent emits a resume command. Paste it back. Do not hand-edit customers-list.logic.ts.
Step 11 — Run /swifter-frontend-developer:generate-page customers-list
Action. Same development chat:
/swifter-frontend-developer:generate-page customers-list
Press Enter. The command re-reads the routing spec (unchanged), the updated function module from Step 10, the framework knowledge, and the API library. At its step 6 it applies the externalize-consumed-api-config skill — which scans the API library for type: consumed entries and writes:
- An example config file (
.env.examplefor React/Vue on Vite,src/environments/environment.ts.examplefor Angular). - A centralized config module (
src/config.tsor framework-equivalent) that reads each var through a fail-fastrequireEnvhelper.
Why this step exists. Re-generating the page picks up the new API-client wiring at the page level (route guards, data fetching, error states) and writes the .env.example placeholder for the operator to populate. The agent never writes the real .env — that is the operator's job in Step 12.
Verification. Under PROJECT_GIT_ROOT:
- The page wrapper file (e.g.
app/customers/page.tsx) was regenerated. Its data-fetching call reads from the Step-10customers-list.logic.ts— no direct API calls or hardcoded URLs in the page wrapper itself. - A centralized config module (e.g.
src/config.ts) exists, exports a typed config object, and validates required env vars at startup with explicit error messages. - No source file contains a hardcoded
baseUrlfrom_library.yaml. All API-client wiring imports base URLs from the config module. -
.env.example(or framework-equivalent*.examplefile) contains an entry forCustomerApiusing the placeholder format{{apilibrary:CustomerApi:baseUrl}}(the build resolves this at deploy time; the agent never substitutes the real URL). -
.env(Vite) — orsrc/environments/environment.ts+environment.development.ts(Angular) — is listed in.gitignore.
Under .swifter/specs/crm-fe/components/customers-list/:
-
page-mapping.jsonupdated;spec_artifacts_appliednow listsfunctionswith the API dependency.
Build:
- Project build: green (reported in chat).
If a check fails: the command's own self-recovery handles common build errors. If it surfaces an unfixable failure, the resume command is /swifter-frontend-developer:generate-page customers-list — re-run in the same chat after fixing the root cause. Do not open a new chat.
Step 12 — Set the API base URL in the real config file
Action. Create the local-only config file the agent did not create (it is gitignored):
- React/Vite or Vue/Vite:
.envat the project root. - Angular:
src/environments/environment.ts(andenvironment.development.tsif used).
Copy the lines from .env.example (or environment.ts.example) and replace the {{apilibrary:CustomerApi:baseUrl}} placeholder with the real base URL of the live BE — for the worked example, http://localhost:8080 if crm-be runs locally, or the dev-environment URL the BE team published.
For Vite (the env-var name is derived from the API library name, CustomerApi → VITE_CUSTOMER_API_BASE_URL):
VITE_CUSTOMER_API_BASE_URL=http://localhost:8080
For Angular:
export const environment = {
production: false,
customerApi: { baseUrl: 'http://localhost:8080' },
};
Why this step exists. The agent deliberately never writes the real .env — base URLs are environment-specific and the build pipeline resolves them. Skipping this step causes Step 14 (Preview) to fail with the fail-fast error Required environment variable VITE_CUSTOMER_API_BASE_URL is not set.
Verification.
-
.env(or framework-equivalent) exists, is in.gitignore, and contains the resolved URL. - The URL is reachable: run a sanity check against the BE (
curl http://localhost:8080/api/customersreturns the contract-shaped JSON).
If a check fails: the BE may not be running locally, or the URL is wrong. Resolve before Step 14 — Preview will not work without a reachable backend.
Step 13 — Review the generated code in the Dev tab
Action. Open the Dev tab — a code browser with a file tree. From the tree, open each changed file:
- The page wrapper (e.g.
app/customers/page.tsx). customers-list.logic.ts.src/config.ts(or framework-equivalent centralized config module)..env.example(or framework-equivalent)..gitignore.
Why this step exists. The agent honoured the spec, but it could have left a stray import to the old mock service, added an unauthorised dependency (an HTTP client the project didn't already have), or hardcoded a URL it shouldn't have.
Verification.
- No imports of the old mock service anywhere in the page or logic modules. Search the changed files for the mock file name (e.g.
customers-mock.ts) — zero hits. -
package.jsonand the lockfile are unchanged — unless the project did not previously have an HTTP client, in which case the WI must have authorised the addition. -
src/config.tsreads the env var through therequireEnvfail-fast helper (or framework-equivalent), not aprocess.env.X || 'http://...'fallback. - The old mock service file (
customers-mock.tsor similar) was either deleted or relocated to__fixtures__/per the WI's Implementation Hints and project guidelines. If the agent did not relocate it, do so now via the Dev tab's file rename action (and add a one-line entry in the WI's session log noting the relocation).
If a check fails: return to the development chat and describe the specific issue (e.g. the page still imports from src/mocks/customers-mock.ts — remove the import and the file). Same chat, not a new one.
Step 14 — Walk the page states in Preview against the live endpoint
Action. Click the Preview button in the work-item header. It opens the running app from the session branch in an overlay panel with a path field — enter /customers. With the BE running on the URL you set in Step 12, walk these states:
- Populated. The endpoint returns its current data set → rows render with name, status badge, and last-payment-date columns from real data.
- Empty. Point the endpoint at an empty fixture (or use the BE's seeded empty state) → empty-state message "No customers yet" appears.
- Status colours. Confirm at least one row of each status renders the right colour and label per the
0042mapping table. - Error. Stop the BE (or point
.envat a deliberately bad URL, then restore) → page surfaces the snackbar"Could not load customers.".
Why this step exists. Storybook and the App tab show the page against mocks; only Preview against the real endpoint proves the wiring works end-to-end. This step is the whole point of the integration WI.
Verification.
- Each row of the
0042mapping table renders correctly: name, status badge colour+label, remaining amount (computed), last-payment date (or"never"when payments are empty). - The error snackbar (
"Could not load customers.") appears when the endpoint is unreachable. - The page's empty state appears when the endpoint returns an empty list.
- The network panel in Preview's developer tools shows the request hitting
GET /api/customersat the configured base URL — not a mock. - No console errors during the happy path.
- The fail-fast
requireEnvfromsrc/config.tsdid not throw at startup (the env var is set).
If a check fails: narrow which check failed:
- Wrong field value (e.g. badge colour off) → return to Step 6 (analyst), fix the function spec's transformation, then re-run Steps 6 → 11.
- Field missing entirely → that row was missing from the mapping table; fix the WI body, then re-run Step 6.
- Network call to wrong URL → re-check Step 12.
- Network call to mock instead of API → re-check Step 13 and remove the stray mock import.
Step 15 — Press Create pull request
Action. In the WI header (Reqs tab), press Create pull request.
Why this step exists. Pressing the button immediately opens the PR from the session branch into main with a server-generated title and description, and surfaces the PR URL in the header.
Verification.
- WI header shows a PR number and a clickable PR URL.
- Source branch is the integration session branch (
feature/0051_Origin); target ismain. - The changed files are the logic module, the page wrapper, the centralized config module,
.env.example,.gitignore, and the relocated/deleted mock service. -
0042(the page WI) is not touched beyond the relocation of its mock service — page authoring stayed out of scope.
If a check fails: the most common cause is uncommitted changes from Step 13's manual mock relocation. Return to the development chat, ask the agent to commit outstanding changes, then re-press Create pull request.
Step 16 — Check pull request status until CI is green
Action. Press Check pull request status in the WI header. Swifter polls the provider and refreshes the latest review state and CI status.
Why this step exists. Complete is blocked until CI passes and required reviews are approved.
Verification.
- WI status panel shows
CI: green. - Required reviewer count is met.
- No
Changes requestedreviews outstanding.
If a check fails: open the PR in the provider UI, look at the failed CI job's log, return to the same development chat, share the failure log. The agent iterates against the same branch — do not open a new session, do not open a new chat.
Step 17 — Press Complete
Action. In the WI header, press Complete.
Why this step exists. Complete merges the PR into main, closes the session, and moves 0051 to DONE. 0042's status is unchanged — it is already Done from its own happy path. Only the Integration WI moves.
Verification.
-
0051status is DONE. - PR shows Merged in the provider UI.
- On
main, Preview at/customersrenders the live endpoint's data. -
0042's status remains DONE — unchanged.
If a check fails: Complete is blocked when CI is not green or reviews are missing — return to Step 16. If Origin shows ABANDONED but the development session shows DONE, that is the project baseline pattern — see Troubleshooting → Session-Naming Artefact.
Exit conditions
A clean integration exits with:
0051status is DONE;0042status unchanged.- The page renders real data from the live endpoint in Preview; the mock service is no longer in the page's dependency graph.
- API-client wiring, mapper logic, the centralized config module, and
.env.exampleare committed on the integration session's branch and merged via the integration PR. - The mapping table from the Integration WI is discoverable from code — either inlined as a comment on
fetchCustomers()or referenced from the function spec. - The original mock service file is either deleted or relocated to
__fixtures__/, per project guidelines.
A real engagement saw an integration WI finish in a single session because its inputs were a one-line path and an attached Swagger reference. That single-session arc is the target.
Note on BE mock data and the real database
The pilot ran with the backend serving in-memory file mocks rather than a real database; this is an acceptable first step for prototypes and parallel FE development (for External API proxy projects, the BE mock data is retrieved from the DEV external APIs or an internal mock-server setup instead). There is no separate DB-migration work item: if a database is present in the project, the connection parameters are defined in the initial project setup scope and then only changed per environment via environment variables. Project guidelines still specify where mock files live and how they are loaded. The Integration WI itself is mock-aware: it wires the FE to whatever the BE serves on the agreed endpoint, file-backed or DB-backed.