Skip to content

Commit f09c375

Browse files
committed
Replace permanent history rail with Notebook Index overlay shell in chat page
1 parent 6a5c8f3 commit f09c375

3 files changed

Lines changed: 168 additions & 26 deletions

File tree

docs/DESIGN-SPEC.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ The top nav has been replaced with a collapsible left rail (`<nav class="rail">`
301301
- Techy's implementation must port the reference interaction model into Svelte rather than importing its React/shadcn implementation; do not add `framer-motion`, `lucide-react`, or Radix React dependencies
302302
- Limit the 21st.dev-inspired composer treatment to current Techy controls: Auto | Create | Update, provider, model, selected update note, and send. Do not add upload, voice, canvas, image preview, or stop-generation controls until the corresponding product/API contracts exist
303303
- A Notebook Index overlay allows the user to resume an older chat without leaving the chat surface
304+
- Notebook Index implementation: `.chat-topbar` row at the top of the conversation column holds a `.notebook-toggle-btn` (pill button, `aria-expanded`); clicking opens/closes `.notebook-overlay` (position absolute within `.chat-shell`, slides via `transform: translateX`); `Escape` and backdrop click also close it; the overlay is desktop-only (hidden at ≤ 960px via `display: none`); mobile uses the existing `history-mobile` `<details>` drawer
305+
- The desktop `.chat-shell` is now a single flex column (`position: relative`; no grid rail column); the `conversation-stream` and `composer-shell` remain centered with `margin: 0 auto`
304306
- In Update mode, a full-width note picker appears below the composer chrome for choosing the note to review
305307
- Send is disabled in Update mode until a note is selected
306308
- Add one assistant settings control that contains provider and model selection

docs/workboard.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3079,7 +3079,7 @@
30793079
"id": "UI-026",
30803080
"group_id": "UI",
30813081
"title": "Build Notebook Index overlay shell in chat page",
3082-
"status": "todo",
3082+
"status": "done",
30833083
"priority": "high",
30843084
"type": "implementation",
30853085
"summary": "Replace persistent history rail with a secondary overlay shell in `/chat` layout only.",

src/routes/(app)/chat/+page.svelte

Lines changed: 165 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
let topicCache = $state<Record<string, unknown>>({});
165165
let activeConversationId = $state(untrack(() => data.conversation?.id ?? ''));
166166
let isHistoryDrawerOpen = $state(false);
167+
let isNotebookOpen = $state(false);
167168
let conversationHistory = $state<AssistantMessageInput[]>(
168169
untrack(() => savedMessagesToHistory(data.messages ?? []))
169170
);
@@ -262,8 +263,14 @@
262263
onMount(() => {
263264
didMount = true;
264265
266+
function handleKeydown(event: KeyboardEvent) {
267+
if (event.key === 'Escape') isNotebookOpen = false;
268+
}
269+
window.addEventListener('keydown', handleKeydown);
270+
265271
return () => {
266272
cleanupComposerModeAnimation?.();
273+
window.removeEventListener('keydown', handleKeydown);
267274
};
268275
});
269276
@@ -1017,15 +1024,39 @@
10171024
</div>
10181025

10191026
<div class="chat-shell">
1020-
<aside class="history-rail" aria-label="Recent conversations">
1021-
<div class="history-rail__head">
1027+
{#if isNotebookOpen}
1028+
<div
1029+
class="notebook-backdrop"
1030+
role="presentation"
1031+
aria-hidden="true"
1032+
onclick={() => (isNotebookOpen = false)}
1033+
></div>
1034+
{/if}
1035+
1036+
<aside
1037+
id="notebook-overlay"
1038+
class="notebook-overlay"
1039+
class:notebook-overlay--open={isNotebookOpen}
1040+
aria-label="Notebook index"
1041+
aria-hidden={!isNotebookOpen}
1042+
inert={!isNotebookOpen}
1043+
>
1044+
<div class="notebook-overlay__head">
10221045
<div>
10231046
<p>Notebook index</p>
10241047
<h2>Recent</h2>
10251048
</div>
1026-
<a class="history-new" href="/chat" aria-label="Start a new chat">
1027-
<span aria-hidden="true">+</span>
1028-
</a>
1049+
<div class="notebook-overlay__head-controls">
1050+
<a class="history-new" href="/chat" aria-label="Start a new chat">
1051+
<span aria-hidden="true">+</span>
1052+
</a>
1053+
<button
1054+
type="button"
1055+
class="notebook-close-btn"
1056+
onclick={() => (isNotebookOpen = false)}
1057+
aria-label="Close notebook index"
1058+
>✕</button>
1059+
</div>
10291060
</div>
10301061

10311062
{#if hasSavedConversations}
@@ -1036,6 +1067,7 @@
10361067
class:history-item--active={conversation.id === activeConversationId}
10371068
href={`/chat/${conversation.id}`}
10381069
aria-current={conversation.id === activeConversationId ? 'page' : undefined}
1070+
onclick={() => (isNotebookOpen = false)}
10391071
>
10401072
<span class="history-item__title">{conversationTitle(conversation)}</span>
10411073
<span class="history-item__time">{formatRelativeTime(conversation.updatedAt)}</span>
@@ -1048,6 +1080,18 @@
10481080
</aside>
10491081

10501082
<section class="chat-main" aria-label="Active conversation">
1083+
<div class="chat-topbar">
1084+
<button
1085+
type="button"
1086+
class="notebook-toggle-btn"
1087+
aria-expanded={isNotebookOpen}
1088+
aria-controls="notebook-overlay"
1089+
onclick={() => (isNotebookOpen = !isNotebookOpen)}
1090+
>
1091+
<span class="notebook-toggle-btn__icon" aria-hidden="true">≡</span>
1092+
<span>Notebook</span>
1093+
</button>
1094+
</div>
10511095
<div class="conversation-stream" class:conversation-stream--empty={displayMessages.length === 0} bind:this={conversationEl} aria-label="Conversation">
10521096
{#if displayMessages.length === 0}
10531097
<div class="empty-stage">
@@ -1637,14 +1681,13 @@
16371681
}
16381682
16391683
.chat-shell {
1684+
position: relative;
16401685
flex: 1;
16411686
min-height: 0;
1642-
display: grid;
1643-
grid-template-columns: minmax(10.5rem, 13.75rem) minmax(0, 1fr);
1644-
gap: clamp(1rem, 3vw, 2.4rem);
1645-
align-items: stretch;
1687+
display: flex;
1688+
flex-direction: column;
16461689
width: 100%;
1647-
max-width: 78rem;
1690+
max-width: 54rem;
16481691
margin: 0 auto;
16491692
}
16501693
@@ -1653,28 +1696,54 @@
16531696
min-height: 0;
16541697
display: flex;
16551698
flex-direction: column;
1699+
flex: 1;
16561700
gap: 1.25rem;
16571701
}
16581702
16591703
.history-mobile {
16601704
display: none;
16611705
}
16621706
1663-
.history-rail {
1664-
align-self: start;
1665-
position: sticky;
1666-
top: 0.75rem;
1707+
/* Notebook overlay (replaces permanent history rail) */
1708+
.notebook-backdrop {
1709+
position: absolute;
1710+
inset: 0;
1711+
z-index: 18;
1712+
background: color-mix(in srgb, var(--bg-base) 20%, transparent);
1713+
}
1714+
1715+
.notebook-overlay {
1716+
position: absolute;
1717+
top: 0;
1718+
left: 0;
1719+
bottom: 0;
1720+
width: 13.75rem;
1721+
z-index: 20;
16671722
display: grid;
1723+
grid-template-rows: auto 1fr;
16681724
gap: 0.85rem;
1669-
max-height: calc(100vh - 6rem);
16701725
overflow: hidden;
16711726
padding: 0.6rem;
16721727
border: 1px solid color-mix(in srgb, var(--border-soft) 78%, transparent);
16731728
border-radius: 0.5rem;
1674-
background: color-mix(in srgb, var(--bg-surface) 68%, transparent);
1729+
background: color-mix(in srgb, var(--bg-surface) 94%, transparent);
1730+
backdrop-filter: blur(14px);
1731+
box-shadow: 0 8px 32px rgb(0 0 0 / 0.16);
1732+
transform: translateX(calc(-100% - 1rem));
1733+
transition:
1734+
transform 240ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
1735+
visibility 240ms;
1736+
visibility: hidden;
1737+
pointer-events: none;
1738+
}
1739+
1740+
.notebook-overlay--open {
1741+
transform: translateX(0);
1742+
visibility: visible;
1743+
pointer-events: auto;
16751744
}
16761745
1677-
.history-rail__head {
1746+
.notebook-overlay__head {
16781747
display: flex;
16791748
align-items: center;
16801749
justify-content: space-between;
@@ -1683,7 +1752,7 @@
16831752
border-bottom: 1px solid color-mix(in srgb, var(--border-soft) 72%, transparent);
16841753
}
16851754
1686-
.history-rail__head p {
1755+
.notebook-overlay__head p {
16871756
margin: 0;
16881757
color: var(--text-subtle);
16891758
font-size: 0.64rem;
@@ -1692,13 +1761,84 @@
16921761
text-transform: uppercase;
16931762
}
16941763
1695-
.history-rail__head h2 {
1764+
.notebook-overlay__head h2 {
16961765
margin: 0.12rem 0 0;
16971766
color: var(--text-secondary);
16981767
font-size: 0.92rem;
16991768
line-height: 1.2;
17001769
}
17011770
1771+
.notebook-overlay__head-controls {
1772+
display: flex;
1773+
align-items: center;
1774+
gap: 0.3rem;
1775+
}
1776+
1777+
.notebook-close-btn {
1778+
display: inline-flex;
1779+
align-items: center;
1780+
justify-content: center;
1781+
width: 1.75rem;
1782+
height: 1.75rem;
1783+
padding: 0;
1784+
border: 1px solid var(--border-soft);
1785+
border-radius: 999px;
1786+
background: transparent;
1787+
color: var(--text-muted);
1788+
font: inherit;
1789+
font-size: 0.75rem;
1790+
cursor: pointer;
1791+
transition:
1792+
border-color 150ms ease,
1793+
background 150ms ease,
1794+
color 150ms ease;
1795+
}
1796+
1797+
.notebook-close-btn:hover {
1798+
border-color: var(--border-strong);
1799+
background: color-mix(in srgb, var(--bg-raised) 60%, transparent);
1800+
color: var(--text-primary);
1801+
}
1802+
1803+
/* Chat topbar with notebook toggle */
1804+
.chat-topbar {
1805+
display: flex;
1806+
align-items: center;
1807+
gap: 0.75rem;
1808+
padding: 0.1rem 0 0.25rem;
1809+
}
1810+
1811+
.notebook-toggle-btn {
1812+
display: inline-flex;
1813+
align-items: center;
1814+
gap: 0.42rem;
1815+
padding: 0.4rem 0.72rem;
1816+
border: 1px solid var(--border-soft);
1817+
border-radius: 999px;
1818+
background: transparent;
1819+
color: var(--text-muted);
1820+
font: inherit;
1821+
font-size: 0.75rem;
1822+
font-weight: 700;
1823+
cursor: pointer;
1824+
transition:
1825+
border-color 150ms ease,
1826+
background 150ms ease,
1827+
color 150ms ease;
1828+
}
1829+
1830+
.notebook-toggle-btn:hover,
1831+
.notebook-toggle-btn[aria-expanded='true'] {
1832+
border-color: color-mix(in srgb, var(--accent-strong) 40%, var(--border-soft));
1833+
background: color-mix(in srgb, var(--accent-soft) 16%, var(--bg-raised));
1834+
color: var(--accent-primary);
1835+
}
1836+
1837+
.notebook-toggle-btn__icon {
1838+
font-size: 0.88rem;
1839+
line-height: 1;
1840+
}
1841+
17021842
.history-new {
17031843
display: inline-flex;
17041844
align-items: center;
@@ -2852,12 +2992,9 @@
28522992
}
28532993
28542994
@media (max-width: 960px) {
2855-
.chat-shell {
2856-
display: block;
2857-
max-width: 54rem;
2858-
}
2859-
2860-
.history-rail {
2995+
.chat-topbar,
2996+
.notebook-overlay,
2997+
.notebook-backdrop {
28612998
display: none;
28622999
}
28633000
@@ -2962,6 +3099,9 @@
29623099
.history-drawer summary::after,
29633100
.history-new,
29643101
.history-item,
3102+
.notebook-overlay,
3103+
.notebook-toggle-btn,
3104+
.notebook-close-btn,
29653105
.loading-dots span {
29663106
transition: none;
29673107
}

0 commit comments

Comments
 (0)