Skip to main content
General

Category rows: one menu, and catching a Blink straight into a category

What changed

Every category row — in Blinks, Goals, Projects and Businesses — used to carry five controls: the name, a count badge, and buttons for Profile, Edit and Delete, plus a “+” for adding a sub-category. On a phone those buttons wrapped onto a second line and the row looked cluttered.

A row now shows two things after the name and count:

  • a catch-a-Blink icon, which files a new Blink straight into that category;
  • a “…” menu holding Profile, Edit, Add sub-category and Delete.

So the row gained a feature and lost three controls at the same time. Delete sits last in the menu with a divider above it, because a destructive action should never be the one your thumb finds by accident.

WidthWhat a row looks like
Desktop# Work  Everything for the day job  12 Blinks  (↓) (…)
Phone# Work  12  (↓) (…) — the name shortens and the badge keeps the number, dropping the word

Catching a Blink into a category

Click the catch icon on any row and the normal Blink form opens with that category already chosen in the Linkage tab. Fill it in as usual. When you save, you come back to the category page you started from, so you can file another one straight away without navigating back.

Nothing about the form changes — it is the same page, the same fields and the same save. The only difference is where you began and where you land.

Which apps have which

The calm row and the “…” menu are in all four apps, because they share one row component. Two menu items appear only where they can actually work:

  • Add sub-category — Blinks only for now. The other three apps' create pages cannot yet accept a parent category, and offering a control that silently does nothing is worse than not offering it.
  • The catch icon — Blinks only for now, for the same reason: only the Blink form can currently open with a category preselected. Each of the other apps can switch it on later by passing one extra value to the row; no new code is needed for the behaviour itself.

Keyboard and screen readers

Both icons carry the category's name in their label — “Catch a Blink into Work”, “More actions for Work” — because someone hearing the page one row at a time needs to know which row is speaking. The menu opens with the keyboard, closes with Escape, and moves focus through its items. Clicking the category name still opens the category page, exactly as before.

For developers

PieceWhere
The row (shared by four apps)templates/_shared/_category_row_inner.html
Tree node (the “+” used to live here)templates/_shared/_category_node.html
Stylesstatic/css/orbitingfox-ui.css § 23t
Category preselect + return pathblink/views.py (?category=, nextnext_url)
Testsconfig/test_category_row_actions.py, config/test_capture_return_path.py

The menu is a Bootstrap dropdown skinned with the existing .of-dropdown-menu — not a sixth expand/collapse pattern. A menu is not a disclosure: <details> cannot float above the row, so it would push the list open instead of overlaying it.

The return path is validated at both ends. The next value arrives in a query string, so it is attacker-chosen: it is refused unless it is a same-site path both when the form is rendered and when the save redirects. A scheme-relative //host is refused too — it starts with a slash and would pass a naive check.

Two traps found while building this, worth knowing: min-w-0 is not a class in this codebase (the house one is of-min-w-0), and five silent no-ops were why the row overflowed on a phone; and btn-outline-* are Bootstrap colour classes that the design ratchet does not currently count, so they can accumulate unnoticed.

🪐 OrbitPilot