# NousResearch/hermes-agent · Kanban assignee case sensitivity and dropdown UI

## Open now

- 2 issues, 0 PRs open on GitHub.
- #20875: \[Feature\] Kanban: Add assignee dropdown to task creation form
- #30437: Kanban dashboard: assignee field should be a dropdown populated from profiles (not freeform text)

These issues and PRs address case sensitivity mismatches in Kanban assignee/profile handling, along with requests to replace the freeform assignee text input with a dropdown populated from existing profiles. Multiple fix attempts and salvage PRs suggest the normalization approach has been revisited more than once.

## TL;DR

- 9 member(s): 2 open issue(s), 3 closed issue(s), 0 open PR(s), 2 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #19700. Verify it addresses your case.
- **Recommended:** read PR #19700 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-19700,pr-23235`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.16`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Open related issues

- #20875: \[Feature\] Kanban: Add assignee dropdown to task creation form
- #30437: Kanban dashboard: assignee field should be a dropdown populated from profiles (not freeform text)

## Closed / duplicate issues

- #18498: \[Bug\]: Kanban dispatcher fails to claim tasks assigned to "jules" due to case sensitivity in profile validation
- #21320: Kanban WebUI silently converts input to uppercase, causing profile mismatch crash loops with no notification
- #21506: \[Feature\]: Kanban assignee should be a dropdown select instead of free text input

## Prior & competing PRs

