refactor: archive GUI clients; keep minimal TUI with hooks/API
ci / tcc-check (push) Successful in 1m8s
ci / linux (aarch64, 0) (push) Failing after 1h0m3s
ci / linux (x86, 1) (push) Failing after 1h0m3s
ci / linux (x86_64, 1) (push) Failing after 1h0m2s
ci / windows (push) Failing after 50s

Move Qt/Tauri/full Web SPA under archive/, ship hermit as CLI/TUI plus
libcstcore and cst-webui/1 HTTP+SSE (turn lifecycle hooks, capabilities).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-30 22:49:23 +08:00
co-authored by Cursor
parent ea38247161
commit 5538ed3412
184 changed files with 14098 additions and 1858 deletions
@@ -0,0 +1,491 @@
.projectRow,
.sessionRow {
display: flex;
align-items: center;
gap: var(--dsh-sidebar-label-gap);
border-radius: var(--dsw-radius-md);
border-inline: var(--dsh-sidebar-paint-inset) solid transparent;
background-clip: padding-box;
box-sizing: border-box;
padding: 0 8px;
padding-inline-start: calc(var(--dsh-sidebar-row-padding) - var(--dsh-sidebar-paint-inset) + var(--dsh-workspace-indent, 0px));
cursor: pointer;
-webkit-user-select: none;
user-select: none;
color: var(--dsw-alias-label-primary);
}
@media (hover: hover) {
.projectRow:hover {
background-color: var(--dsw-alias-interactive-bg-hover);
}
}
.sessionRow.selected {
background-color: var(--dsw-alias-interactive-bg-hover);
}
.dragPreview {
background: var(--dsw-specific-sidebar-fill);
}
.searchResultRow {
display: flex;
flex-direction: column;
align-items: stretch;
width: 100%;
min-height: 48px;
box-sizing: border-box;
border: none;
border-radius: var(--dsw-radius-lg);
padding: 4px calc(var(--dsh-sidebar-row-padding) - var(--dsh-sidebar-paint-inset));
border-inline: var(--dsh-sidebar-paint-inset) solid transparent;
background: transparent;
background-clip: padding-box;
cursor: pointer;
text-align: left;
color: var(--dsw-alias-label-primary);
}
@media (hover: hover) {
.searchResultRow:hover {
background-color: var(--dsw-alias-interactive-bg-hover);
}
}
.searchResultRow.selected {
background-color: var(--dsw-alias-interactive-bg-hover);
}
.searchResultHeading {
display: flex;
align-items: center;
min-width: 0;
}
.searchResultTitle {
flex: 0 1 auto;
min-width: 0;
margin: 0 var(--dsh-sidebar-label-gap);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
line-height: 20px;
}
.searchResultMeta {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
margin-left: calc(16px + var(--dsh-sidebar-label-gap));
}
.searchResultWorkspace,
.searchResultSnippet {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
line-height: 17px;
}
.searchResultWorkspace {
flex: none;
max-width: 40%;
color: var(--dsw-alias-label-tertiary);
}
/* The 40% share only protects a snippet; a snippetless match keeps the row. */
.searchResultWorkspace:only-child {
max-width: 100%;
}
.searchResultSnippet {
flex: 1;
min-width: 0;
color: var(--dsw-alias-label-secondary);
}
/* Compact one-line Workspace row after removing the session-count subtitle. */
.projectRow {
height: 34px;
align-items: center;
box-sizing: border-box;
}
.projectRow .rowActions {
height: 20px;
}
/* Session titles share the workspace icon slot and compact label gap. */
.sessionRow {
height: 32px;
gap: 0;
}
.sessionRow .title {
margin: 0 6px 0 var(--dsh-sidebar-label-gap);
}
.slot {
flex: none;
width: 16px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--dsw-alias-label-tertiary);
}
.visuallyHidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.folderActive {
color: var(--dsw-alias-state-business-primary);
}
/* Project leading slot: folder by default, expand arrow on row hover. */
.projectRow .chevron { display: none; }
@media (hover: hover) {
.projectRow:hover .chevron { display: inline-flex; }
.projectRow:hover .folder { display: none; }
}
/* Expand arrow (filled triangle): points right closed, rotates to point down open. */
.arrow {
transition: transform 150ms var(--ds-ease-in-out);
}
.arrowOpen {
transform: rotate(90deg);
}
.projectText {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
line-height: 20px;
}
.renameInput {
min-width: 0;
font-size: 14px;
line-height: 20px;
padding: 0 2px;
border: 0.5px solid var(--dsw-alias-border-l4);
border-radius: var(--dsw-radius-sm);
background: var(--dsw-alias-button-elevated-fill);
color: inherit;
outline: none;
}
/* Hovering a session row marquees a title wider than its cell: Rows.tsx crawls
it at a constant speed to its far edge while the pointer rests on the row,
holds it there, and returns it to the start in one step when the pointer
leaves. The motion is scripted frame by frame (reduced motion jumps), so no
scroll-behavior applies here. */
.sessionRow .title {
flex: 1;
}
/* While the marquee travels, both clipping edges fade in over 12px instead of
hard-cutting a character: the left once the title has moved off its start
(data-scrolled), the right while text still remains beyond the cell
(data-clipped) — at the far edge the right fade lifts so the final character
reads at full strength. The mask rides the title span itself, so the status
slot and every other leading cell keep their full color. */
.sessionRow .title[data-scrolled] {
mask-image: linear-gradient(to right, transparent, #000 12px);
}
.sessionRow .title[data-clipped] {
mask-image: linear-gradient(to left, transparent, #000 12px);
}
.sessionRow .title[data-scrolled][data-clipped] {
mask-image: linear-gradient(to right, transparent, #000 12px, #000 calc(100% - 12px), transparent);
}
.meta {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
line-height: 20px;
color: var(--dsw-alias-label-tertiary);
}
.time {
flex: none;
font-size: 10px;
line-height: 16px;
color: var(--dsw-alias-label-tertiary);
margin-inline-end: -1px;
}
.pinIndicator {
display: inline-flex;
flex: none;
width: 16px;
height: 20px;
align-items: center;
justify-content: center;
margin-left: 6px;
color: var(--dsw-alias-label-caption);
}
/* Archived rows stay in place but read as inactive: titles drop to
the caption label step (one step above dimmed, which reads as too faint
over the sidebar fill). Archived search rows drop their meta line to the
same step so both lines read as one unit. */
.sessionRow.archived .title,
.searchResultRow.archived .searchResultTitle,
.searchResultRow.archived .searchResultWorkspace,
.searchResultRow.archived .searchResultSnippet {
color: var(--dsw-alias-label-caption);
}
.dot {
flex: none;
}
/* The trailing strip keeps a shared gap between its fixed-size control boxes. */
.rowActions {
flex: none;
display: none;
align-items: center;
gap: 13px;
}
@media (hover: hover) {
.projectRow:hover .rowActions,
.searchResultRow:hover .rowActions {
display: inline-flex;
}
}
.projectRow.menuOpen .rowActions {
display: inline-flex;
}
.rowQuickActions {
display: contents;
}
/* The search heading has no trailing time cell, so the unarchive action
right-aligns itself. */
.searchResultHeading .rowActions {
margin-left: auto;
}
.rowResting {
flex: none;
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--dsw-alias-label-tertiary);
}
.rowResting .pinIndicator {
margin-left: 0;
}
/* The unclipped hover state drops the ellipsis, which would otherwise cover
the characters the marquee reached. Guarded because a touch tap
latches `:hover` on the row after the title has already returned: an
unguarded rule would strand that row without its ellipsis. */
@media (hover: hover) {
.sessionRow:hover {
background-color: var(--dsw-alias-interactive-bg-hover);
}
.sessionRow:hover .rowActions,
.sessionRow.menuOpen .rowActions,
:global(html[data-input-modality='keyboard']) .sessionRow:focus-within .rowActions {
display: inline-flex;
}
.sessionRow:hover .rowResting,
.sessionRow.menuOpen .rowResting,
:global(html[data-input-modality='keyboard']) .sessionRow:focus-within .rowResting {
display: none;
}
.sessionRow:hover .title,
.sessionRow.menuOpen .title {
text-overflow: clip;
}
}
@media (hover: none) {
.projectRow .rowActions,
.searchResultRow .rowActions,
.sessionRow .rowActions {
display: inline-flex;
}
/* Only update timestamps disappear; pending-interaction labels remain visible. */
.sessionRow .timestamp,
.sessionRow:has(.pinnedAction) .pinIndicator,
.sessionRow .rowQuickActions > :global([data-slot]) > :not(.pinnedAction),
.projectRow[aria-expanded='false'] .rowActions {
display: none;
}
.sessionRow .pinnedAction {
/* Keep More aligned across rows. Tab order remains More, then Unpin. */
order: -1;
}
}
/* An open row menu pins the hover affordances (figma: the row keeps its
hover fill while its dropdown is up). */
.projectRow.menuOpen,
.sessionRow.menuOpen {
background-color: var(--dsw-alias-interactive-bg-hover);
}
/* Session drag insert marker: a leading chevron and 2px rule between rows,
absolutely positioned so it neither resembles a row border nor changes layout. */
.sessionRow.dropBefore,
.sessionRow.dropAfter {
position: relative;
}
.sessionRow.dropBefore::before,
.sessionRow.dropAfter::after {
content: '';
position: absolute;
z-index: 1;
left: calc(-1 * var(--dsh-sidebar-paint-inset));
right: calc(4px - var(--dsh-sidebar-paint-inset));
height: 12px;
background:
linear-gradient(
55deg,
transparent calc(50% - 1px),
var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px),
transparent calc(50% + 1px)
) 0 0 / 5px 7px no-repeat,
linear-gradient(
125deg,
transparent calc(50% - 1px),
var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px),
transparent calc(50% + 1px)
) 0 5px / 5px 7px no-repeat,
linear-gradient(
var(--dsw-alias-state-business-primary) 0 0
) 4px 5px / calc(100% - 4px) 2px no-repeat;
pointer-events: none;
}
.sessionRow.dropBefore::before {
top: -7px;
}
.sessionRow.dropAfter::after {
bottom: -7px;
}
/* Hover-card body (figma 169:16903): dark surface, fixed colors both themes. */
.hoverContent {
display: flex;
flex-direction: column;
gap: 8px;
}
.hoverTitle {
font-size: 14px;
line-height: 20px;
color: #FFFFFF;
overflow-wrap: break-word;
}
.hoverPath {
font-size: 12px;
line-height: 16px;
color: #CFD3D6;
word-break: break-all;
}
.hoverTime {
font-size: 12px;
line-height: 16px;
color: #CFD3D6;
}
.hoverStatus {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
line-height: 20px;
color: #ADB2B8;
}
/* Archive glyph rides the status row's text color; slight inset aligns it with the 6px status dots. */
.hoverArchived svg {
flex-shrink: 0;
margin: 0 -4px;
}
/* Compensated margins keep the 24px target on the compact icon's center. */
.iconButton {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin: -4px;
border: none;
border-radius: var(--dsw-radius-xs);
padding: 0;
background: transparent;
cursor: pointer;
color: var(--dsw-alias-label-tertiary);
}
/* Keep the focus ring on its original compact outline. */
.iconButton:focus-visible {
outline-offset: -2px;
}
@media (hover: hover) {
.iconButton:hover {
color: var(--dsw-alias-label-primary);
}
}
/* Chevrons ride the caption grey (#ADB2B8); the folder glyph stays one step
darker (tertiary, #81858C) per the cell spec. */
.chevron {
color: var(--dsw-alias-label-caption);
}
@media (prefers-reduced-motion: reduce) {
.arrow {
animation: none;
transition: none;
}
}