Skip to content

merge: preview → feature/lark-oauth-provider (promote 2026-05-21 cleanup to lark-stable lane)#25

Merged
JOBYINC merged 6 commits into
feature/lark-oauth-providerfrom
merge/preview-to-lark-oauth-2026-05-21
May 21, 2026
Merged

merge: preview → feature/lark-oauth-provider (promote 2026-05-21 cleanup to lark-stable lane)#25
JOBYINC merged 6 commits into
feature/lark-oauth-providerfrom
merge/preview-to-lark-oauth-2026-05-21

Conversation

@JOBYINC
Copy link
Copy Markdown
Owner

@JOBYINC JOBYINC commented May 21, 2026

What this PR does

Promotes the 5 PRs merged into `preview` since the last lark-stable promotion (PR #11) into the production image lane.

On merge: `build-lark-feature.yml` workflow triggers and pushes new `ghcr.io/jobyinc/plane-{backend,frontend,admin,space}:lark-stable` images.

Production hosts: NOT auto-deployed. Only pick up the new image on the next explicit `docker-compose pull && docker-compose up -d` on the production host.

Promoted PRs

PR Title
#20 fix(migrations): missing 0127_workitemfield_external_ids + custom-field token API idempotency
#21 feat(custom-fields): sort the issue list by a custom field (List parity)
#22 docs(agent-notes): commit 6 standing reference docs
#23 chore(preview): fix pre-existing CI drift on web format + i18n types + oxlint warnings
#24 chore(dx): apps/api unused-import cleanup + turbo --continue in CI + relaxed pre-commit

Conflict resolution (1 file)

`apps/api/plane/api/views/work_item_field.py` line 280:

Resolution: took preview's wrapped version. Local `ruff check` clean.

Note on PR #21

`lark-oauth-provider` already carried PR #21's work at the original SHA `315ed1c00d` (pre-PR cherry-pick). `preview` has the squash-merged cherry-pick at `62b605471b`. Content is identical; no conflicts on the actual sort-by-custom-field code.

Test plan

  • CI green on this PR's merge commit (proves the merge result builds cleanly)
  • After merge: `build-lark-feature.yml` succeeds and pushes new `lark-stable` tag to GHCR
  • Production deploy is a separate, deliberate step (not triggered by this PR): `ssh prod && docker-compose pull && docker-compose up -d` when ready
  • Optional: verify the new image SHA-tagged version works on staging first before pulling `lark-stable` on prod

JOBYINC and others added 6 commits May 21, 2026 13:29
…tent custom-field token API (#20)

* feat(api): external_source/external_id idempotency on custom-field token API

The public token custom-field API (/api/v1/.../projects/<id>/fields/)
already existed but lacked the external-id idempotency convention every
other tick_client write uses (Issue: GET-then-create / 409+id). External
agents' ensure_* therefore couldn't safely create field/option schema.

Mirror the Issue pattern exactly:
- WorkItemField + WorkItemFieldOption: add external_source/external_id
  CharField(255, null, blank) (identical decl to Issue/IssueType).
  Migration 0127, additive + nullable, no data migration. (Value layer
  unchanged: PUT upsert is already idempotent via the (issue,field)
  unique key.)
- Token serializers expose external_source/external_id (writable).
- WorkItemFieldListAPIEndpoint / WorkItemFieldOptionListAPIEndpoint:
  GET ?external_source=&external_id= single lookup (mirrors Issue list);
  POST returns 409 + {error, id:<existing>} when the external keys
  already exist (mirrors Issue create), scoped to project / field.

Pre-existing automation RenameIndex drift detected by makemigrations is
deliberately excluded from 0127 (not ours; documented in the migration).

Verified end-to-end on the local stack: 15/15 acceptance probes pass
(LD_Offset number create/list/set-value/read + idempotent 409+id; Tier
single_select + PS/S/A/B options idempotent). No new migration on the
prod line beyond 0127; frontend untouched.

* style(work-item-field): wrap E501 line in 409 error message

ruff check enforces 120-char max on apps/api; the inline 123-char error
string in WorkItemFieldOptionListAPIEndpoint POST tripped Lint API CI.
Wrap into a multi-line string literal — no behavior change.

---------

Co-authored-by: Marcus Cheung <marcusm5@Marcuss-MacBook-Pro.local>
Working-notes that have stabilized into reference material the JOBY agent
team and engineering rely on day-to-day; promoting them out of untracked
limbo on local working trees into the repo so they version with the code
they document.

- asana-list-architecture.md — Asana-style list view architecture decision (2026-05-17)
- asana-list-plan.md — F1+F2 implementation plan, ratified 2026-05-17
- custom-fields-token-api.md — public token-API reference for the JOBY agents writing structured Tick data
- spreadsheet-recon.md — recon notes for feature/custom-fields-spreadsheet
- tick-primer.md — paste-into-system-prompt primer for any agent that needs to talk to Tick
- tick-agent-reference.md — full Tick API reference companion to tick-primer.md

No secrets: scan confirms only secret NAMES (PLANE_API_KEY, X-Api-Key header)
appear, never values.

Co-authored-by: Marcus Cheung <marcusm5@Marcuss-MacBook-Pro.local>
…oxlint warnings) (#23)

* chore(preview): fix pre-existing CI drift on web format + i18n types + oxlint warnings

Two pre-existing problems on preview were blocking any PR touching apps/web
or @plane/i18n from going green:

1. oxfmt --check flagged 5 web files as unformatted drift (none owned by a
   single PR). Ran oxfmt --write on:
     - apps/web/app/(all)/lark-quick-create/page.tsx
     - apps/web/ce/components/automations/root.tsx
     - apps/web/core/components/profile/overview/workload.tsx
     - apps/web/core/components/workspace/settings/lark-invite-modal.tsx
     - apps/web/core/services/project/project-automation.service.ts
   Pure whitespace / line-wrapping; no behavior change.

2. @plane/i18n tsc --noEmit failed at src/store/index.ts:68 with
   "Property 'env' does not exist on type 'ImportMeta'". The repo has no
   vite-env.d.ts ambient declaration anywhere, and this is the only
   import.meta.env usage in the package. Inline cast to a minimal typed
   shape — single line, no new file, no `any`.

3. The 5 web files also had 16 pre-existing oxlint warnings flagged the
   moment lint-staged ran on them. CI tolerates 11957 warnings on web, but
   the pre-commit hook uses --deny-warnings (0 tolerance). Fixed in place:
     - 8 unused vars → underscore-prefixed (the rule's own suggested fix)
     - 2 s.onload= / s.onerror= → addEventListener (mechanical)
     - 2 await-in-loop (sequential SDK fallback) → eslint-disable-next-line
       with reason ("intentional sequential CDN fallback")
     - 2 key={idx} on local-state lists with no stable id →
       eslint-disable-next-line with reason
     - 1 <a> with no href on a non-link wrapper → replaced with <div>
       (closest non-anchor element)
     - 1 .then() missing return → added explicit `return;`

Effect: any PR touching apps/web or @plane/i18n will pass check:format,
check:types, and pre-commit lint-staged again. Unblocks PR #21.

* chore(preview): fix 11 pre-existing apps/web type errors exposed once i18n no longer short-circuits tsc

After the i18n fix in the previous commit, tsc no longer fails fast on
@plane/i18n, so check:types finally runs against apps/web — and surfaces
11 type errors that have been sitting on preview unnoticed:

1. Button variant="neutral-primary" (5 occurrences). The propel Button
   variant union was narrowed to remove neutral-primary; consumers were
   never updated. Closest semantic = "secondary" (bordered neutral button
   used for lower-emphasis actions next to a "primary" Add/Submit).
     - apps/web/.../members/page.tsx (2x: "Sync from Lark", "Invite from Lark")
     - apps/web/.../lark-invite-modal.tsx (1x: Cancel)
     - apps/web/.../lark-quick-create/page.tsx (1x: Cancel)
   Plus the 2nd "Invite from Lark" instance.

2. Button size="md" (2 occurrences). Same story for size — "md" was removed,
   the previous default-equivalent is "base".
     - apps/web/.../lark-invite-modal.tsx (Cancel + Invite N members)

3. `catch (err: unknown)` chains that produce `unknown | string` instead of
   `string` (2 occurrences). The pattern
     const message = (err && typeof err === "object" && "error" in err && (err as {error?: string}).error) || "fallback"
   returns `unknown | string` (because `err && X` returns `err` when `err` is
   falsy, and `err` is `unknown`). Rewrote with explicit narrowing + a
   `string` accumulator. Behavior unchanged.
     - apps/web/.../members/page.tsx (line 174-178)
     - apps/web/.../lark-invite-modal.tsx (line 120-124)

4. TLarkContact verbatim-import error (1 occurrence). TypeScript 5+ with
   verbatimModuleSyntax requires `import type` for type-only symbols.
     - apps/web/.../lark-invite-modal.tsx (line 14): split into
       `import type { TLarkContact }` and `import { WorkspaceService }`.

5. MemberDropdown multiple={false} prop mismatch (1 issue, 2 lines).
   The MemberDropdownProps union narrows by `multiple`:
     multiple: false  → value: string|null, onChange: (string|null)=>void
     multiple: true   → value: string[],    onChange: (string[])=>void
   lark-quick-create was passing `value={[id]}` and `onChange: (ids: string[])`
   under `multiple={false}`, which only the `true` branch accepts. Switched
   to the correct `multiple: false` shape.
     - apps/web/.../lark-quick-create/page.tsx (line 658-659)

6. fetchWorkspaceMembers callback signature mismatch (1 occurrence). The
   store returns `IWorkspaceMember[]` but the call site treats each member
   as a defensive `Record<string, unknown>` (member-object vs flat — depends
   on API shape evolution). Added an explicit boundary cast and let `.map`
   infer the parameter type. Behavior unchanged.
     - apps/web/.../lark-quick-create/page.tsx (line 391-392)

apps/web `tsc --noEmit` now passes (exit 0), apps/web `oxlint
--deny-warnings` passes on the 3 modified files, and `oxfmt --check` passes.

---------

Co-authored-by: Marcus Cheung <marcusm5@Marcuss-MacBook-Pro.local>
…ty) (#21)

Custom fields could be filtered but not sorted; built-in sort is a
hardcoded closed enum. Wire the pre-built (but gated) custom-field
order parser end to end so a custom column sorts like a built-in one.

Backend:
- filters.py: add apply_custom_field_order — mirrors the
  labels/assignees Min-annotate branch of order_issue_queryset but
  filters the aggregate to the target field_id (the shipped
  parse_custom_field_order_by returns a bare field_values__value_*
  path; applying that directly fans the reverse-FK join out and sorts
  by an arbitrary field — wrong). Rewrites order_by_param to the
  annotation name so the grouper/paginator are unchanged.
- app IssueListEndpoint + IssueViewSet.list and the token issue list
  else-branch: custom-field sort takes precedence, else built-in.

Frontend:
- TIssueOrderByOptions admits custom_field__<id> / -custom_field__<id>.
- CustomColumnHeaderCell: asc/desc + clear-sort menu mirroring
  ListSortHeaderCell; list-header-row threads the display-filter props.
- base-issues.store issuesSortWithOrderBy: a custom_field key now
  returns the server order unchanged (root-cause fix — the client
  re-sort fell through to workItemSortWithOrderByExtended which
  returns a -created_at-sorted array, silently discarding the correct
  server order; the value isn't on the client TIssue to re-sort).
- i18n en/zh-CN: sort_ascending / sort_descending.

Also fixes the 15 pre-existing oxlint warnings in base-issues.store.ts
(no-shadow / no-unused-expressions, NOT introduced here). They had to
be cleared: the un-bypassable pre-commit hook (oxlint --deny-warnings)
blocks staging this file otherwise, and the root-cause fix must live
here. All zero-behavior (cond && expr -> if (cond) expr; local renames
orderBy->orderByValue, update->updateItem/updateAction,
action->groupedAction, groupId/subGroupId->grpId/subGrpId).

Verified: backend token probe 10/10 (asc/desc, nulls last, no fan-out,
built-in not hijacked); oxlint 0/0 on changed files; tsc no new errors
(11 pre-existing lark/members unrelated); user-verified List sort +
grouping/counts unaffected.

Co-authored-by: Marcus Cheung <marcusm5@Marcuss-MacBook-Pro.local>
…commit warnings (#24)

Three independent dev-experience problems found during the 2026-05-21
preview hygiene cleanup. All three are root-cause fixes, not workarounds.

1. apps/api had 5 pre-existing F401 unused-import warnings that ruff
   --fix in CI silently auto-fixed (in-CI working copy) without committing
   back. Every PR touching apps/api saw "Found N errors (N-1 fixed,
   1 remaining)" noise that masked the real failure. Applied
   `ruff check --fix` and committed:
     - apps/api/plane/app/views/issue/sub_issue.py (Func, Q)
     - apps/api/plane/db/management/commands/lark_long_poll.py (build_url_preview_card)
     - apps/api/plane/tests/contract/api/test_assigned_work_items.py (uuid)
     - apps/api/plane/tests/contract/api/test_personal_tasks.py (APIClient)

2. CI's `pnpm turbo run check:types --affected` (and check:format,
   check:lint) was short-circuiting on the first failing package. The
   2026-05-21 cleanup discovered that a single @plane/i18n type error
   was hiding 11 latent type errors in apps/web for an unknown duration.
   Added `--continue` to all three turbo runs in
   `.github/workflows/pull-request-build-lint-web-apps.yml` so every
   package surfaces its own failures in one CI run, regardless of any
   prior package's outcome.

3. The husky pre-commit hook ran `pnpm exec oxlint --fix --deny-warnings`
   on staged files. apps/web carries 964 pre-existing oxlint warnings
   (within the CI budget of 11957), but `--deny-warnings` is 0-tolerance —
   so any author running `oxfmt --write` or otherwise re-staging a
   warning-laden file got their commit refused. CI's `--max-warnings=11957`
   already catches NEW warnings (any increase fails CI); pre-commit doesn't
   need to be stricter than that. Changed to `--fix --quiet` (only fail
   pre-commit on actual lint ERRORS, not warnings).

Co-authored-by: Marcus Cheung <marcusm5@Marcuss-MacBook-Pro.local>
…nup to lark-stable image lane)

Brings the 5 PRs merged into preview since the last lark-stable promotion
(PR #11) into the production image lane:

  - PR #20 fix(migrations): missing 0127_workitemfield_external_ids + custom-field token API idempotency
  - PR #21 feat(custom-fields): sort the issue list by a custom field (List parity)
            (lark-oauth-provider already carried this work at SHA 315ed1c
             as the un-squashed original; preview has the cherry-pick squash
             62b6054. Conflict-free except for the wrapped 409 error string.)
  - PR #22 docs(agent-notes): commit 6 standing reference docs
  - PR #23 chore(preview): fix pre-existing CI drift on web format + i18n types + oxlint warnings
  - PR #24 chore(dx): apps/api unused-import cleanup + turbo --continue + relaxed pre-commit

Conflict resolved (1 file):
  apps/api/plane/api/views/work_item_field.py:280
    HEAD (lark-oauth-provider): one-line 123-char "error" string (would re-fail Lint API)
    origin/preview: wrap into multi-line literal (PR #20's E501 ruff fix)
    Resolution: take preview's wrapped version. ruff check clean.

No behavior change beyond what the 5 individual PRs already shipped to
preview. Production image (lark-stable tag) will rebuild on push; production
hosts will only pick it up on the next deliberate redeploy
(docker-compose pull && up -d).
@JOBYINC JOBYINC merged commit 0758322 into feature/lark-oauth-provider May 21, 2026
@JOBYINC JOBYINC deleted the merge/preview-to-lark-oauth-2026-05-21 branch May 21, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant