feat(ext/vault): vault-status indicator renderer (Plan C Phase 6)
Renders sidebar-footer indicator with ahead/behind/pending state. Pure DOM; reuses shared/glyphs (four new status glyphs) and shared/relative-time. Status fetch happens in the wiring layer (Task 6.3). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,10 @@ export const GLYPH_LOCK = '⏻'; // sidebar lock nav
|
||||
export const GLYPH_NEXT = '▸'; // forward / next button (matches ▾/▸ disclosure family)
|
||||
export const GLYPH_COPY = '⎘'; // copy to clipboard
|
||||
export const GLYPH_SYNC = '⇅'; // sync / upload
|
||||
export const GLYPH_SYNCED = '✓'; // vault status: in sync (no pending/ahead/behind)
|
||||
export const GLYPH_AHEAD = '↑'; // vault status: local commits ahead of remote
|
||||
export const GLYPH_BEHIND = '↓'; // vault status: remote commits not yet pulled
|
||||
export const GLYPH_PENDING = '◌'; // vault status: items changed but not yet synced
|
||||
export const GLYPH_PREVIEW = '⊕'; // preview / expand
|
||||
|
||||
export const GLYPH_VAULT_TAB = '⧉'; // U+29C9 pop-out to fullscreen vault tab
|
||||
|
||||
34
extension/src/vault/__tests__/status-indicator.test.ts
Normal file
34
extension/src/vault/__tests__/status-indicator.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { renderStatusIndicator } from '../vault-status';
|
||||
|
||||
describe('vault status indicator', () => {
|
||||
it('renders "in sync" when ahead/behind/pending all zero', () => {
|
||||
const el = document.createElement('div');
|
||||
renderStatusIndicator(el, { ahead: 0, behind: 0, lastSyncAt: 1700000000, pendingItems: 0 });
|
||||
expect(el.textContent).toMatch(/in sync/i);
|
||||
});
|
||||
|
||||
it('renders "N ahead" when ahead is non-zero', () => {
|
||||
const el = document.createElement('div');
|
||||
renderStatusIndicator(el, { ahead: 3, behind: 0, lastSyncAt: 1700000000, pendingItems: 0 });
|
||||
expect(el.textContent).toMatch(/3 ahead/i);
|
||||
});
|
||||
|
||||
it('renders "N behind" when behind is non-zero', () => {
|
||||
const el = document.createElement('div');
|
||||
renderStatusIndicator(el, { ahead: 0, behind: 2, lastSyncAt: 1700000000, pendingItems: 0 });
|
||||
expect(el.textContent).toMatch(/2 behind/i);
|
||||
});
|
||||
|
||||
it('renders "N pending" when pendingItems is non-zero', () => {
|
||||
const el = document.createElement('div');
|
||||
renderStatusIndicator(el, { ahead: 0, behind: 0, lastSyncAt: 1700000000, pendingItems: 5 });
|
||||
expect(el.textContent).toMatch(/5 pending/i);
|
||||
});
|
||||
|
||||
it('renders "never synced" when lastSyncAt is null', () => {
|
||||
const el = document.createElement('div');
|
||||
renderStatusIndicator(el, { ahead: 0, behind: 0, lastSyncAt: null, pendingItems: 0 });
|
||||
expect(el.textContent).toMatch(/never synced/i);
|
||||
});
|
||||
});
|
||||
36
extension/src/vault/vault-status.ts
Normal file
36
extension/src/vault/vault-status.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
GLYPH_SYNCED,
|
||||
GLYPH_AHEAD,
|
||||
GLYPH_BEHIND,
|
||||
GLYPH_PENDING,
|
||||
} from '../shared/glyphs';
|
||||
import { relativeTime } from '../shared/relative-time';
|
||||
|
||||
// Local shape for the sidebar-footer indicator. Mirrors the get_vault_status
|
||||
// response data (ahead/behind/lastSyncAt/pendingItems). lastSyncAt is a unix
|
||||
// timestamp in SECONDS, or null when the vault has never synced.
|
||||
export interface VaultStatus {
|
||||
ahead: number;
|
||||
behind: number;
|
||||
lastSyncAt: number | null;
|
||||
pendingItems: number;
|
||||
}
|
||||
|
||||
export function renderStatusIndicator(el: HTMLElement, status: VaultStatus): void {
|
||||
const ts = status.lastSyncAt !== null
|
||||
? `last sync ${relativeTime(status.lastSyncAt)}`
|
||||
: 'never synced';
|
||||
|
||||
const parts: string[] = [];
|
||||
if (status.pendingItems > 0) parts.push(`${GLYPH_PENDING} ${status.pendingItems} pending`);
|
||||
if (status.ahead > 0) parts.push(`${GLYPH_AHEAD} ${status.ahead} ahead`);
|
||||
if (status.behind > 0) parts.push(`${GLYPH_BEHIND} ${status.behind} behind`);
|
||||
if (parts.length === 0) parts.push(`${GLYPH_SYNCED} in sync`);
|
||||
|
||||
el.innerHTML = `
|
||||
<div class="vault-status">
|
||||
<div class="vault-status__state">${parts.join(' · ')}</div>
|
||||
<div class="vault-status__ts">${ts}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user