- **#19700: fix(profiles,kanban): normalize profile IDs for assignees (salvage of #18518)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `hermes_cli/kanban_db.py`, `hermes_cli/profiles.py`, `scripts/release.py`, `tests/hermes_cli/test_kanban_db.py`, `tests/hermes_cli/test_profiles.py`
- **#23235: fix(kanban): preserve assignee casing in dashboard (salvage #21459)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `plugins/kanban/dashboard/dist/index.js`, `plugins/kanban/dashboard/dist/style.css`, `scripts/release.py`, `tests/plugins/test_kanban_dashboard_plugin.py`
  - fix hunks:
  - `plugins/kanban/dashboard/dist/index.js`

    ```diff
    @@ -1924,6 +1924,10 @@
               title: props.columnName === "triage"
                 ? "Hermes profile that will spec this task (default: the dispatcher's configured specifier). Leave blank to let the dispatcher pick."
                 : "Hermes profile to assign. Leave blank and the dispatcher will pick from available profiles when the task is Ready.",
    +          style: { textTransform: "none" },
    +          autoCapitalize: "none",
    +          autoCorrect: "off",
    +          spellCheck: false,
             }),
             h(Input, {
               type: "number",
    ```

    ```diff
    @@ -2517,6 +2521,10 @@
             },
             placeholder: tx(t, "emptyAssignee", "(empty = unassign)"),
             className: "h-7 text-xs flex-1",
    +        style: { textTransform: "none" },
    +        autoCapitalize: "none",
    +        autoCorrect: "off",
    +        spellCheck: false,
           }),
         );
       }
    ```
  - `plugins/kanban/dashboard/dist/style.css`

    ```diff
    @@ -202,8 +202,10 @@
       align-items: center;
       gap: 0.4rem;
       font-size: 0.65rem;
    -  text-transform: uppercase;
    -  letter-spacing: 0.08em;
    +  /* Assignee/profile names are case-sensitive. Do not visually uppercase
    +   * lane headers, otherwise a valid `analyst` profile appears as `ANALYST`
    +   * in the WebUI and users may copy the wrong casing back into edits. */
    +  letter-spacing: 0.02em;
       color: var(--color-muted-foreground);
       padding: 0 0.1rem;
     }
    ```
  - `scripts/release.py`

    ```diff
    @@ -713,6 +713,7 @@
         "[redacted:email]": "tymrtn",
         "[redacted:email]": "kallidean",
         "[redacted:email]": "baocin",
    +    "[redacted:email]": "princepal9120",
         "[redacted:email]": "sicnuyudidi",
         "[redacted:email]": "haimu0x",
         "[redacted:email]": "NIDNASSER-Abdelmajid",
    ```
  - `tests/plugins/test_kanban_dashboard_plugin.py`

    ```diff
    @@ -1723,3 +1723,65 @@ def test_dashboard_requests_default_board_explicitly():
         assert "SDK.fetchJSON(withBoard(`${API}/config`, board))" in dist
         assert "SDK.fetchJSON(withBoard(`${API}/boards`, board))" in dist
         assert "}, [loadBoardList, switchBoard, board]);" in dist
    +
    +
    +def test_dashboard_assignee_inputs_preserve_casing():
    +    """Assignee/specifier inputs must disable browser auto-capitalization.
    +
    +    Hermes profile names are case-sensitive — the dispatcher uses the
    +    assignee string as a literal directory/profile lookup. Mobile browsers
    +    (iOS/Android) and some IMEs auto-capitalize the first letter of any
    +    text input by default, so a user typing ``analyst`` ends up submitting
    +    ``Analyst`` and the dispatcher fails to spawn a matching profile,
    +    leading to the crash loop reported in #21320.
    +
    +    The fix sets ``autoCapitalize="none"``, ``autoCorrect="off"``,
    +    ``spellCheck=false``, and ``style={textTransform: "none"}`` on the
    +    two assignee ``<Input>`` elements (inline triage/lane create-task
    +    input + task-edit panel "(empty = unassign)" input).
    +
    +    This test pins those attributes in the compiled dist bundle so a
    +    future rebuild that loses them fails CI immediately.
    +    """
    +    repo_root = Path(__file__).resolve().parents[2]
    +    dist = (repo_root / "plugins" / "kanban" / "dashboard" / "dist" / "index.js").read_text()
    +
    +    # Both sites should have all four attributes. Count occurrences to
    +    # ensure both inputs got the treatment, not just one.
    +    assert dist.count('autoCapitalize: "none"') >= 2, (
    +        "Expected autoCapitalize=\"none\" on both assignee inputs (inline "
    +        "create + task-edit panel)"
    +    )
    +    assert dist.count('autoCorrect: "off"') >= 2
    +    assert dist.count("spellCheck: false") >= 2
    +    assert dist.count('textTransform: "none"') >= 2
    +
    +
    +def test_dashboard_lane_head_preserves_assignee_casing():
    +    """Lane headers must not visually uppercase profile names.
    +
    +    The previous CSS rule ``.hermes-kanban-lane-head { text-transform:
    +    uppercase; letter-spacing: 0.08em }`` made a valid ``analyst`` profile
    +    appear as ``ANALYST`` in column headers; users then copied the
    +    uppercase form back into edits, hitting the same crash loop as the
    +    auto-capitalization path. The fix removes ``text-transform: uppercase``
    +    from the rule and tightens letter-spacing.
    +
    +    Static-asset regression test for the rule contents.
    +    """
    +    repo_root = Path(__file__).resolve().parents[2]
    +    style = (repo_root / "plugins" / "kanban" / "dashboard" / "dist" / "style.css").read_text()
    +
    +    # Locate the .hermes-kanban-lane-head block. Use a generous slice to
    +    # keep this resilient to nearby unrelated CSS edits.
    +    marker = ".hermes-kanban-lane-head {"
    +    idx = style.find(marker)
    +    assert idx != -1, "could not locate .hermes-kanban-lane-head rule"
    +    end = style.find("}", idx)
    +    assert end != -1
    +    rule = style[idx:end]
    +
    +    assert "text-transform: uppercase" not in rule, (
    +        "Lane head must not visually uppercase profile names — see #21320 "
    +        "and the explanatory comment in the CSS rule."
    +    )
    ```
- **#18515: fix(kanban): normalize profile names to lowercase in assign_task**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#18518`, `#19700`, `#18518`
- **#21459: fix(kanban): preserve assignee casing in dashboard**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#23235`, `#21320`, `#1`

## Fix status

- Merged to main in PR #19700 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-10`.
- Subsystem: `dashboard`
