Skip to content

Dart lane 0: shared contract (dart in TaskProvider) #2

Open

Description

@Interstellar-code

Lane 0: shared contract

Base: upstream main 51e183a08e0 (fork fast-forwarded 2026-10-31). Paths and lines below are from that base; see "Revision 2026-10-31" at the end.

Goal: make dart a first-class value everywhere the shared layer enumerates providers, make every compile-forced site compile (desktop and mobile), publish the RPC contract and error codes that Lanes A, B, C and D build against, and keep existing profiles, new-profile defaults and mobile goldens unchanged. No behaviour yet.
Delivers: G3 (link model), G5 (mixed versions), G6 (small upstream diff).
Depends on: nothing to start (Phase 0's read-only findings are in #8). Lane P's later findings arrive as additions before this lane merges. Merges first; every other lane starts from its branch.

Files owned (re-check every line number before editing)

New: src/shared/dart/dart-types.ts, dart-issue-url.ts (parses Dart task URLs and validates htmlUrl: https only and host app.dartai.com, so a hostile or malformed URL in an API reply is never stored in a link or opened), dart-capabilities.ts (defines the capability constant dart.provider.v1 only; Lane A adds it to the advertised list in src/shared/task-source-runtime-capabilities.ts when the methods exist, so no server advertises Dart without methods), dart-provider-payload.ts (strips 'dart' from settings payloads for a host lacking the capability; pending open decision D2 in #8: if D2 is (a), skip this file and its test); src/shared/rpc-contract/dart-params.ts (verified: config/scripts/generate-rpc-params-catalog.mjs:38 globs rpc-contract/*.ts flat).
Edit, shared layer:

  1. src/shared/task-providers.ts (union :1, TASK_PROVIDERS :3, isTaskProviderAvailable :89-105 dart branch returning true like Jira at :100, a separate DEFAULT_VISIBLE_TASK_PROVIDERS kept at the original four, both fallbacks of normalizeVisibleTaskProviders at :35 and :49 returning the default list). Other tests pin the four providers too (src/main/persistence/applying-settings/persistence-settings-ui-defaults.test.ts, src/main/persistence/applying-settings/persistence-source-control-ai-migration.test.ts); they must keep passing unchanged.
  2. src/shared/default-global-settings.ts:216 and src/main/runtime/runtime-client-settings.ts:83 (both use [...TASK_PROVIDERS] today; switch to the default list). Both files are hot upstream (11 and 12 commits in a week): keep each hunk to one line. src/shared/task-providers.test.ts pins the empty fallback to the four providers and must stay unchanged.
  3. src/shared/task-provider-identity.ts (untouched upstream since the first draft): the type; all four switches (normalizeTaskProviderIdentity at :49-82, case 'jira' at :75; isStoredTaskProviderIdentity at :96-115, :113; taskProviderIdentityCachePart at :150-160, :158), the fields table at :118-123, and areTaskProviderIdentitiesEqual at :125-143. Missing the arm at :75 makes every dart providerIdentity normalize to null. workspace-linked-item.ts equality (:18-20) must also compare dartIdentifier.
  4. src/shared/task-source-context.ts:239, src/shared/workspace-linked-item.ts:34 and :46-61, src/shared/workspace-attachment-normalization.ts:25-102,294, src/shared/workspace-reference-identity.ts, src/shared/workspace-attachments.ts, workspace-attachment-mutation.ts, workspace-linked-item-source-context.ts, src/shared/workspace-name.ts:50, src/shared/new-workspace/workspace-source.ts, smart-workspace-*.ts: no edit, Dart is not a Create-Workspace smart-source mode in v1 (SmartNameMode at smart-workspace-source-results.ts:21 is a literal union; see Dart AI task provider (core extension): parent plan #8 "Out"). The single edit at workspace-linked-item.ts:34 covers RPC validation, IPC validation and persistence, because workspace-linked-item-schema.ts:5-14 and every persistence path call normalizeWorkspaceLinkedItem; do not hunt for more call sites. Identifier-drop sites (a dart identifier is silently lost unless each copies dartIdentifier): workspace-attachment-mutation.ts:81-94 (the stripUndefinedFields allowlist, :88), workspace-linked-item-source-context.ts:11-22, workspace-name.ts around :50-70 and :159-210.
    4.1. workspace-attachment-normalization.ts, required edits with one test each: :36 (the non-issue rejection names only linear/jira), :47 (number === 0 is allowed only for linear/jira with an identifier, so a Dart attachment is dropped), :101-102 (the attachment identity uses the identifier only for linear/jira, so Dart keys collapse to number), :293-294 (toLinkedItem copies the identifier only for jira). Tests: a Dart attachment with number: 0 survives; its dedupe key uses the identifier; the linked item carries dartIdentifier.
    4.2. src/shared/new-workspace/workspace-source.ts:81-96 (switch point 14): add a dartIdentifier arm before the number === 0 Linear fallback; test the round trip normalizeWorkspaceAttachment → normalizeWorkspaceLinkedItem → getWorkspaceSourceProvider → buildWorkspaceSourceSelection.
    4.3. src/shared/workspace-reference-identity.ts (switch point 15): add app.dartai.com to HOST_PROVIDERS (:12-19), a Dart task route (parse https://app.dartai.com/t/<id>-<slug>, reusing dart-issue-url.ts), and dart arms in the identifier and externalUrl switches (:164-194). Tests: a Dart URL parses to provider dart with the 12-character id; an unknown Dart route is rejected cleanly.
    4.4. workspace-linked-item.ts: test that two Dart links with number: 0 and different dartIdentifier are not equal, and that a Dart link survives save and reload.
  5. src/shared/worktree/types.ts:24,40 and :38-46 (linked item, decision 3).
  6. src/shared/rpc-contract/client-settings-params.ts:15,109-110: verify only, no edit (TaskProviderParam is derived from isTaskProvider, so it accepts 'dart' once item 1 lands). Edit: src/shared/rpc-contract/automation-params.ts:65-100 (dart arm beside the jira arm at :96) and :109 (the hand-listed z.enum), src/shared/orcad-migration/orcad-migration-dormant-automation-validation.ts:245 (hand-listed provider array).
  7. New 2026-10-31, provider error codes (upstream fix(i18n): localize provider errors and shared/native UI labels stablyai/orca#34680): add this fixed seed set of dart.* codes — dart.notConnected, dart.connectFailed, dart.unauthorized, dart.loadFailed, dart.taskNotFound, dart.createFailed, dart.updateFailed, dart.commentFailed, dart.badRequest, dart.rateLimited, dart.invalidTaskUrl — plus any extra code Lane P's findings add (Lane P only adds, it never blocks this step), to TaskProviderErrorCode in src/shared/task-provider-failure.ts, following the jira.* entries. src/renderer/src/lib/task-provider-error-copy.ts has COPY: Record<TaskProviderErrorCode, ...>, so the new codes force copy entries there: add plain English entries with taskProviders.dart.* translate keys, as COPY does for the other providers, under the compile-forced rule; after Lane 0 merges, the file belongs to Lane B for the final wording (a hand-over in sequence, not shared). No census test: COPY is a Record<TaskProviderErrorCode, ...>, so a missing entry already fails pnpm tc.
    Compile-forced rule (the ownership fix): Lane 0 owns EVERY edit the compiler forces after 'dart' joins TaskProvider, in any tree (shared, main, renderer, mobile), provided the edit only adds a dart key, arm or neutral value. Lanes A to D own behaviour. A forced site that needs real behaviour gets a neutral placeholder here and a note in the lane report naming the lane that fills it in. This is what makes Lane 0's typecheck gates passable.
    Edit, named exceptions in other trees:
  8. src/renderer/src/lib/agent-feature-skill-names.ts (add ORCA_DART_SKILL_NAME, so Lane B's install row compiles without waiting for Lane C).
  9. src/preload/api-types.ts: declare the DartApi type and the dart: DartApi member of PreloadApi. Because src/preload/index.ts:197 ends with } satisfies PreloadApi, the new member breaks the node typecheck until the preload object has it, so Lane 0 also adds a compile-forced stub: new src/preload/api/dart-bridge.ts exporting a dartApi whose every method resolves to taskProviderFailure('dart.notConnected'), wired as dart: dartApi in src/preload/index.ts. Both files then pass to Lane A in sequence (same hand-over rule as task-provider-error-copy.ts). The web preload needs nothing (web-preload-api.ts:68 returns Partial<PreloadApi>). Without this item, Lane B cannot type-check before Lane A merges.
  10. Compile-forced sites, expected to include (verify by typechecking; the compiler on the new base is the real list):
    10.1. Renderer: components/settings/use-task-source-provider-readiness.ts:17 (a literal Record<TaskProvider, TaskProviderReadiness>), components/settings/task-source-setup-state.ts (Record<TaskProvider, ...> at :103,121,131,143), components/settings/TasksPane.tsx PROVIDER_META at :37 (add a dart entry; the card body is Lane B's), components/task-source-context-summary.ts (jira at :58,71,73,220), components/task-page/use-task-page-repo-selection.ts, components/sidebar/workspace-attachment-source-result.ts (jira :55,56,82,83), lib/linked-work-item-context.ts, components/task-page/task-page-list-chrome-visibility.ts (a switch (taskSource) at :23-32 with four arms and no default; if the compiler does not force it, treat it as a silent switch and add a hasDartDetail arm anyway, with a unit test per provider, otherwise list chrome stays visible over an open Dart detail), components/automations/automation-source-display.ts:59,76, components/automations/automation-target-availability.ts:241 (exhaustive switches with no default; give dart a neutral arm), store/slices/ui/ui-slice-contract-core.ts:88-96 (the NewWorkspaceDraft.linkedWorkItem provider union and identifier fields: add 'dart' and dartIdentifier).
    10.2. Mobile (mobile/src changed in about 500 files since the first draft): tasks/mobile-task-view-options.tsx:7,14 (PROVIDER_LABELS, PROVIDER_EMPTY_LABELS, still Record<TaskProvider, string>), and the one TaskProviderLogo.tsx call site that passes a TaskProvider-typed value: tasks/mobile-tasks-create-drawers.tsx:60 (its provider comes from mobile-tasks-hydrated-view-state.ts:15). Lane 0 passes a neutral, non-Linear kind value there and writes no filtering or other logic; Lane D's first step replaces it and widens the logo kind. Not forced, so not Lane 0's: components/SmartSourceModeIcon.tsx:8 (typed by the literal union in mobile-smart-source-modes.ts:7) and host-screen/workspace-details-sheet.tsx:176-178 (already narrowed by item.provider === 'linear' || item.provider === 'jira'); Lane D checks them. No longer forced: mobile-tasks-screen-chrome.tsx (REFRESH_LABELS became a function returning an object literal) and mobile-tasks-provider-controls.tsx (no longer uses TaskProviderLogo). Deleted upstream (feat(mobile): rebuild Home around what needs you, with actions pinned at the bottom stablyai/orca#36520, Home rebuilt 2026-10-31): mobile/src/home/MobileHomeTasksCard.tsx and mobile-home-host-requests.ts.
    10.3. The test that pins the old four providers in a compile-forced file (src/renderer/src/components/settings/task-source-setup-state.test.ts) is updated only where the compile-forced change requires it.
  11. Classify in the lane report (may carry a Jira-only arm; decide dart arm or "not needed"): src/shared/jira/jira-review-title.ts (reads jiraIdentifier), components/task-page/use-task-page-provider-metadata.ts (has jira branches), components/task-page/use-task-page-source-summary.ts (a Partial<Record<TaskProvider, ...>> at :38-40, so not compile-forced). Excluded as out of scope (Cmd-J), recorded as "excluded, documented", not as compile-forced edits: components/cmd-j/task-search-requests.ts (its provider loop is a literal ['github', 'gitlab'] as const at :56; Cmd-J will not search Dart in v1), worktree-palette-task-url-match.ts, cmd-j-evaluation-fixture.ts.
  12. New (OMP review), the Dart identity field list: write it in dart-types.ts before step 4, and cite it from Lanes A, B and D. Minimum: the signed-in user id from GET /me plus whatever workspace scope Lane P finds; define the taskProviderIdentityCachePart format from those fields. Without one written list, Lanes A and B build different identities and areTaskProviderIdentitiesEqual silently returns false (duplicate attachments, cache misses).

Must not touch

Main client, RPC registration, the advertised capability list in task-source-runtime-capabilities.ts (Lane A's one shared-file exception) and the generated params catalog (Lane A), UI and mobile behaviour beyond the compile-forced rule, CLI. Do not extend ProjectProviderIdentity. Do not add linkedDartIssue* fields. integration-credential-errors.ts is not needed: Lane A uses createEncryptedApiKeyFileStore, which has its own label.

Decisions (write them in the lane report)

  1. Default visibility: dart is opt-in from Settings > Task Sources; defaults stay the original four everywhere (items 1 and 2). No migration, no edit to src/main/persistence/loading-store/prepare-loaded-profile-settings.ts. The mobile goldens contain the literal ["github","linear"] and are unaffected; no regeneration. Verify by running the mobile golden tests. Note: TasksPane.tsx and getStalledVisibleTaskProviders(TASK_PROVIDERS, ...) iterate all TASK_PROVIDERS, so a dart card shows in Task Sources while not visible by default. That is intended; Lane B adds a UI test for it.
  2. Protocol: adding methods and an optional capability needs no RUNTIME_PROTOCOL_VERSION bump. Verified on the new base: version is still 3, minimum client and server still 2, and src/shared/protocol-version.ts:41-43 still says new methods and optional fields are additive.
  3. Linked item: WorkspaceLinkedItem requires a numeric number; Dart ids are 12-character strings. Follow Jira: number: 0 plus optional dartIdentifier on the linked-item and attachment types, copied at workspace-linked-item.ts:46-61 and compared in equality.
  4. Availability: isTaskProviderAvailable('dart') is true (connect happens from the Tasks surface, like Jira). Mobile hides dart until Lane D ships reads (Lane D owns that filter).
  5. Errors: Dart uses the shared TaskProviderErrorCode convention (item 7), not free-text error strings, so its messages are localized like Jira's and Linear's.

Steps

  1. Add 'dart'; run pnpm tc (node, cli, web) and the mobile typecheck (typecheck and typecheck:tests in mobile/package.json; pnpm tc does not cover mobile); fix compile-forced sites per item 10. Record the real compiler list in the lane report and compare it with item 10; every difference is a finding.
  2. Fix the shared-layer silent switch points from the parent plan (items 1 to 6, 8, 11, 14 and 15; items 7, 9, 10, 12, 13, 16 and 17 belong to Lanes A and B, except item 12's PROVIDER_META entry, which is compile-forced and yours).
  3. Define the dart identity (Dart ids are strings, from Lane P) with normalize, equality and cache-part arms.
  4. Write the RPC contract in dart-params.ts (method-name constants and param and reply schemas): dart.status|connect|disconnect|testConnection|config|listTasks|getTask|listComments|createTask|updateTask|addComment, and the rules from Lane P (below). Failures are returned as taskProviderFailure results with the dart.* codes from item 7. State that no layer may log dart.connect params. State a permission per method, following Jira (src/main/runtime/rpc/methods/jira.ts:71-90): dart.connect, dart.disconnect and dart.testConnection are accounts-admin; reads and writes are workspace. Otherwise a paired phone with workspace access could change the credential. "Mine" has one source: dart.status and dart.connect return the signed-in user's id and name (from GET /me), and listTasks takes an optional assigneeId; Lane B and Lane C both use that. The dartboard filter is unconfirmed (it returned 0 for every value tried): the contract says the host falls back to filtering on the returned dartboard field if Lane P does not confirm the server filter. Write methods (createTask, updateTask, addComment) are marked "unverified against the live API" until Lane P's write checks have run.
  5. Add the error codes (item 7). Only if D2 is (b): add the stripping helper with its unit test.
    Do not regenerate the RPC params catalog: it reads the host registry, which Lane A creates. Do run pnpm verify:rpc-params-catalog (the generator globs dart-params.ts); if it drifts, stop and report, because the regenerated file belongs to Lane A.

Rules the contract must encode (from Phase 0, live API)

  1. Never send no_defaults=false (workspace defaults would hide tasks); always send explicit filters.
  2. Unknown query parameters are silently ignored by Dart, so the client validates filter names against a whitelist.
  3. Page size: default 100, maximum 250 (larger values are silently capped); totals come from count.
  4. Error bodies are not always JSON (unknown task id returns an HTML 404).

Done when

  • pnpm tc clean for node, cli and web (every compile-forced site fixed here under the rule above); both mobile checks clean: typecheck and typecheck:tests in mobile/package.json (pnpm tc does not cover mobile).
  • Tests prove a dart task source and a dart link (with dartIdentifier) survive normalize, save and reload, and a dart identity validates in the RPC params.
  • Only if D2 is (b): a test proves the stripping helper removes 'dart' for a host without the capability.
  • pnpm verify:rpc-params-catalog passes unchanged.
  • The preload stub (item 9) is in place and pnpm tc passes with dart: DartApi required in PreloadApi.
  • Default and fallback visible-provider tests still pass unchanged; mobile golden tests pass unchanged.
  • oxlint and pnpm run check:code-quality:changed clean; the contract is readable on its own by the other lane reviewers.
  • The lane report lists the real compile-forced sites and the item 11 classifications.
  • Tests for items 4.1 to 4.4 pass; a pasted Dart URL parses.

Revision 2026-10-31 (c): Fable 5.1 final review applied

  1. Item 9: satisfies PreloadApi (src/preload/index.ts:197) means declaring dart breaks Lane 0's own typecheck; Lane 0 now adds a compile-forced preload stub (src/preload/api/dart-bridge.ts plus the dart: line), handed to Lane A in sequence.
  2. Item 10.2: only mobile-tasks-create-drawers.tsx:60 is compile-forced; the other two logo call sites move to Lane D as checks.
  3. Item 6: client-settings-params.ts is verify-only (derived from isTaskProvider).
  4. Step 5 and Done-when: the stripping helper is conditional on D2 (b).
  5. Item 7: census test dropped (the Record type already enforces it).
  6. Step 5: run pnpm verify:rpc-params-catalog.
  7. Item 4: Dart is not a Create-Workspace smart mode in v1; one note that the normalizer edit covers every validation and persistence path.

Revision 2026-10-31 (b): OMP review applied

  1. Items 4.1 to 4.4: exact normalization lines, the workspace-source Linear fallback (switch point 14), Dart URL parsing (switch point 15) and link-equality tests.
  2. Item 7: a fixed seed set of eleven dart.* codes (Lane P only adds); sequential hand-over of the copy file to Lane B; census test.
  3. Item 10.1: task-search-requests.ts moved to "excluded" and use-task-page-source-summary.ts to "classify" (neither is compile-forced); list-chrome switch handled even if not forced.
  4. Item 10.2: neutral placeholders only, no logic at mobile call sites.
  5. New item 12: one written Dart identity field list.
  6. Step 4: a permission per method (accounts-admin for connect, disconnect, test).
  7. Rejected (stays as written): adding DEFAULT_VISIBLE_TASK_PROVIDERS at task-providers.ts:27 and :81. Those TASK_PROVIDERS.filter calls only keep providers already on the user's list, so they cannot add Dart.

Revision 2026-10-31

  1. Re-based on 51e183a08e0; full paths and new line numbers throughout (task-providers.ts:35,49,89-105, runtime-client-settings.ts:83, automation-params.ts:96,109, dormant validation :245, workspace-name.ts:50, ui-slice-contract-core.ts:88-96, automation display :59,76 and availability :241).
  2. Added item 7 (provider error codes, fix(i18n): localize provider errors and shared/native UI labels stablyai/orca#34680) and decision 5.
  3. Added TasksPane.tsx PROVIDER_META (switch point 12) to the compile-forced list.
  4. Mobile compile-forced list rewritten: Home files deleted upstream, REFRESH_LABELS and mobile-tasks-provider-controls.tsx no longer forced, new TaskProviderLogo call sites.
  5. Removed the instruction about protocol-version-published-capabilities.test.ts (deleted upstream, test: remove low-value capability, copy, and source inventories stablyai/orca#36114).
  6. task-source-runtime-capabilities.ts is under src/shared/; it is Lane A's one named shared-file exception (contradiction resolved).
  7. Added item 11 (files to classify) and the Task Sources UI note in decision 1.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions