Skip to content

Foundation: Single-Block Selection with Visual Highlighting #35

Closed as not planned

Description

@malibio

Foundation: Single-Block Selection with Visual Highlighting

Implement core single-block selection functionality with CSS-based visual highlighting as the foundation for multi-node selection features.

Dependencies

Related Issues

  • Part of multi-node selection epic (foundational component)

Requirements

Core Selection State Management

  • Selection Store: Create reactive Svelte store for tracking selection state
  • Block-Level State: Each node tracks its own selected state via store subscription
  • State Persistence: Selection state persists across component re-renders
  • State Cleanup: Automatic cleanup when components are destroyed
  • File Location: Create src/lib/stores/selectionStore.ts for centralized state management

Visual Highlighting System

  • CSS-Based Highlighting: Use CSS classes for selection visual feedback (no inline styles)
  • Theme Integration: Leverage existing design system color tokens for highlighting
  • Smooth Transitions: 200ms ease transition for selection state changes
  • Accessibility: High contrast highlighting that meets WCAG standards
  • File Location: Add selection styles to src/lib/design/tokens/selection.css

Click-Based Selection

  • Single Click: Focus and select individual nodes
  • Clear Selection: Click on empty space or different node to clear current selection
  • Visual Feedback: Immediate visual response to selection changes
  • Event Handling: Proper event delegation to avoid performance issues
  • File Location: Enhance existing src/lib/design/components/BaseNode.svelte

Technical Implementation Notes

Selection State Architecture

// src/lib/stores/selectionStore.ts
interface SelectionState {
  selectedBlocks: string[]; // Array of block IDs
  mode: 'single' | 'range' | 'multi';
  lastSelectedBlock: string | null;
  direction: 'up' | 'down' | null;
}

interface SelectionStore extends Readable<SelectionState> {
  selectBlock: (blockId: string) => void;
  clearSelection: () => void;
  isSelected: (blockId: string) => boolean;
  getSelectedCount: () => number;
}

CSS Design Tokens Pattern

/* src/lib/design/tokens/selection.css */
:root {
  --selection-background: var(--color-accent-subtle);
  --selection-background-hover: var(--color-accent-muted);
  --selection-border: var(--color-accent-border);
  --selection-transition: background-color 200ms ease;
}

[data-theme="dark"] {
  --selection-background: var(--color-accent-subtle-dark);
  --selection-background-hover: var(--color-accent-muted-dark);
  --selection-border: var(--color-accent-border-dark);
}

BaseNode Integration Pattern

<\!-- src/lib/design/components/BaseNode.svelte -->
<script lang="ts">
  import { selectionStore } from '$lib/stores/selectionStore';
  export let nodeId: string;
  
  $: isSelected = $selectionStore.selectedBlocks.includes(nodeId);
  
  function handleClick(event: MouseEvent) {
    if (\!event.shiftKey && \!event.ctrlKey) {
      selectionStore.selectBlock(nodeId);
    }
  }
</script>

<div 
  class="base-node"
  class:selected={isSelected}
  on:click={handleClick}
  role="treeitem"
  aria-selected={isSelected}
  tabindex="0"
>
  <\!-- existing node content -->
</div>

<style>
  .base-node {
    transition: var(--selection-transition);
    cursor: pointer;
  }
  
  .base-node.selected {
    background-color: var(--selection-background);
  }
  
  .base-node.selected:hover {
    background-color: var(--selection-background-hover);
  }
</style>

Mock Data Integration

// src/lib/services/mockSelectionService.ts
export class MockSelectionService {
  private selectedNodes = new Set<string>();
  
  selectNode(nodeId: string): void {
    this.selectedNodes.clear(); // Single selection mode
    this.selectedNodes.add(nodeId);
    this.notifySelectionChange();
  }
  
  clearSelection(): void {
    this.selectedNodes.clear();
    this.notifySelectionChange();
  }
  
  isNodeSelected(nodeId: string): boolean {
    return this.selectedNodes.has(nodeId);
  }
  
  getSelectedNodes(): string[] {
    return Array.from(this.selectedNodes);
  }
  
  private notifySelectionChange(): void {
    // Integration point for future features
    console.log('Selection changed:', this.getSelectedNodes());
  }
}

Acceptance Criteria

Visual Requirements

  • Single click on node highlights it with themed background color
  • Previously selected node is automatically deselected when new node is selected
  • Smooth 200ms transition animation when selection state changes
  • Highlighting uses design system color tokens (consistent with theme)
  • High contrast highlighting meets WCAG AA standards for accessibility
  • No visual glitches or flashing during selection state transitions

Interaction Requirements

  • Click on node immediately selects it and provides visual feedback
  • Click on empty space (NodeTree background) clears current selection
  • Click on different node switches selection to that node
  • Selection state persists during component re-renders and navigation
  • No selection conflicts between multiple NodeTree instances

Accessibility Requirements

  • Selected nodes have aria-selected="true" attribute
  • Keyboard focus can navigate to selected nodes
  • Screen readers announce selection state changes
  • High contrast mode preserves selection visibility
  • Focus indicators remain visible on selected nodes

Performance Requirements

  • Selection changes respond within 16ms (60fps) for smooth animation
  • No performance degradation with 100+ nodes in hierarchy
  • Event handling uses efficient delegation patterns
  • CSS-based highlighting avoids layout thrashing
  • Memory usage remains stable during extended selection operations

Integration Requirements

  • Works seamlessly with existing BaseNode and NodeTree components
  • Compatible with current mock data service patterns
  • No breaking changes to existing node rendering or state management
  • Selection store can be easily extended for future multi-selection features
  • Integrates with existing design system tokens and theme switching

Code Quality Requirements

  • TypeScript compilation passes with strict mode enabled
  • All ESLint rules pass with no errors (development warnings acceptable)
  • Prettier formatting applied consistently
  • Svelte component validation passes with no accessibility violations
  • Unit test coverage >90% for selection store and node integration

Resources & References

Existing Implementation Patterns

  • BaseNode Component: src/lib/design/components/BaseNode.svelte - Foundation for selection integration
  • NodeTree Component: src/lib/components/NodeTree.svelte - Container for selection context
  • Design System: src/lib/design/tokens/ - Color tokens and theming patterns
  • Store Patterns: Existing Svelte stores for state management examples

Architecture Documentation

  • State Management: docs/architecture/core/state-management.md - Store implementation patterns
  • Component Architecture: docs/architecture/components/design-system.md - Component integration guidelines
  • Accessibility Standards: WCAG 2.1 guidelines for selection and focus management

UX Research References

  • Logseq Analysis: Research findings from multi-node highlighting investigation
  • Selection Patterns: Modern web application selection standards (VS Code, GitHub, Figma)
  • Accessibility Guidelines: ARIA patterns for tree widgets and selection states

Definition of Done

Functionality

  • Single-block selection works reliably in isolation
  • Visual highlighting provides immediate, clear feedback
  • Selection state management is robust and performant
  • Accessibility features are complete and tested
  • Integration with existing components is seamless

Code Quality

  • All acceptance criteria verified through testing
  • Component follows established Svelte and TypeScript patterns
  • Performance requirements met through profiling
  • No memory leaks or state management issues
  • Clean interfaces ready for multi-selection extension

Testing

  • Unit tests cover selection store logic comprehensively
  • Integration tests verify BaseNode selection behavior
  • Accessibility testing confirms WCAG compliance
  • Performance testing validates smooth transitions
  • Cross-browser compatibility verified (Chrome, Firefox, Safari)

Documentation

  • Selection store API documented with usage examples
  • Component integration patterns documented
  • Accessibility features documented for future reference
  • Extension points identified for multi-selection features
  • Code comments explain complex selection logic

This foundation will enable seamless extension to range selection, multi-selection, and advanced selection features in subsequent issues.

Activity

  1. malibio commented on Aug 3, 2026

    @malibio
    CollaboratorAuthor

    Closing as superseded by #278 (Copy text across node boundaries).

    This issue is part of the 2025 four-issue chain (#35 → #36 → #37 → #38) specifying a block-level, id-set selection model — a selection store of node ids, driven by click / shift+click / ctrl+click, intended to enable bulk operations on a set of nodes.

    Two reasons for closing:

    1. The specs are not implementable as written. They target a file layout that no longer exists: src/lib/design/components/BaseNode.svelte and src/lib/components/NodeTree.svelte (the codebase is kebab-case — base-node.svelte, node-tree.svelte), src/lib/design/tokens/ as a directory (it is a single file, lib/design/tokens.ts), Svelte 4 stores with $: reactivity (the codebase is on Svelte 5 runes), an in-repo docs/ directory, and LanceDB.

    2. The actual need is text-level, not block-level. The requirement driving this work is copying a passage of text that spans several nodes — a range that can start and end partway through a node. Block selection copies whole nodes and cannot express that. #278 now covers it, scoped to text-level selection and copy.

    Bulk operations on a set of nodes (delete, indent, move) remain unbuilt and untracked. There is currently no UI surface for node operations at all — no context menu (contextmenu has zero hits in src/lib), no toolbar, and deletion exists only as a side effect of Backspace in the editor (base-node-viewer.svelte:809-905). If that becomes wanted, it should be filed fresh against the current architecture rather than revived from these.

    Worth noting what already exists and is not lost by closing these: arrow-key node-to-node navigation (lib/commands/keyboard/navigate-up.command.ts:26, navigate-down.command.ts:26), a keyboard command registry (lib/services/keyboard-command-registry.ts:207-210), and single-node focus management (lib/services/focus-manager.svelte.ts).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions