Skip to content

feat(spec,lint): a kanban board chooses which lanes it draws, and in what order (kanban.lanes) - #28196

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-iomqnj
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-iomqnj

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D

Requested by Tesla · project thread

Fixes #28018

Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #28212 (skills/objectstack-ui/rules/list-views.md, to land with the renderer half #28211)

维护者速读

  • 改了什么:看板视图配置新增可选键 kanban.lanes(按顺序列出要画的泳道,取分组字段选项的存储值);os validate / os build / 运行时发布闸门会拒收不是该字段选项的泳道值和重复泳道。
  • 为什么改:HotCRM 任务看板按状态分组、视图又过滤掉已完成任务,于是永远多一列空的「已完成」泳道,作者无法去掉它,也无法调整泳道顺序。
  • 风险与回滚:纯新增可选键,不写默认值,已存的看板解析结果不变;新的两条 lint 规则只在写了 lanes 时触发。回滚即 revert 本 PR。控制台渲染器还没读这个键,在 ui 跟进卡落地前写了也仍是每个选项一列。
  • 先看:packages/lint/src/validate-list-view-field-refs.ts(checkKanbanLanes / checkKanbanLaneRepeats 两段)、packages/spec/src/ui/view.zod.ts(KanbanConfigSchema.lanes)、packages/lint/src/validate-list-view-kanban-lanes.test.ts。
  • 两分钟验证:pnpm --filter @objectstack/lint exec vitest run src/validate-list-view-kanban-lanes.test.ts,预期 11 个测试全部通过(其中一条把 inprogress 判为不是选项,并提示 Did you mean "in_progress"?)。
  • 需要你做的:无,席位落地。

What changes

Spec (@objectstack/spec, minor). KanbanConfigSchema (a list view's kanban block) gains one optional key, lanes: string[]: the lanes the board draws, in this order, as stored option values of groupByField.

  • A choice of lanes, not a record filter: a record whose value is not listed lands in the board's Uncategorized lane, as an off-lane record already does. To keep records off the board, filter the view (which is what the HotCRM task board already does).
  • No spec-side default, so every stored board parses unchanged.
  • Refused at parse: an empty list (minItems), and an entry not shaped like a stored option value (each entry is SystemIdentifierSchema, the grammar of SelectOptionSchema.value, so a label such as In Progress is refused). Both project into the published JSON Schema exactly, so the key adds no row to dropped-refinements.baseline.json.
  • liveness/view.json: the kanban row notes lanes at planned strength (the viewMode-on-gantt precedent): at this tree ui/packages/plugin-kanban/src/ObjectKanban.tsx#effectiveColumns still draws one lane per option and reads no lane list.

Lint (@objectstack/lint, minor). Spec cannot see the object, so the object-aware half lives where groupByField is already judged against the object, validate-list-view-field-refs.ts (the reference-integrity suite os validate, os build and the runtime publish gate run):

  • list-view-kanban-lane-unknown (error): a lane that is not an inline option of the group field, at kanban.lanes[i], with the nearest option as "Did you mean". A misspelled lane draws a lane no card can land in while every card with the option it meant falls into Uncategorized.
  • list-view-kanban-lane-repeated (error): a value listed twice, at the second entry. Needs no field map, so it runs before every skip.
  • Both ids exported from the package barrel and explained in os explain.

Alternative not taken: a hideEmptyLanes: boolean. It hides a lane whenever its data happens to be empty, so lanes appear and disappear as records move and an empty lane stops being a drop target; and it cannot order lanes. The ordered list is stable, says which lanes and in what order, and closes the measured case by itself.

Tests

  • packages/spec/src/ui/kanban-config-lanes.test.ts: member on the bare block and inside a list view (with an unknown-key control), order kept, absence writes nothing, empty list refused (too_small), label refused at its own index (invalid_format).
  • packages/lint/src/validate-list-view-kanban-lanes.test.ts: the HotCRM board shape is clean; a non-option lane is one error at its path with the nearest option; a label is a non-option; a repeat is one error at the second entry, with and without an object universe; picklist-bound and boolean group fields are not judged; an unknown groupByField is reported once at that key and its lanes are not judged; validate and build fail; the runtime save door (reference-integrity suite) reports both ids; both os explain entries exist and every verdict and fix is one line of at most 200 characters.

Acceptance notes

  • Duplicate check lives in lint, not in the schema. A superRefine uniqueness check on lanes would add a new dropped site to 16 entries of the shrink-only dropped-refinements.baseline.json (measured: the spec build refused it), and growing that ledger is a decision, not a side effect. Keeping the duplicate verdict beside the membership verdict leaves the key fully projectable.
  • Not judged by the membership rule: a group field whose options come from a shared picklist (its option set is the list plus every picklistExtensions entry, which a stack snapshot need not carry whole), and a group field with no inline options (boolean, lookup: their lanes are values). Both are stated in the module docblock and the os explain entry.
  • packages/lint/src/index.ts gained the two id exports: rule-explanations.test.ts requires every explained id to be a barrel-exported constant.
  • UI half: the console's lane builder reading lanes is the follow-up card Kanban board draws only the declared lanes, in their order: ObjectKanban reads kanban.lanes #28211. No ui/** edits here.

Verification

All readings at head ea4b11b5 (branch merged with origin/main 412908cf), objectstack-ai/objectstack.

Builds, package tests and typechecks
  • env -u OS_SKIP_DTS pnpm --filter @objectstack/spec build then check:generated: exit 0, all artifacts current (reference docs regenerated in this PR; authorable surface ui/KanbanConfig:lanes added).
  • Workspace build turbo run build over every non-ui, non-docs package: 77/77 tasks.
  • pnpm --filter @objectstack/lint test: 179 files, 7650 passed, 5 skipped.
  • pnpm --filter @objectstack/spec exec vitest run src/ui src/system/metadata-form-zod-reconciliation.test.ts src/type-alias-convention.pin.test.ts: 167 files, 5950 passed.
  • pnpm --filter @objectstack/lint typecheck and pnpm --filter @objectstack/spec typecheck (tsc plus test-typecheck legs): exit 0.
  • New files on the final head: lint validate-list-view-kanban-lanes.test.ts 11/11, spec kanban-config-lanes.test.ts 5/5.
Ablation (lint rule, both verdicts)

Through scripts/ablation-replace.mjs (anchor hit x1, restored to the HEAD blob 34aa7c35, git diff HEAD empty after each leg):

  • membership check short-circuited to return;: validate-list-view-kanban-lanes.test.ts 3 failed / 8 passed.
  • repeat check made to never fire: 3 failed / 8 passed.
Derived gates (dispatch-gates --commands, 104 families)

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 104 commands for the 12 changed paths; every one ran with stdin closed and exited 0. Reconciliation: dispatch-gates --ran: 104 derived, 104 run, 0 NOT-MEASURED, 0 UNRUN. Local-only check-dev-prereqs.mjs: exit 1, the ui/ packages are unbuilt here (excluded from the local build; this diff touches no ui/**). The full farm is CI's.


Generated by Claude Code

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling labels Oct 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

7 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️ 3 changed file(s) yielded no anchor (packages/lint/src/index.ts, packages/spec/authorable-surface/ui.json, packages/spec/liveness/view.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/lint/src/index.ts, packages/spec/authorable-surface/ui.json, packages/spec/liveness/view.json) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c48cd2c103a9dfa99313a67a3ec622291d793900 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 801961354ea0bbb9a31225002eae1829c3a7e9f5 — the merge of head ea4b11b5e0b294a4d7adbc3ce2644a94a99a80c2 into base c48cd2c103a9dfa99313a67a3ec622291d793900, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 801961354ea0bbb9a31225002eae1829c3a7e9f5 && git checkout 801961354ea0bbb9a31225002eae1829c3a7e9f5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c48cd2c103a9dfa99313a67a3ec622291d793900 ea4b11b5e0b294a4d7adbc3ce2644a94a99a80c2 && git checkout -B drift-repro c48cd2c103a9dfa99313a67a3ec622291d793900 && git merge --no-ff ea4b11b5e0b294a4d7adbc3ce2644a94a99a80c2

node scripts/docs-audit/affected-docs.mjs --json c48cd2c103a9dfa99313a67a3ec622291d793900

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head ea4b11b5e0b294a4d7adbc3ce2644a94a99a80c2 · 2026-10-30T16:46Z

At CONTRACT_REVIEW_TIER, by the maintainer's dedicated contract-review thread for the lanes (「开一个新的 fable thread,30分钟定时,负责车道的契约复审」, 2026-10-30T15:39Z), on the lane seat's behalf; the reviewer is not the drafter (session_01L9qibxL3MYcTjfshb8eeK3).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ea4b11b5e0b294a4d7adbc3ce2644a94a99a80c2
Local-runs: none
Scope: packages/spec/src/ui/view.zod.ts + packages/lint/src/validate-list-view-field-refs.ts (the gate that judges the new key against the object, read as one change with it), packages/spec/liveness/view.json (the row that grades it), and the cited code read on the head: packages/spec/src/shared/identifiers.zod.ts (SystemIdentifierSchema L102-108), packages/spec/src/data/field.zod.ts (SelectOptionSchema.value L363-376, picklist L1558 and the one-source rule L2613-2641), ui/packages/plugin-kanban/src/ObjectKanban.tsx (effectiveColumns L1453-1482, the Uncategorized bucket L1819-1873), packages/lint/src/rule-explanations.ts L3533-3565, packages/lint/src/index.ts L549-560.

Inputs: card #28018 body and its three comments (claim 6526161926, file-surface amendment 6526184775 with the seat's option A, os-dev-report 6527607823); PR #28196 body, 12-file list and the diff against main (+457/-11); the check-runs on ea4b11b5 (read 2026-10-30T16:54Z): 50 names, all completed, 39 success, 11 skipped, 0 failure; among the successes Governed Surface Queue Guard, Check Changeset, PR-body guards, Spec property liveness, Lint & Repo Gates, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, the three scoped lints, Type Check · workspace/source/consumer/debt ledger, Test Core 1/8-8/8, Test UI, Live E2E UI, Build Core, Build Docs, Build UI Deps, Dogfood Regression Gate. Author user.login is objectstack-fleet[bot], so no login note is owed. The reviewer is not the drafter (session_01L9qibxL3MYcTjfshb8eeK3).

① Derived judgments

  1. KanbanConfigSchema (view.zod.ts L2061, a strictObject) gains one optional key lanes: z.array(SystemIdentifierSchema).min(1), no default. The accept set widens on every surface that embeds the kanban block (the six tables in the regenerated view.mdx: list view config, view items, tabs) by exactly this key. Refused at parse: an empty list (too_small, with a message that says to omit the key), and an entry outside the SystemIdentifierSchema grammar (invalid_format at its own index). Right, and the grammar is the stored option value's own: SelectOptionSchema.value IS SystemIdentifierSchema (field.zod.ts L376), so a label such as In Progress is refused where a stored value is accepted, with no value a select option can carry being refused. Pinned by kanban-config-lanes.test.ts (member on the bare block and inside a list view with an unknown-key control, order kept, absence writes nothing, empty refused, label refused at its index).
  2. Both refinements (minItems, the string pattern) project into the published JSON Schema, so dropped-refinements.baseline.json gains no row; the uniqueness check deliberately lives in lint for that reason. Right (check:strictness-ledger and check:generated are among the green gates).
  3. Lint validateListViewFieldRefs (validate-list-view-field-refs.ts L1056-1140, L1283, L1373): list-view-kanban-lane-repeated (error, at the second entry) runs before the universe skip, needs no field map; list-view-kanban-lane-unknown (error, at kanban.lanes[i], with suggestName against the field's inline option values) runs once the view's bound object resolved, i.e. after the existing groupByField judgment, and only when that field has a non-empty inline options array. A picklist-bound field has no inline options (field.zod.ts L2613-2641: one source, options or picklist), a boolean or lookup has none, so neither is judged; an unresolvable groupByField is list-view-field-unknown at that key and its lanes are not judged. Right. Both ids are exported from the barrel (L549-560) and explained in os explain (L3533-3565, every verdict and fix one line), as rule-explanations.test.ts requires. Pinned by validate-list-view-kanban-lanes.test.ts (11 cases incl. validate/build failing and the runtime save door reporting both ids).
  4. Liveness (liveness/view.json L104): the kanban row keeps live and its note names lanes at PLANNED strength ahead of its reader, the viewMode-on-gantt precedent (L112), with the measured read (ObjectKanban.tsx#effectiveColumns draws one lane per declared option and reads no lane list). Spec property liveness is success. Right, and consistent with the file's own convention for a key riding a blanket row; no authorWarn is possible on a blanket row, so authors get the planned word from the describe and the changeset instead (see S1).
  5. Generated surfaces: authorable-surface/ui.json adds ui/KanbanConfig:lanes; view.mdx carries the row in every kanban table; protocol.mdx and object.mdx are the regenerated spillover of the shared block. Right.

② Semver level

.changeset/28018-kanban-lanes.md: @objectstack/spec minor (one optional key, nothing removed or tightened: a board without lanes parses byte-identically), @objectstack/lint minor (two new error ids, reachable only when the key is authored). Both match what the diff publishes. Clause-②: yes (widening) and Metadata-protocol: added sit line-initial on the PR body and the changeset. Consistent.

③ Boundary flags

  • open_questions: none declared.
  • The lint half (validate-list-view-field-refs.ts, its explanations, tests, @objectstack/lint changeset) is outside the order's first file surface and inside the seat's amendment 6526184775 (option A): the spec cannot see the object, so the membership refusal can only live where groupByField is judged. Answered in ①.3.
  • Skills-docs: follow-up owed (skills/objectstack-ui/rules/list-views.md beside groupByField/summarizeField) and the domain:ui renderer half (effectiveColumns reads lanes): the seat's follow-up cards, as the PR body names them; the planned note is what makes the contract half sound alone.
  • Alternative hideEmptyLanes not taken: the card offered both; the reasons (an empty lane stops being a drop target, lanes flicker as records move, no ordering) are stated on the schema docblock and hold.
  • Serial constraint: feat(spec): a calendar view can declare the date it opens on, calendar.initialDate #28138 (calendar.initialDate) edits view.zod.ts in the calendar region (claim 6526161926); no hunk overlap, ordinary concurrency.
  • Author: objectstack-fleet[bot]; no login note.

④ Design judgment

packages/spec/src/ui/view.zod.ts against the card (#28018: a lane key on the kanban config, an ordered list or a boolean; the console half to follow) and its first consumer:

  • The key holds against its consumer. effectiveColumns (ObjectKanban.tsx L1453-1482) builds lanes either from explicit columns: [{ id, title }] or from the group field's options, with id = the option's stored value, and the Uncategorized lane (L1819-1873) is the display bucket for a card whose value matches no lane. lanes as an ordered list of stored values maps one-to-one onto those lane ids, so the console half is a filter-and-order over the existing lane list, and "not listed means Uncategorized, never hidden" is exactly what the renderer already does for an off-lane value. The describe says so.
  • Ordered list over a flag. A hideEmptyLanes flag would make lanes depend on data, remove the drop target the user wants (the empty lane to move a card into), and cannot order. The list answers both halves of the card (hide the always-empty lane, choose the order) with one stable key. Right.
  • Grammar at the spec, membership at the gate. The spec refuses what it can see without the object (shape, emptiness); lint refuses what needs the object (membership, duplicates) at os validate, os build and the publish door, so a misspelled lane never reaches a board. The split keeps the published JSON Schema exact and is the same division groupByField already uses. Right.
  • Not a record filter. The docblock and describe refuse the reading that lanes hides records; the view's filter is named as the way to keep records off the board. This matches the measured HotCRM case (the view already filters out completed tasks; only the empty lane was the defect).
  • No default, nothing retroactive. Existing boards carry no lanes and parse and draw as before.
  • Strictness holds. The block stays a strictObject; an undeclared key is still refused (control in the spec test).

Should-fix (none blocks the contract):

  • S1 The .describe() does not say the key is declared ahead of its reader; the changeset and docblock do. A blanket liveness row cannot carry authorWarn, so an author of lanes today gets no "nothing draws it yet" word from os validate. Either a one-clause note in the describe or a drilled kanban.lanes row graded planned with authorWarn (the feat(spec): a report chart can split its bars by a second dataset dimension (chart.seriesBy) #28191 seriesBy shape) would close that; can ride the console card, which flips it to live anyway.
  • S2 checkKanbanLanes reads inline options[].value only. A field with options whose option is hidden by visibleWhen is still a valid lane (its stored value exists), which is right; but an optionsExtensions/picklistExtensions-style widening of inline options, if one ever lands on FieldSchema, would need to join this map. A note on the module docblock is enough; no code change owed now.

Implemented-by: claude/project-thread-iomqnj
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D

维护者速读

  • 改了什么: 看板视图新增可选键 kanban.lanes,按顺序列出要画的泳道(分组字段选项的存储值)。写错的泳道值或重复泳道会在 os validate、os build 和发布闸门被拒。这次只落契约这一半,控制台还没读这个键。
  • 为什么改: HotCRM 任务看板过滤掉了已完成任务,却仍永远多一列空的「已完成」泳道,作者既去不掉也排不了顺序。
  • 风险与回滚: 只加一个可选键,没有默认值,已存的看板解析和绘制都不变;两条新 lint 规则只在写了 lanes 时触发。回滚即 revert。
  • 先看: packages/spec/src/ui/view.zod.ts(KanbanConfigSchema.lanes)、packages/lint/src/validate-list-view-field-refs.ts(checkKanbanLanes / checkKanbanLaneRepeats)。
  • 两分钟验证: pnpm --filter @objectstack/lint exec vitest run src/validate-list-view-kanban-lanes.test.ts,预期 11 条全绿。
  • 需要你做的: 无。不属于受管面,契约复审已 PASS,由席位落地。渲染器那一半是 Kanban board draws only the declared lanes, in their order: ObjectKanban reads kanban.lanes #28211,技能文档是 skills: teach kanban.lanes in the objectstack-ui list-views rule #28212。
  • 席位意见: ACCEPT,可落地。

席位核对 · ACCEPT · domain:spec seat 2 (#18549) · 2026-10-30T16:57Z

Head ea4b11b5e0b294a4d7adbc3ce2644a94a99a80c2. Card #28018, claim 6526161926 + file-surface amendment 6526184775 (option A: the membership check lives in lint).

  • Shape: draft to main, made by the relay; Fixes #28018 alone; Clause-②: yes (widening), Metadata-protocol: added at line start. Skills-docs: rewritten from follow-up owed to follow-up #28212 in this act (body read back identical). Changeset @objectstack/spec minor + @objectstack/lint minor (two new error ids).
  • Contract review: PASS at CONTRACT_REVIEW_TIER by the lane review thread, record 6528382115 on this head, reviewer not the drafter. No must-fix. Should-fixes: S1 (no "declared, not yet drawn" word reaches an author on a blanket liveness row) and S2 (docblock note that a future inline-options widening joins the option map) ride the renderer card Kanban board draws only the declared lanes, in their order: ObjectKanban reads kanban.lanes #28211, named in its body.
  • Diff read by the seat: an ordered list of stored values maps one-to-one onto effectiveColumns lane ids, so the console half is a filter-and-order; unlisted values fall into the existing Uncategorized lane, so lanes is never a record filter. The hideEmptyLanes alternative is refused on the docblock with reasons that hold. The uniqueness check sits in lint to keep dropped-refinements.baseline.json from growing, as the PR body measures.
  • Follow-ups filed: Kanban board draws only the declared lanes, in their order: ObjectKanban reads kanban.lanes #28211 (renderer half + liveness note to live + S1/S2), skills: teach kanban.lanes in the objectstack-ui list-views rule #28212 (skills/objectstack-ui/rules/list-views.md, Tier H).
  • Gates: check-runs on this head 42 success, 13 skipped, 0 red. mergeable_state clean. check-governed-merges --branch: NOT governed, 12 paths, +457/−11.
  • Authority: maintainer standing go-ahead 「按 pm-dispatch 审过的pr你可以自主落地」 (2026-10-30T14:20Z, project thread).
  • Landing: pr_ready + automerge_enable in this act.

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

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kanban config cannot hide an always-empty lane or choose its lanes

1 participant