How DrawDB Handles Notes and Annotations in Diagrams: A Technical Deep-Dive

DrawDB manages notes through a dedicated React context that provides CRUD operations, with UI components consuming a useNotes hook to create, position, edit, and delete draggable annotations on the canvas.

Notes and annotations in database diagrams serve a critical purpose: documenting design decisions, marking TODOs, or explaining complex relationships. DrawDB implements this feature as a lightweight, fully-integrated subsystem rather than an afterthought. This article examines the architecture, state management, and persistence mechanisms that power DrawDB's note functionality, based on the drawdb-io/drawdb source code.

State Management with NotesContext

The foundation of DrawDB's note system resides in src/context/NotesContext.jsx. This file establishes a React context that centralizes all note-related state across the application.

The context provider wraps the entire editor, ensuring any component can access notes without prop drilling. The context stores an array of note objects, each containing:

  • id: Unique identifier for the note
  • x, y: Canvas coordinates for positioning
  • content: The annotation text
  • theme: Background color styling

According to the drawdb-io/drawdb source code, the context exposes four primary actions: addNote, updateNote, removeNote, and getNotes. These pure functions modify the React state, triggering re-renders wherever notes are consumed.

The useNotes Hook Interface

To abstract the context API, DrawDB provides src/hooks/useNotes.js. This custom hook simplifies component integration by returning an object with the available actions.

Components throughout the editor—toolbar buttons, canvas elements, and export utilities—all rely on this hook rather than directly importing the context. This pattern decouples the UI from state implementation details and enables easier testing.

// Consuming the notes API in a component
import { useNotes } from '../hooks/useNotes';

function CanvasNotes() {
  const { addNote, updateNote, removeNote, notes } = useNotes();
  
  // Component logic using note operations
  return notes.map(note => <NoteElement key={note.id} note={note} />);
}

Creating Notes: The Toolbar Integration

User interaction begins at the toolbar through src/icons/IconAddNote.jsx. Clicking this icon invokes addNote with the current cursor position.

The creation flow works as follows:

  1. User clicks the Add Note icon
  2. Event coordinates are captured from e.nativeEvent
  3. addNote receives position data plus default styling from src/data/constants.js
  4. A new note object enters the context state
  5. React re-renders the canvas, displaying the draggable note
// Triggering note creation from toolbar
import { useNotes } from '../hooks/useNotes';
import { defaultNoteTheme } from '../data/constants';

function AddNoteButton() {
  const { addNote } = useNotes();

  const handleClick = (e) => {
    const { x, y } = e.nativeEvent;
    addNote({ 
      x, 
      y, 
      content: '', 
      theme: defaultNoteTheme 
    });
  };

  return <IconAddNote onClick={handleClick} />;
}

Rendering and Editing Note Elements

Each note renders as a draggable, resizable DOM element positioned absolutely on the canvas. The note component—integrated within src/pages/Editor.jsx—consumes the NotesContext for live updates.

The editing interface supports:

  • Inline text editing via a <textarea> element
  • Drag repositioning with coordinate updates on onDragEnd
  • Theme customization through color selection
  • Deletion with immediate context removal
// Core note component implementation pattern
import { useNotes } from '../hooks/useNotes';

function Note({ note }) {
  const { updateNote, removeNote } = useNotes();

  return (
    <div
      className="drawdb-note"
      style={{ 
        left: note.x, 
        top: note.y, 
        background: note.theme 
      }}
      draggable
      onDragEnd={(e) => updateNote(note.id, { x: e.x, y: e.y })}
    >
      <textarea
        value={note.content}
        onChange={(e) => updateNote(note.id, { content: e.target.value })}
      />
      <button onClick={() => removeNote(note.id)}>
        Delete
      </button>
    </div>
  );
}

Styling and Theming Configuration

Visual consistency for notes originates in src/data/constants.js. The defaultNoteTheme constant defines the initial background color (#fcf7ac, a light yellow) that distinguishes notes from tables and relationships.

Users can override this default per-note through the editing UI. The selected theme persists in the note object's state and survives export/import cycles.

Persisting Annotations Across Formats

Notes integrate into DrawDB's export pipeline, ensuring annotations survive beyond the current editing session. Two primary export paths handle note serialization:

DBML Export (src/utils/exportAs/dbml.js)

The DBML formatter appends note declarations after table definitions:

function exportDbml(tables, notes) {
  const tablePart = tables.map(formatTable).join('\n');
  const notesPart = notes
    .map((n) => `Note ${n.id} "${n.content}" at (${n.x}, ${n.y})`)
    .join('\n');
  return `${tablePart}\n${notesPart}`;
}

Documentation/Image Export

The src/utils/exportAs/documentation.js utility includes notes in generated PNG exports and shareable URLs. Position and content data serialize alongside schema information, enabling full diagram restoration.

Complete Interaction Flow

Understanding the full lifecycle clarifies how these subsystems cooperate:

  1. Initialization: NotesContextProvider mounts in Editor.jsx, establishing empty note state
  2. Creation: Toolbar click → useNotes().addNote() → state update → canvas re-render
  3. Positioning: User drags note → onDragEnd fires → updateNote commits new coordinates
  4. Editing: Text input → onChange debounced → updateNote synchronizes content
  5. Styling: Color picker selection → updateNote applies new theme value
  6. Deletion: Remove button → removeNote filters state → component unmounts
  7. Export: Formatter iterates notes array → serializes to target format

Summary

  • Centralized state: NotesContext.jsx maintains the source of truth for all diagram annotations
  • Clean API: useNotes hook provides ergonomic access to CRUD operations without context boilerplate
  • Visual integration: Notes render as first-class canvas elements with drag, resize, and edit capabilities
  • Customizable appearance: Default theming from constants.js with per-note overrides
  • Persistence guarantee: Export utilities in dbml.js and documentation.js preserve notes across formats
  • React-native architecture: Pure state updates ensure UI consistency without external dependencies

Frequently Asked Questions

Where are notes stored in DrawDB's codebase?

Notes live in a React context defined in src/context/NotesContext.jsx, with the hook interface at src/hooks/useNotes.js. This pattern keeps note state accessible throughout the component tree while avoiding prop drilling.

Can notes be exported with the database schema?

Yes. The DBML export utility in src/utils/exportAs/dbml.js serializes notes alongside tables, including their content and canvas coordinates. Notes also appear in PNG exports and shareable URLs through src/utils/exportAs/documentation.js.

How does DrawDB position notes on the canvas?

Each note stores x and y pixel coordinates in its state object. When rendered, the note component applies these as absolute CSS positioning values. Drag operations update these coordinates through updateNote, with the new position persisting immediately.

Is there a default style for new notes?

Yes. src/data/constants.js exports defaultNoteTheme set to #fcf7ac (light yellow). This value applies automatically when creating notes, though users can change individual note colors through the editing interface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →