diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..a93ac00
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,37 @@
+# UI Redesign — Design Handoff Implementation
+
+## Phase 1: Layout + Tokens + Header + Rail
+- [ ] Update CSS tokens (add --a-str, switch mono font to JetBrains Mono)
+- [ ] Fix grid dimensions (192px rail, 400px peek)
+- [ ] Move capture bar from header to bottom of center panel
+- [ ] Add search bar to header (centered, max-width 400px)
+- [ ] Redesign tag rail: grid layout (arrow ▸ + dot + name + count)
+- [ ] Add intent section (grab/read/fill) for cards view in rail
+
+## Phase 2: Stream + Cards Views
+- [ ] Stream rows: promoted entries get card-style border/radius + card-type badge
+- [ ] Card rows: rich single-line with title — preview — affordance badges — tag pills — pin — use count
+- [ ] Affordance detection client-side (fill, steps, decide, link, code)
+- [ ] Affordance badge components
+- [ ] Cards sub-header (scope label + card count + sort dropdown)
+- [ ] Section labels (★ pinned, recent)
+- [ ] Flash animation on copy
+- [ ] Bottom capture bar styling per view (different placeholders)
+
+## Phase 3: Peek Pane + Modes
+- [ ] Idle state with keyboard shortcuts display
+- [ ] Stream entry peek: eyebrow, body, tags, context, actions
+- [ ] Card peek: card container with eyebrow, title, desc, meta, content sections
+- [ ] Code block with syntax highlighting
+- [ ] Decision section display
+- [ ] Steps section display
+- [ ] Link section display
+- [ ] Run mode (interactive checklist with progress bar)
+- [ ] Fill mode (inline slot editor with tab navigation)
+- [ ] Edit mode (form fields)
+- [ ] Toast notifications
+
+## Phase 4: Polish
+- [ ] Promote modal enhancement (add hint text per type)
+- [ ] Remaining keyboard shortcuts (r=run, f=fill)
+- [ ] Scroll behavior and edge cases
diff --git a/web/app.js b/web/app.js
index 10c1ec6..4b3970f 100644
--- a/web/app.js
+++ b/web/app.js
@@ -16,6 +16,8 @@
const PAGE_SIZE = 50;
+ const INTENT_HINTS = { grab: 'scan + copy', read: 'expand + study', fill: 'templates only' };
+
const state = {
view: 'stream',
entities: [],
@@ -24,6 +26,7 @@
activeTag: null,
hasMore: false,
activeMonth: null,
+ intent: 'grab',
};
const $ = (sel) => document.querySelector(sel);
@@ -223,32 +226,128 @@
function formatDate(dateStr) {
const d = new Date(dateStr);
- const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
+ const months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
return months[d.getMonth()] + ' ' + d.getDate();
}
+ // ── Tag Rail ──
+
function renderTagRail() {
const rail = $('#tag-rail');
- const allItem = `
- all
-
`;
+ const total = state.tags.reduce((s, t) => s + t.count, 0);
- rail.innerHTML = allItem + state.tags.map(t =>
- `
- ${t.tag}
- ${t.count}
-
`
- ).join('');
+ let html = `nib
`;
+ html += '';
+ rail.innerHTML = html;
+
+ rail.querySelectorAll('.rail-item[data-tag]').forEach(el => {
el.addEventListener('click', () => {
state.activeTag = el.dataset.tag || null;
loadEntities();
renderTagRail();
});
});
+
+ rail.querySelectorAll('.rail-item[data-intent]').forEach(el => {
+ el.addEventListener('click', () => {
+ state.intent = el.dataset.intent;
+ renderTagRail();
+ });
+ });
}
+ // ── Capture Bar ──
+
+ function renderCaptureBar() {
+ const bar = $('#capture-bar');
+ const placeholder = state.view === 'stream'
+ ? 'capture · - todo @time event !time reminder #tag |title'
+ : '|title // desc #tag ${slot} 1. step';
+
+ bar.innerHTML = `
+
+ ›
+
+ ⏎ save
+
+ `;
+
+ const input = $('#capture-input');
+ input.addEventListener('keydown', (ev) => {
+ if (ev.key === 'Enter' && !ev.shiftKey) {
+ ev.preventDefault();
+ handleCapture();
+ }
+ });
+ }
+
+ async function handleCapture() {
+ const input = $('#capture-input');
+ const val = input.value.trim();
+ if (!val) return;
+
+ const parsed = parseInput(val);
+ if (!parsed) return;
+
+ const data = {
+ body: parsed.body,
+ glyph: parsed.glyph,
+ tags: parsed.tags,
+ };
+ if (parsed.title) data.title = parsed.title;
+ if (parsed.description) data.description = parsed.description;
+ if (parsed.timeAnchor) data.time_anchor = parsed.timeAnchor;
+ if (parsed.cardSuffix) data.card_type = parsed.cardSuffix;
+
+ await api.createEntity(data);
+ input.value = '';
+ await loadEntities();
+ await loadTags();
+ showToast('captured');
+ }
+
+ // ── Entity List ──
+
function groupByDate(entities) {
const groups = [];
let current = null;
@@ -308,10 +407,12 @@
function renderEntityItem(e, idx) {
const glyph = displayGlyph(e);
const gc = glyphClass(e);
- const selected = idx === state.selectedIndex ? 'selected' : '';
- const tags = (e.tags || []).map(t => `${t}`).join('');
+ const selected = idx === state.selectedIndex ? ' selected' : '';
+ const isCard = e.card_type ? ' is-card' : '';
+ const tags = (e.tags || []).slice(0, 2).map(t => `${t}`).join('');
const time = e.time_anchor ? `@${e.time_anchor}` : '';
const useBadge = e.use_count > 0 ? `${e.use_count}×` : '';
+ const cardBadge = e.card_type ? `${e.card_type}` : '';
let label;
if (e.title) {
@@ -321,11 +422,11 @@
label = `${escHtml(e.body)}`;
}
- return `
+ return `
${glyph}
${label}
${time}
- ${tags}
+ ${tags}${cardBadge}
${useBadge}
`;
}
@@ -351,10 +452,10 @@
if (e.card_type) {
cardContent = renderCardContent(e);
- actions += `
`;
+ actions += `
`;
actions += `
`;
} else {
- actions += `
`;
+ actions += `
`;
actions += `
`;
}
actions += `
`;
@@ -363,17 +464,19 @@
const titleHtml = e.title ? `
${escHtml(e.title)}
` : '';
pane.innerHTML = `
-