Skip to content

design(desktop): unify Regenerate, Branch, and Edit-and-resend into one retry-from-turn model #5132

Closed

Description

@Astro-Han

Problem

A settled turn exposes three ways to "go back and try again", and they do not share a model. Each one lands in a different place, keeps a different kind of history, and enforces a different set of rules. The seams between them are where users get stuck.

Current behaviour, verified against main (8b3655a17):

Entry point Host operation Where the result lands What the model sees
Assistant footer Regenerate turn.regenerate (interactive-turn-coordinator.ts:191) A new turn appended to the same Session, tagged regeneratedFromTurnId The full linear history, including the answer being regenerated. model-history.ts has no lineage-aware exclusion.
Assistant footer Branch session.branch.create, slice mode through (session-revision-coordinator.ts:364) A new Session copied up to and including the turn Clean copy
User message Edit (pencil) session.revision.create, slice mode before, then a normal send (app-shell-revision-actions.ts:75) A new Session copied up to but excluding the turn; the TUI /rewind uses the same operation Clean copy

Concrete symptoms:

  1. Regenerate is not a fork, it is "ask the same thing again in the same conversation". The old answer stays in the transcript and in the model context, so the model is told the user asked twice. The UI stacks both turns and links them with "regenerated from / regenerated to" badges. Users who expected a replacement or a branch read this as the app re-sending their message.
  2. Sending an unchanged edit is refused and redirected to a different feature. app-shell.tsx:1747 blocks the send with "Nothing changed. Use Regenerate if you only want a new answer." The user is already in a flow the toast described as "rewound to before that message", and is now told to leave it and use a button with different semantics (same Session, old answer kept). fix(ui): expose Regenerate for failed turns with no assistant output #2372 recorded one shape of this dead end where Regenerate was not even rendered.
  3. Rewind has no entry point of its own; it is hidden inside Edit. Clicking the pencil only refills the composer. The revision copy is created lazily at send time, which is why the desktop shell carries a reserved / started / abandoning copy-attempt state machine and a rollback path (app-shell-revision-actions.ts:278-407) whose whole job is to make "nothing happened yet" survive cancel, navigation and send failures.
  4. Branch and Edit create two differently-named kinds of Session. Branch records branchOfTurnId; Edit records a revision family (revisionRootSessionId, revisionParentSessionId, revisionIndex). The sidebar shows "branch" tasks and "version N" tasks for what the user experiences as one action: continue from an earlier point.
  5. Edit eligibility is a pile of independent guards (attachments, directory references, quotes, transformed skill text, slash commands, another edit in progress), each surfaced as a separate toast (chat-turn.tsx:583-601). Edit-and-resend revisions reject or drop structured message context across Desktop and TUI #5109 / fix(desktop,cli): stop rejecting legal edit-and-resend / rewind on structured history #5118 are already peeling one of them off, which shows they are patches rather than one contract.
  6. Footer availability is derived from TurnStatus only (turn-footer-actions.ts); the Host then rejects with session_busy when the Session has a live root turn. The user learns the action is unavailable only after clicking.

The root of all six is that "go back to before turn T and submit again, with the text either changed or unchanged" is one operation, but it is implemented as two entry points (Regenerate, Edit) with two storage semantics (append in place vs. revision copy) and two rule sets. The "unchanged text" boundary falls exactly in the gap between them.

Desired outcome

One decision on the model, recorded here before any implementation:

  • One primitive for "retry from T". Regenerate becomes the unchanged-text case of the same operation Edit uses, so both produce the same kind of history and the old answer is never in the model context of the new attempt. Whether that operation lands in a new Session (today's revision copy) or as a hidden sibling in the same Session is the design question to settle; either is acceptable, both at once is not.
  • The unchanged-text guard goes away. Sending the same text from an edit draft is simply a regenerate.
  • Branch stays as the one "continue from here in a new Session, keep the answer" action, and Branch and revision copies share one lineage vocabulary in the catalog and sidebar, or the two are explicitly documented as different products with different names.
  • Eligibility is one contract, stated once, ideally by the Host (operation_unavailable / session_busy reasons) and reflected in the footer before the click, not a set of renderer-side toasts.

Non-goals for this issue: any change to what Branch copies, side conversations (/side), or the Agent Graph revision history in #2039.

Alternatives or workarounds

  • Keep three entry points but rename them honestly ("Ask again", "Branch", "Edit") and drop the unchanged-text redirect. This removes the worst confusion but leaves the double storage model and the duplicated answer in the model context.
  • Keep Regenerate as an in-Session sibling but exclude the superseded turn from model-history and collapse it in the transcript. This fixes the context problem without touching the revision path, at the cost of a third lineage rule.

Related: #2372, #5109, #5118, #4994, #1287 (the PR that introduced edit-and-resend as a revision copy).

Activity

  1. Nyvo-io commented on Sep 13, 2026

    @Nyvo-io
    Contributor

    I'd like to take #5132, starting with the design decision rather than an implementation PR.

    After reading #700, #1287, #5109/#5118, #4994 and the backend architecture, my initial preference is to make Regenerate the unchanged-input case of the existing revision path: retry before T into a new physical Session within the same logical conversation/version family. This is a proposal, not an assumption that the issue has already settled the choice.

    Importantly, #1287 deliberately distinguishes conversation versions from ordinary branches. I would preserve that product distinction: Branch still continues through T as an independent conversation; retry continues before T. A consistent vocabulary need not erase their different lifecycle semantics.

    I propose a short design note covering:

    • One Host-owned retry/eligibility contract, with submission-time validation as well as client-visible availability; preserve Runtime Event history rather than rewriting old execution facts.
    • Exact provider-input selection, including compaction and archived tool results, so the excluded answer cannot leak back through a derived context projection.
    • Stable request identity and recovery across copy preparation, Turn admission, disconnects and duplicate submissions; no empty visible versions or duplicate attempts.
    • Structured-message preservation coordinated with @ggbdpq's Edit-and-resend revisions reject or drop structured message context across Desktop and TUI #5109 work, keeping unsupported cases explicitly rejected rather than silently dropping content. I would not duplicate that staging work or fix(runtime-host): add numbered suffixes to branch session titles #4994's branch naming.
    • Compatibility for existing revision families and regenerate history, and explicit separation between conversation retry and filesystem/external-side-effect rollback. Side conversations and Agent Graph revision semantics remain out of scope.

    Two useful precedents, with different tradeoffs:

    • pi uses a parent-linked session tree and distinguishes in-place tree navigation from a separate-session fork. This is a useful comparison for a same-Session sibling design, not a reason to transplant its storage model.
    • OpenCode undo also reverts file changes using Git. That stronger promise should not be implied by Maka's conversation-only retry.

    Before recommending the revision-copy option conclusively, I would compare its long-history latency and copied bytes with the sibling alternative, taking the copy/retention concerns in Discussion #4704 into account. Verification would also capture actual provider inputs and inject failures at copy/admission boundaries, rather than relying only on UI screenshots.

    Does this design-first scope fit the intended direction? I will wait for agreement on the model before implementation, and follow CONTRIBUTING's public dev-list process for material product decisions.

    AI disclosure: this comment was prepared and posted by OpenAI Codex at @Nyvo-io's request.

  2. Nyvo-io commented on Sep 16, 2026

    @Nyvo-io
    Contributor

    1. The model

    retry(T, original input)    = Regenerate
    retry(T, replacement input) = Edit-and-resend
    branch(T)                   = copy through T, then continue
    

    Retry uses the existing Host-owned session.revision.create path with a before slice (packages/runtime-host/src/server/session-revision-coordinator.ts:378) and creates a new physical Session in the existing revision family. The source Session and its Runtime Events are not rewritten by retry and remain available for independent later use. The target holds the source prefix strictly before T plus one normally admitted user turn. regenerateTurn stays as a client name but routes through this operation instead of the current same-Session append (packages/runtime/src/session-manager.ts:4147-4160).

    Branch keeps its distinct lifecycle: it copies through T as an independent conversation. Retry continues before T. The two are not unified in storage.

    Out of scope, unchanged: files, Git state, external side effects, Side Conversations, Agent Graph revisions.

    2. Lineage

    New retries do not write regeneratedFromTurnId. Its Desktop consumer renders that edge only when the referenced Turn exists in the displayed Session's materialized view (apps/desktop/src/renderer/app-shell-turn-view-model.ts:137, derive-turn-lineage-badges.ts:56-61), and the retry target deliberately does not contain T. New retries use the existing revision provenance instead: revisionRootSessionId, revisionParentSessionId, revisionOfTurnId, revisionIndex (packages/core/src/session.ts:285-293). Existing regeneratedFromTurnId records stay readable and are not rewritten.

    3. Binding between copy identity and the first replacement admission

    requestFingerprint identifies copy kind, source Session, target Session, and source turn (conversationCopyFingerprint, session-revision-coordinator.ts:225). It answers whether a target exists; it does not identify the replacement content or the first admitted Turn.

    Root-turn admission already supplies the durable half: admitRootTurn keys on (sessionId, turnId) and returns admitted | existing | conflict, with canonical content, placement, and intent compared on replay (packages/storage/src/agent-run-store.ts:365-378). What is missing is a retry-owned stable identity: Desktop generates crypto.randomUUID() per submit (apps/desktop/src/renderer/app-shell-chat-actions.ts:345, :513), so a resend after a disconnect is a new identity and the existing idempotency never fires.

    The binding extends SessionConversationCopy the same way SubagentSessionSpawn already works. That type carries requestFingerprint, initialTurnId, initialRunId, is registered in a strict shape guard, and has a decoder guard (packages/core/src/session.ts:164-169, :497-500, :575). Conversation copies should take the same shape:

    • SessionConversationCopy gains the derived first-message identity, the derived first-Turn identity, and the canonical-content digest of the replacement input. Its entry in SESSION_CONVERSATION_COPY_SHAPE, the strict isSessionConversationCopy guard, and isValidConversationCopyTransition are extended in the same change (packages/storage/src/session-conversation-copy.ts:32-48, enforced on every write at sqlite-session-metadata-store.ts:6244). The new fields stay optional so an old record remains readable; a malformed new one fails closed; and the transition check holds them immutable alongside requestFingerprint, since the binding is fixed when the copy commits. Without that third extension a later patch could rewrite the binding while every existing check still passes.
    • The Host derives those identities from the retry request and returns them with the copy result. A client that lost the copy acknowledgement replays the derived identities, not a fresh UUID.
    • The first root-turn admission persists the same identity and digest. Exact replay resolves to existing; different identity or canonical content for the same target copy is conflict.
    • Per-message idempotency alone is not enough: a normal later message in the target has a different identity and is valid. The one-replacement guard belongs to the retry operation and applies until the first replacement admission is committed. After that the target is an ordinary Session.

    The digest is not content storage. Before admission the initiating client still holds the full canonical payload; the Host holds the binding, not a recoverable draft.

    4. Recovery: four boundaries, with the abandoned/absent choice made

    Boundary Required behavior
    Lost copy acknowledgement The client replays the original target identity and the derived first-message identity with the full payload. The Host validates the binding, then admits.
    Client exit before admission No autonomous submission. The client calls session.revision.abandon on cancel or orderly exit; if it disappears without cleanup, Host recover() discards the orphan under existing rules.
    Lost admission acknowledgement Resolve the existing root-turn admission and return its outcome. Do not admit a second replacement and do not re-run the model.
    Changed-content replay Conflict against the copy binding, both before and after admission.

    The choice, stated explicitly: abandoned and absent are indistinguishable. discardStableConversationCopy deletes the metadata and claim rows with no tombstone (packages/storage/src/session-store.ts:457-478, sqlite-session-metadata-store.ts:1005-1010), so a replay of a discarded target resolves as absent. It does not resume that attempt: the client starts a new attempt after fresh eligibility checks, which means a new copyId, a new targetSessionId, and a new version seat in the family. No tombstone or retention rule is added.

    Preparing copies are excluded from the Host catalog query (packages/storage/src/sqlite-session-catalog-query.ts:41). A committed copy awaiting its first admission is still returned by that query; the hiding is client-side, in visibleSessionRevisionMembers (packages/core/src/session-revisions.ts:33-43), which also keeps the currently active version visible. This decision does not change either behavior. An admitted target remains recoverable from its own admission facts.

    5. Prerequisites before implementation

    1. The public decision on the model, on dev@maka.apache.org, per CONTRIBUTING — this is a product decision.
    2. A long-history copy baseline: copy latency, copied bytes, SQLite write amplification, and cold-restart time on a representative long Session. [DISCUSS] What should a normal Runtime turn persist? #4704 is directly relevant: conversation copy physically copies artifact payloads and clones model-call events, so a retry copying a long prefix inherits both. This measurement does not introduce a competing storage design; it only decides whether the physical-copy direction is acceptable as-is.
  3. Astro-Han commented on Sep 17, 2026

    @Astro-Han
    ContributorAuthor

    Updated position after auditing this against main: remove Regenerate entirely. Edit-and-resend already covers its only function with strictly better semantics, so this issue becomes a deletion rather than a redesign.

    @Nyvo-io your model had the right shape: retry as a revision copy before T plus a normal admission, and the binding analysis was correct for the two-RPC flow. None of it is needed if we never add the operation. One thing worth keeping from the discussion: had we kept two RPCs, the window between committed copy and first admission is optional anyway, since a fused copy-plus-admit operation removes it. That argument no longer matters here, but the instinct is right.

    What deletion buys:

    • No storage-model question to settle. Edit's revision copy already carries retry at user-driven frequency, so the copy-cost baseline stops gating anything.
    • The "Nothing changed. Use Regenerate" dead end deletes itself: sending unchanged text from an edit draft is just a resend.
    • /rewind already is this operation, so the vocabulary settles on its own.
    • Branch stays untouched as the one copy-through-T path.

    Deletion surface, verified on main:

    • turn.regenerate: protocol registration (protocol/turn.ts:273, operations.ts:354), handler (interactive-turn-coordinator.ts:62,194), wiring (execution-composition.ts:1908), Desktop IPC/preload/bridge contract.
    • regenerateTurn (session-manager.ts :4147) and the regeneratedFromTurnId write (:4248), plus the read-model and recovery projections of it.
    • Renderer: footer button (turn-footer-actions.ts), lineage badges (derive-turn-lineage-badges.ts, app-shell-turn-view-model.ts), the unchanged guard in app-shell.tsx, copy strings.
    • Guests: session-turn-access-request-coordinator.ts:36-48 wraps regenerateTurn for turn-request flows; that path needs an explicit answer (drop the guest affordance or route it through revision copy).
    • Removing an operation bumps the epoch. Existing regeneratedFromTurnId records stay readable; whether the badge consumer keeps rendering old data or goes with it, pick one in the PR.

    One gap to name rather than silently ship: turns carrying attachments, quotes, or directory references are Edit-ineligible today, so once Regenerate is gone those turns have no retry path until #5109/#5118 land the structured-content support. Accept that explicitly or sequence the removal after them.

    What survives from the original issue: eligibility as one Host-owned contract reflected in the footer before the click. That is now a small cleanup on the single remaining path, not a redesign.

    @Nyvo-io if you still want the work, it is now a removal PR and a smaller one than the design implied.

    中文版

    更新结论:整个砍掉 Regenerate。"编辑上一条重发"已覆盖它唯一的功能且语义更好,本 issue 从设计建造变成纯删除。Nyvo-io 的模型方向是对的(retry = 修订拷贝 before T + 正常准入),但只要不加这个操作,绑定/恢复那套机制全都不需要。删除面:turn.regenerate 协议注册、coordinator、session-manager 与 regeneratedFromTurnId 投影、footer 按钮与 badge、Desktop IPC 桥、guest-turn 包装(需单独处置);epoch 需 bump,旧记录保持可读。唯一要点名的缺口:带附件/引用/目录的 turn 现在 Edit 不可用,删 Regenerate 后它们没有重试路径,要么明确接受要么排在 #5109/#5118 之后。幸存的诉求只剩"eligibility 一个 Host 契约、点击前反映到 footer",降级成单一路径的小清理。

  4. Astro-Han commented on Sep 17, 2026

    @Astro-Han
    ContributorAuthor

    A related but separable question, probably worth its own thread if it gains traction: the storage model underneath revisions.

    Every revision and branch today materializes the sliced prefix into a new Session: transcript messages, ledger events, artifacts, model_call rows, linked references, all physically copied. The alternative is a shared-prefix fork tree, where a revision stores a pointer (parent session plus through-sequence) and shares the prefix instead of duplicating it. This is the model pi uses.

    What the current choice buys: a Session is a self-contained unit of ownership. Delete means delete its rows, export and import bundles stand alone (load-bearing since #5308), usage attribution is a group-by on sessionId, and ledger repair never crosses session boundaries.

    What a fork tree buys: O(1) forks and unbounded cheap retries. What it costs: every read path resolves the ancestor chain (transcript, usage screens, repair, export), deletion needs a policy (refuse, cascade, or materialize-on-delete), usage attribution on the shared prefix gets ambiguous, export still materializes lazily so the copy moves rather than vanishes, and the shared prefix must stay frozen while parents today can grow turns, repair rewrites events, and retention compacts.

    This may be YAGNI for now. The decision gate is the same #4704 copy-cost baseline: if prefix copies stay cheap at real history sizes, materialization is right and this whole question is moot. If not, fork tree is where Edit and Branch costs end up long-term, and that is a bigger conversation than this issue.

    @likun666661 @M4n5ter curious how you two see it: is the self-contained-session invariant worth defending at copy cost, or is shared lineage the long-term direction?

  5. likun666661 commented on Sep 17, 2026

    @likun666661
    Member

    My view is that shared lineage is probably the better long-term direction. A revision or branch only needs topology metadata to describe the fork:

    parentSessionId
    parentRevision / eventWatermark
    throughSequence
    local suffix
    

    The important constraint is that the child must reference an immutable snapshot boundary, not a live view of the parent. Parents may continue growing, and repair or retention may rewrite or compact their physical representation; none of that should change the prefix observed by an existing child.

    The fork itself is therefore straightforward and O(1). Most of the real design work is in lifecycle and projection semantics: deletion policy, artifact/blob ownership or reference counting, usage attribution, standalone export, repair across lineage, and preventing deep ancestor chains from making reads progressively more expensive.

    A practical middle ground would be shared-prefix lineage with local suffixes, plus checkpoints or materialization when a chain becomes deep or when an operation such as export or parent deletion requires a self-contained Session. Content-addressed immutable artifacts would fit that model as well. This keeps ordinary Edit/Branch operations cheap without requiring every read path to traverse an unbounded tree forever.

    I would keep physical self-contained copies if the #4704 baseline shows that they remain cheap at representative history sizes and the operational simplicity materially outweighs the duplication. If copying becomes visible in latency, storage, or write amplification, though, metadata-backed shared lineage seems preferable to optimizing repeated full-prefix copies.

  6. M4n5ter commented on Sep 17, 2026

    @M4n5ter
    Member
    English

    I lean toward sharing immutable history. Being able to use, delete, and export each Session independently does not mean every Session must store a separate copy of its entire history. We should preserve that independence while allowing Sessions to share the same underlying historical data.

    My direction is broadly aligned with @likun666661’s. A child Session should reference a fixed history snapshot and append its own content. parentSessionId can record provenance, but reading that history should not require the parent Session to remain alive, nor should subsequent appends, repairs, or compaction in the parent change what the child sees.

    I would favor settling a few semantics explicitly:

    • Deletion: Deleting a Session removes its references. History still referenced by other Sessions remains available and becomes reclaimable only when no references remain. Deleting a parent Session should not, by itself, cascade into its children or force them to copy their inherited history.
    • Usage: Forking does not make another physical model call, so inherited history should not create new accounting facts. A branch may display the usage associated with its inherited history, but global actual spend must count each physical call only once.
    • Export: Export should collect the complete dependency closure and produce a standalone importable bundle. I favor materialization at this boundary because export is specifically intended to cross the current storage boundary and let the recipient operate independently of the source environment. The cost matches the purpose of the operation. Ordinary Edit/Branch operations remain within the same storage domain and need not pay the cost of making an independent physical copy on every fork. Even if a Session is eventually exported, only the selected version’s dependencies need to be packaged at that point; preceding retries need not repeatedly copy them. Standalone export should therefore be a guarantee of the export format, not a reason to require physically exclusive storage for every Session.
    • Repair: Rebuilding indexes or changing the physical representation can preserve the same logical history identity. Changing the logical content requires a new version and must not silently alter snapshots already referenced by children.

    On implementation, I would avoid having every Edit add another Session-parent layer that readers must traverse. Shared history blocks and their indexes should be able to outlive individual Sessions. Checkpoints or materialization can bound read costs; a prototype can establish the appropriate structure.

    One relevant detail: the current context-offload copyReferences path already creates target-Session references while reusing the same blobId. We are therefore not starting from entirely exclusive storage. It is worth mapping the existing sharing boundary before deciding which additional Artifact payloads or event history should be shared.

    I support collecting a copy-cost baseline on the current implementation. It should guide optimization priorities and migration timing, but should not alone determine history identity or accounting semantics. A useful next step would be a small storage prototype that verifies stable inherited history and model inputs across parent continuation, deletion, and repair, as well as standalone export/import of the child. We can then compare write volume, read latency, and recovery cost. This work can proceed independently of the interaction simplification in #5132.

    简体中文

    我倾向共享不可变历史。会话能独立使用、删除和导出,并不意味着每个会话都必须复制并保存一份完整历史。我们应该保留会话操作上的独立性,同时允许底层共用相同的历史数据。

    我和 @likun666661 的方向基本一致。子会话应引用一个固定的历史快照,再追加自己的内容。parentSessionId 可以记录来源,但读取历史不应依赖父 Session 继续存在,也不能受父会话后续追加、修复或压缩影响。

    几个语义我倾向直接确定下来:

    • 删除: 删除 Session 移除它的引用;其他会话仍使用的历史继续保留,最后一个引用消失后再回收。删除父会话本身不应触发子会话级联删除或强制复制继承的历史。
    • 用量: 分叉没有产生新的物理模型调用,继承历史不应生成新的计费事实。分支可以展示继承历史的用量,但全局实际支出必须只计算一次。
    • 导出: 导出时收集完整依赖,生成可独立导入的包。我倾向这样做,因为导出的目标本来就是跨越当前存储边界,让接收方不依赖源环境;在这个边界物化完整内容,成本与操作目的相符。而普通 Edit/Branch 仍在同一个存储域内,没有必要为每次分叉都提前支付独立复制的成本。即使某个会话最终需要导出,也只需打包所选版本的依赖,不必在此前每次重试时重复复制。因此,独立导出应作为导出格式的保证,而不是所有 Session 内部存储都必须物理独占的理由。
    • 修复: 重建索引、调整物理表示可以保持历史身份;改变逻辑内容则需要新版本,不能静默改变已有子会话的快照。

    实现上,我不希望每次 Edit 都增加一层必须遍历的 Session 父链。共享历史块及其索引应能独立于会话生命周期存在;checkpoint 或物化是控制读取成本的手段,具体结构可以通过原型确定。

    还有一点:当前 context-offload 的 copyReferences 已经为目标 Session 创建独立引用、复用相同 blobId,所以我们并非从完全独占存储起步。可以先明确现有共享边界,再判断哪些 Artifact 正文或事件历史值得继续共享。

    我支持补充当前实现上的 copy-cost baseline,但它应该决定优化优先级和迁移时机,不应独自决定历史身份与计费语义。下一步更有价值的是一个小范围存储原型,验证父会话继续、删除、修复,以及子会话独立导出再导入后,继承历史和模型输入仍然稳定,再比较写入量、读取延迟和恢复成本。这项工作可以独立于 #5132 的交互简化推进。

  7. Astro-Han commented on Sep 17, 2026

    @Astro-Han
    ContributorAuthor

    Storage-model discussion moved to #5439 — both @likun666661 and @M4n5ter lean toward shared-prefix lineage with boundary materialization; prototype and baseline live there now. (Correction: the copy-cost baseline I cited as #4704 does not exist as an issue; it is now tracked as the first deliverable in #5439.)

    For the removal PR itself, three scoping decisions:

    regeneratedFromTurnId: stop writing, keep reading. Old badges keep rendering from existing records; removal only stops producing new ones.

  8. Nyvo-io commented on Sep 17, 2026

    @Nyvo-io
    Contributor

    @Astro-Han I’ll follow these decisions in one removal PR: replace side-chat regenerate with composer refill, route guest retry through revision copy when permitted and otherwise remove the affordance, and keep legacy lineage reads/badges while stopping new writes. I’ll document the temporary structured-content retry gap against #5109/#5118, bump the compatibility epoch, and keep eligibility Host-owned and reflected in the footer. Branch stays unchanged; storage work stays in #5439.

  9. Nyvo-io commented on Sep 18, 2026

    @Nyvo-io
    Contributor

    Implemented in #5468. Regenerate is removed; main chat uses Edit & resend, Side Chat refills the composer through message edit, and historical lineage remains readable.

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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions