iNote is a local-first notes app organized like a small personal library.
Instead of a flat list of notes, the app uses a simple hierarchy:
- Books
- Chapters inside books
- Pages inside chapters
- Loose pages that are not assigned to a chapter yet
The current UI is library-oriented rather than document-oriented. Navigation is built around a persistent top bar, a context-aware sidebar, and screen-level views for the root library, books, chapters, pages, search results, tag results, and loose pages.
The app is designed for browsing and organizing notes through structure first:
- the root screen acts like a library shelf
- books act as top-level collections
- chapters group related pages inside a book
- loose pages work like an inbox or staging area for notes that have not been filed yet
The app runs entirely in the browser with no backend, accounts, or sync layer. Data is stored locally in IndexedDB and the app uses internal view state instead of URL routing.
At a high level, the current implementation supports:
- Creating, renaming, and deleting books, chapters, pages, and loose pages
- Reordering books, chapters, and chapter pages with drag and drop
- Moving chapters between books
- Moving pages between chapters
- Moving loose pages into existing chapters
- Plain-text page editing with inline title editing
- Page tags and tag-based filtering
- Wiki-style links with derived backlinks
- Global page search
- A settings screen for library density (
Books per row) - An app menu with Help, Shortcuts, Settings, and Credits sections
The implementation is intentionally client-side and self-contained. useLibraryApp acts as the main orchestration layer, while store helpers handle mutation logic and selectors derive read-only views of the current library state.
- Books are the top-level containers.
- Chapters belong to a single book.
- Pages belong to a chapter.
- Loose pages are regular pages with no chapter assignment.
- Loose pages can be moved into an existing chapter from the page editor.
- Books, chapters, and pages all carry timestamps and persistent ordering information where applicable.
- The persisted data model is normalized rather than nested, so relationships are rebuilt from IDs instead of being stored as deeply nested objects.
- The top bar is always visible.
- Navigation is view-state based rather than URL based.
- The breadcrumb shows the current location and, when applicable, the parent location.
- A back/up control appears when the current view has a parent.
- The root screen presents books as gallery-style cards.
- Opening a book leads to its chapter list.
- Opening a chapter leads to its page list.
- Opening a page leads to the editor.
The app currently renders these main view states:
- root library view
- book view
- chapter view
- page view
- loose pages view
- search results view
- tagged pages view
The top bar includes:
- app menu button
- sidebar toggle button
- back/up button when relevant
- breadcrumb-style location display
- global search input
The sidebar changes with the current view instead of showing one static tree.
- Root view: Books and a short Loose Pages list
- Book view: Books plus chapters for the active book
- Chapter view: Chapters for the active book plus pages for the active chapter
- Chapter page view: Chapters for the parent book plus pages for the parent chapter
- Loose page view: Books plus the full Loose Pages list
- Search view: Books plus Loose Pages
- Tag view: Books plus Loose Pages
The sidebar also supports:
- Creating chapters in book context
- Creating pages in chapter context
- Creating loose pages in loose-page context
- Reordering books, chapters, and chapter pages where applicable
On smaller screens, the sidebar is toggleable instead of always visible. The app hook opens it automatically on desktop-width layouts and treats it as an overlay on narrower widths.
- The main books screen uses a shelf-like gallery layout.
- Clicking the main card surface opens the book.
- Inline rename, add chapter, delete, and book reordering are preserved on the card.
- The gallery density is controlled by the
Books per rowsetting. - Fewer books per row produce larger cards; more books per row produce more compact cards.
- Additional books wrap into new rows naturally and the main content scrolls vertically as shelves accumulate.
- Book view lists chapters for the active book.
- Chapter view lists pages for the active chapter.
- Both views support inline renaming from the list surface.
- Book view supports:
- creating chapters
- deleting the current book
- moving chapters to a different book
- drag-reordering chapters
- Chapter view supports:
- creating pages
- deleting the current chapter
- moving pages to a different chapter
- drag-reordering pages
- Loose pages have a dedicated screen.
- The root/search/tag sidebar shows a short loose-page list by default.
- The loose-pages screen shows the full loose-page list.
- Loose pages can be opened, renamed, and deleted like chapter pages.
- Loose pages can be moved into an existing chapter from the page editor.
- Search lives in the top bar.
- Search indexes pages only, not books or chapters directly.
- Text search looks across page titles and page content.
- Results appear in a dedicated Search Results view.
- Search results show:
- page title
- path (
Book / ChapterorLoose Pages) - a content snippet when available
- a match label
Search matching is currently:
- case-insensitive
- whitespace-normalized
- phrase-aware, with token fallback
Current search scope and output are intentionally narrow:
- search returns pages only
- books and chapters are not returned as separate result types
- results are sorted by score, then by most recently updated page
- empty searches show an instructional empty state rather than an all-pages view
- Tags are stored in lowercase normalized form.
- Tags are added from the page editor by typing into the tag input and pressing
Enter. - Page editor tag pills support:
- opening tag search
- removing the tag from the page
- Clicking tags in tag results can refine the active filter.
- Recent tags are remembered in app state and surfaced as quick-add suggestions in the dedicated tag results view.
There are two implemented tag flows:
- Click-driven tag filtering
- Clicking a tag opens the dedicated Tagged Pages view.
- The tag view supports multiple active tags.
- Multiple active tags use AND logic: a page must contain every selected tag.
- Removing an active tag updates the result set immediately.
- Slash-tag search from the top bar
- Search input beginning with slash-prefixed tags such as
/history /mythologyis parsed as tag search. - Slash-tag search still renders through the Search Results screen rather than the dedicated Tagged Pages view.
- The top-bar search input includes slash-tag autocomplete with keyboard navigation.
- The top-bar autocomplete uses currently known tags and can be navigated with arrow keys and
Enter.
The page editor is plain text and textarea-based.
- Page titles are editable inline.
- Page content is edited in a textarea.
- Clicking the content preview enters edit mode.
- Blurring the textarea exits edit mode and returns to preview mode.
- New pages and new loose pages auto-focus the editor after creation.
- Page text size is adjustable per page with a range control.
- The page screen includes inline title editing, tag editing, content editing, delete actions, and loose-page move actions in one place.
- The preview surface is also the entry point into edit mode, so the current experience is edit-in-place rather than split into separate read and edit screens.
Pages support wiki-style links in the form:
[[Page Title]]
Current behavior:
- Links are resolved from current page titles
- Matching is case-insensitive
- Extra whitespace inside brackets is ignored
- The first normalized title match wins if there are duplicate page titles
- Resolved links are clickable in preview mode
- Unresolved links remain visible but are not clickable
Backlinks are derived from current content and rendered in a Referenced by section on the page screen when present.
- Tags are displayed as pills under the page title.
- Each tag pill can:
- open tag filtering for that tag
- remove the tag from the page
- The editor validates new tags before saving them and ignores duplicates.
The current implementation supports drag-and-drop reordering for:
- Books
- Chapters within a book
- Pages within a chapter
Reordering is available in both the main content area and the sidebar where those lists are shown.
The same shared ReorderableList primitive is used across these surfaces, so drag/drop behavior is consistent between sidebar lists and main content lists.
The current implementation supports:
- Moving chapters to a different book
- Moving pages to a different chapter
- Moving loose pages into an existing chapter
Move flows are context-specific:
- chapter moves are initiated from book view
- page moves are initiated from chapter view
- loose-page moves are initiated from the page editor
- destination choices are constrained to valid books/chapters in the current data set
The app menu currently includes:
- Help
- Shortcuts
- Settings
- Credits
The implemented settings surface is still intentionally small. Right now it includes:
Books per rowfor the root books gallery
The app menu is not just a placeholder shell anymore, but it is still lightweight. It currently functions as the home for:
- usage/help copy
- implemented keyboard shortcuts
- the root-library density setting
- a small project credits/about section
- Library data is stored in IndexedDB under the
note-library-dbdatabase. - The app stores:
- one snapshot for library data
- one snapshot for app settings
- Persistence is debounced during normal interaction.
- A
pagehideflush attempts to save the latest library data and settings when the page is being left. - Hydration normalizes persisted state defensively, including missing or invalid settings values.
- Current persistence is coarse-grained: the library graph is written as a whole snapshot rather than being synced entity-by-entity.
- In production, the app registers a service worker.
- The service worker caches the app shell and same-origin GET responses.
- In development, existing service workers and
note-library-*caches are cleaned up on load to avoid stale cached shells. - Note content still lives in IndexedDB rather than in the service worker cache.
The current code clearly implements these keyboard interactions:
Entercommits inline title editsEscapecancels inline title editsEnteradds a tag from the page editor tag inputEscapecloses the app menu- Search and tag suggestion inputs support:
ArrowDownArrowUpEnterEscape
These are implemented as local interaction controls, not as a global command system. The app menu itself explicitly describes broader keyboard navigation as future work rather than current functionality.
Key parts of the current codebase:
src/App.tsx- main shell composition and route-to-view rendering
src/hooks/useLibraryApp.ts- central app orchestration, view state, persistence, search, and actions
src/views/- root, book, chapter, and loose-page screens
src/components/- top bar, sidebar, app menu, page editor, search results, tag results, and shared UI pieces
src/store/- library mutation helpers and selectors
src/utils/- search, tags, links/backlinks, IDs, dates, and page-state helpers
src/db/indexedDb.ts- IndexedDB persistence helpers
Contributors should look first at:
src/hooks/useLibraryApp.tsfor app behavior and screen transitionssrc/store/libraryStore.tsfor mutations and persistence-facing data rulessrc/store/librarySelectors.tsfor derived lists and contextual navigation datasrc/utils/search.ts,src/utils/tags.ts, andsrc/utils/pageLinks.tsfor core derived behavior
Install dependencies:
npm installStart the dev server:
npm run devBuild the app:
npm run buildPreview the production build:
npm run previewThe following limitations are clearly visible in the current implementation:
- Editing is plain text only; there is no rich text or block editor.
- Navigation is internal state only; there are no deep-linkable URL routes.
- There is no sync, collaboration, or authentication.
- Search operates on pages only, not books or chapters as first-class search entities.
- Clicked tag filters and typed slash-tag queries do not use the same result screen:
- clicked tags use the dedicated Tagged Pages view
- slash-tag queries render through Search Results
- Loose pages are shown by recency rather than manual ordering.
- Duplicate page titles can make link resolution ambiguous because the first normalized match wins.
- The settings surface is still narrow and currently exposes only library density for the root books screen.
- Search/tag behavior is useful but not fully unified into one filtering model.