In the previous post, I wrote about fixing text input in Claudian, a Claude plugin for Obsidian (by Yishen Tu, MIT license). In fact I have kept modifying this plugin in stages, on a much larger scale. This post is about the main part: rebuilding the whole UI of the chat tab list.
When working alongside an AI becomes part of every day, one conversation is not enough. Tabs multiply, one per project and one per investigation, and having seven or eight open at all times becomes normal. But in the original UI, tabs were small badges with only a number: “1 2 3 4 5”. You could not tell which conversation was which until you hovered and read the tooltip.
The starting point: the data is there, and the display throws it away
The first thing I noticed when reading the source was that the data for each tab (title, a working flag, a waiting-for-reply flag) was all there internally. Only the way of showing it was a number badge. The information had been there from the start. This pattern, “the data exists and the display just throws it away”, is the target with the best return for a modification.
In the first modification, I turned the tabs into full-width pills: number, chat name, and a status dot.
- The chat name is always shown (truncated with a CSS ellipsis when long)
- While working, a dot in the brand color blinks. While waiting for a reply, the dot is red
- Each tab shows three buttons on hover: rename, close, and delete permanently
I split deletion into two levels on purpose. “Close” only closes the tab and keeps the history. “Delete permanently” erases the conversation data too. The more destructive an operation is, the more it should be split into levels to prevent mistakes.
A color that follows the theme: the one line I reached after three failures
To make the boundary between the list and the chat body clear, I gave the list block a background color. I redid that color three times.
- Use
--background-secondaryas it is → in some themes it is the same color as the body, and the boundary disappears - Darken from
--background-primary→ the sidebar surface the plugin sits on is of the secondary family, so a gray of a different family looks out of place - The final form:
background: color-mix(in srgb, var(--background-secondary) 86%, var(--text-normal));
The idea is “take the color of the surface I am sitting on, and darken it by just 14% toward the text color”. It does not hard-code a color. It declares “relatively a little darker”, so whatever theme the user has installed, it always stands out in the same color family. In an environment with countless themes, like Obsidian, this relative specification works well.
When tabs multiply, change where they live: a side drawer
The list became comfortable as pills, but once there were more than eight tabs, it grew tall and started to squeeze the input area. So I pulled the list off the top of the input area and rebuilt it as a drawer that slides in from the right edge.
- I restructured the container’s DOM from a vertical stack to a horizontal row (row flex), and wrapped the chat body as
main-column - A pin button switches the drawer between two modes: overlay (it covers the chat and closes automatically when you pick a tab) and dock (it pushes the body aside and stays fixed)
- The mode and the open/closed state are saved in the settings and restored when Obsidian restarts
I also implemented drag-and-drop reordering of tabs. The interesting part was that the order was really “the insertion order of the Map that manages the tabs”. To reorder, you only clear the Map and register the tabs again in the order you want. Saving and rendering follow as they are. The drop position is judged as before or after by the upper or lower half of the pill under the pointer, and the insertion line is drawn with box-shadow: inset.
Details for Japanese input: renaming and the IME
Renaming a tab inline is confirmed with Enter, and here too it collides with the Enter that confirms a Japanese IME conversion. I added the same !e.isComposing check as in the previous post, so that the Enter that confirms a conversion does not confirm the rename. When you modify a tool that is used in Japanese, every place that catches Enter needs to take the IME into account. I think this can be remembered as a standard rule.
Summary: do not aim for the ideal form in one go
I did not do this modification all at once. “A number alone tells me nothing” → pills. “It squeezes the screen when tabs multiply” → drawer. “I want to change the order” → dragging. I went forward in five rounds, finding the next complaint while using it. If I had designed the drawer from the start, I would have built it without what I learned in the pill period: which information must always be visible, and which may be hidden.
Modify a tool in turns with using it. In the end that is the fastest way: that is what this series of work has taught me.
At oobe I work on improving the UI of business tools and on building work environments with AI built in. If something you use every day is “almost there”, write to me through the contact form.