Sample probe pages
How to build and maintain Blazor sample pages that validate SDK wire-to-model mapping against the live FreeAgent API.
Audience: contributors implementing or retrofitting SDK endpoints.
Purpose
The sample app is a developer workbench, not a product UI. Each probe page should answer:
- Does the SDK call succeed against a real sandbox account?
- Does every model property map correctly to the wire JSON FreeAgent returns?
- Can a contributor spot mismatches, missing fields, and read-only attributes quickly?
Reference implementations:
| Pattern | Sample routes | SDK surface |
|---|---|---|
| Single-resource GET | /company |
CompanyService.GetCompanyAsync() |
| List + per-row mapping (paginated) | /contacts |
ContactService.ListAsync(...) |
| List + per-row mapping (full collection) | /categories |
CategoryService.ListAsync(...) |
| CRUD + seed data | /contacts/detail |
ContactService create/get/update/delete |
| CRUD + write-variant selector | /categories/detail |
CategoryService documented create/update variants |
Read those pages before adding a new resource.
Shared building blocks
Reuse these sample-only components and services. Do not duplicate probe logic on new pages.
| Asset | Location | Role |
|---|---|---|
EndpointProbeHeader |
Components/Shared/EndpointProbeHeader.razor |
Page title, call under test, API docs link, environment, endpoint path |
ModelProbeResults |
Components/Shared/ModelProbeResults.razor |
Mapping summary chips, mapping table, all-fields table, raw JSON |
ModelWireDiagnostics |
Services/ModelWireDiagnostics.cs |
Builds ModelProbeSnapshot with SDK-aware equivalence checks |
ApiDiagnosticsService |
Services/ApiDiagnosticsService.cs |
Fetches raw wire JSON for a path after an SDK call |
ApiErrorDiagnostics |
Components/Shared/ApiErrorDiagnostics.razor |
Shows raw error payload on API failures |
Wire-to-model comparison rules
ModelWireDiagnostics compares wire JSON to the deserialised SDK model by:
- Deserialising each wire field through the same property type the model uses.
- Using
JsonNumberHandling.AllowReadingFromStringso decimal fields returned as strings (for exampleaccount_balance) do not false-positive. - Classifying each property as Match, Mismatch, Missing in model, Model-only, or Not returned.
Only add model properties that exist on the FreeAgent API docs. Do not invent wire fields (for example contact_name is not part of the contacts API).
Page patterns
Single GET (one resource)
Follow Components/Pages/Company.razor:
- Render
EndpointProbeHeaderwith the SDK method,/v2/...path, andDocsUrlpointing at the official FreeAgent docs page for the resource. - On load (or button click), call the SDK service.
- Fetch the matching raw payload via
ApiDiagnosticsService. - Build
ModelProbeResultswithModelWireDiagnostics.Build(model, rawPayload, envelopeProperty). - Show
ApiErrorDiagnosticswhen the call fails.
List GET (collection)
Follow Components/Pages/Contacts.razor when the API paginates, or Components/Pages/Categories.razor when it returns a complete collection:
- Load through the SDK (
ListAsyncfor paginated or full-collection lists;ListAutoPagingAsyncwhen probing auto-pagination). - Fetch the list wire payload for the same query.
- For each row, offer Inspect mapping using
ModelWireDiagnostics.TryGetArrayItem(or the matching wire array for grouped collections) to pass the array item element intoModelProbeResults. - Expose filters the SDK supports (view, sort,
updated_since,sub_accounts, and so on). - Link to a detail/CRUD page with a documented identifier (
?id=or?nominal_code=).
CRUD (create, read, update, delete)
Follow Components/Pages/ContactDetail.razor for a single create/update shape, or Components/Pages/CategoryDetail.razor when the SDK exposes multiple write variants.
- Support
?id=deep links for get/update/delete. - After create or update, fetch
GET /resource/:idwire JSON so diagnostics reflect persisted state. - Show
MudProgressLinearwhile_loadingis true (bulk seed operations can take several seconds). - Disable action buttons during in-flight requests.
When the SDK exposes multiple write variants for the same resource (for example CreateIncomeCategoryAsync and CreateCostOfSalesCategoryAsync), the probe page must be able to invoke each public write method. A variant selector on one CRUD page is sufficient; exercising only one variant is not adequate as the standard.
Optional seed helpers (when useful for testing):
- Narrative demo data - Turpinverse canon is loaded on demand from the upstream Turpinverse repository (
canon/on GitHub; configure viaTurpinverse:RepositoryandTurpinverse:CanonRefin the sample app). Each seed operation refreshes canon from GitHub before upserting contacts. No Turpinverse canon is bundled in this repository; sample seed buttons require network access to GitHub at runtime. Seed helpers live insamples/FreeAgent.Client.BlazorSample/Services/Turpinverse/. FreeAgent contacts are B2B company records: seed one contact per Turpinverse organisation, with the organisationtradingNameand the primary contact persona’s name, email, and phone. Contact address fields are billing-oriented (they appear on invoices), so Turpinverse maps each organisation’sregisteredOffice- not persona home addresses. If you previously seeded persona-based demo contacts, re-run organisation sync to update overlapping records by email; leftover persona-only contacts are not removed automatically. Load additional canon files (for exampleinvoices.json) as SDK endpoints are implemented. - Full-field probe fixture - one contact with every writable attribute populated (
SampleContactFixtures,SampleContactSeeder). - Upsert by stable key - match existing records by email (contacts) or an equivalent natural key; update when canon changes (
ContactSeederSupport).
Navigation and honesty
- Register new routes in
Components/Layout/MainLayout.razor. - Add the page to the table in
samples/README.md. - Do not add sample UI for SDK endpoints that do not exist yet (see ADR-0006).
Checklist for a new resource
- SDK models, service, and tests land in the same PR as the sample pages.
- List probe page (if the API supports listing) with per-row mapping inspection.
- Detail or CRUD probe page (if the API supports get/create/update/delete).
- When multiple write variants exist, sample can invoke each SDK write method (variant selector or equivalent).
EndpointProbeHeaderon each page with accurateCallUnderTest,EndpointPath, andDocsUrl.ModelProbeResultsafter successful SDK calls;ApiErrorDiagnosticson failures.- Raw JSON panel present for full payload inspection.
- Navigation group added in
MainLayout.razor. docs/reference/api-coverage.mdandsamples/README.mdupdated.
Related documents
plan/IMPLEMENTING_ENDPOINTS.md- full endpoint checklist.agents/skills/implement-endpoint/SKILL.md- agent workflowadr/adr-0010-documented-operations-to-sdk-methods.md- operation heading → typed SDK methodAGENTS.md- sample sync policyadr/adr-0006-sample-app-living-reference.md