Skip to content

feat: add calendar notes directory preference#642

Draft
colebemis wants to merge 14 commits intomainfrom
calendar-notes-directory
Draft

feat: add calendar notes directory preference#642
colebemis wants to merge 14 commits intomainfrom
calendar-notes-directory

Conversation

@colebemis
Copy link
Member

@colebemis colebemis commented Jan 26, 2026

Summary

Add a user preference to configure which directory contains calendar notes (daily and weekly notes with ISO date filenames like 2026-01-26.md or 2025-W04.md).

What This Does

Users can now set a custom directory for calendar notes in Settings → Notes. For example, setting it to journal means:

  • Daily notes save to journal/2026-01-26.md instead of 2026-01-26.md
  • Weekly notes save to journal/2025-W04.md instead of 2025-W04.md
  • The calendar component navigates to the correct paths

Key Implementation Details

Config Storage

  • Config stored in .lumen/config.json at repo root (committed to git, shared across clones)
  • Schema validated with Zod: { calendarNotesDirectory?: string }
  • Also persisted to localStorage for immediate access before git clone completes

Note Type Detection

parseNote(id, content, calendarNotesDir) uses isCalendarNoteId() which enforces two conditions:

  1. Basename pattern: Last path segment must match ISO date (YYYY-MM-DD) or week (YYYY-Wnn)
  2. Directory prefix (when configured):
    • Empty string "": Note must be at root (no / in path)
    • Non-empty (e.g., journal): Note must start with journal/ and have the date as the immediate child

This ensures a note like random/2026-01-26.md is NOT treated as a daily note when the config is set to journal.

Calendar Navigation

  • useBuildCalendarNoteId() hook returns a function that prepends the configured directory
  • Calendar component extracts basename via getCalendarNoteBasename() for date parsing
  • Links use the full path: <Link to="/notes/$" params={{ _splat: "journal/2026-01-26" }}>

Files Changed

  • src/utils/config.ts - Config schema, parsing, path utilities (isCalendarNoteId, buildCalendarNoteId, etc.)
  • src/hooks/config.ts - React hooks (useCalendarNotesDirectory, useBuildCalendarNoteId, useSaveConfig)
  • src/global-state.ts - Atoms for config state, passes calendarNotesDir to parseNote()
  • src/utils/parse-note.ts - Accepts optional calendarNotesDir parameter for type detection
  • src/components/calendar.tsx - Uses hooks to build correct note IDs and extract basenames
  • src/routes/_appRoot.notes_.$.tsx - Uses isCalendarNoteId and hooks for consistent behavior
  • Settings page - UI for changing the directory preference

Behavior Notes

  • No auto-migration: Changing the directory does not move existing files
  • Single setting: Same directory for both daily and weekly notes
  • Empty = root: Leave blank to use repository root (backward-compatible default)
  • Path normalization: Leading/trailing slashes are stripped automatically

Test Coverage

  • 30 tests for config utilities covering:
    • Config parsing and serialization
    • Directory path normalization
    • isCalendarNoteId with/without directory config (empty, configured, nested paths)
    • buildCalendarNoteId path construction
    • getCalendarNoteBasename extraction
  • parse-note tests for note type detection with calendarNotesDir parameter
  • All existing tests continue to pass

Add a user preference to set a custom directory for calendar notes
(daily/weekly notes with ISO date filenames like 2026-01-26.md).

Changes:
- Add src/utils/config.ts with config schema and path utilities
- Add src/hooks/config.ts with hooks for reading/saving config
- Update global-state.ts to derive note IDs based on config
- Update hooks/note.ts to save notes to the correct directory
- Add Notes section in settings page with UI to change the directory
- Store config in .lumen/config.json (committed to repo)

The config supports a single calendarNotesDirectory setting that applies
to both daily and weekly notes. When not set, notes are stored in the
repository root (the current default behavior).
@vercel
Copy link

vercel bot commented Jan 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
lumen Ready Ready Preview, Comment Jan 27, 2026 4:07am
lumen-storybook Ready Ready Preview, Comment Jan 27, 2026 4:07am

Copy link
Member Author

@colebemis colebemis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note ids should always be just the filepath without the ".md". That shouldn't change when a calendar note directory is specified. What needs to update is, for example, when we type "today" and hit enter it would insert "[[path/to/2025-01-01]]" instead of [[2025-01-01]]. Also "[[path/to/2025-01-01]]" should render as the formatted date instead of "path/to/2025-01-01". Clicking on the calendar nav item and command menu should go to path/to/yyyy-mm-dd instead of yyyy-mm-dd. And so on


const GITHUB_USER_STORAGE_KEY = "github_user" as const
const MARKDOWN_FILES_STORAGE_KEY = "markdown_files" as const
const CONFIG_STORAGE_KEY = "lumen_config" as const
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This key should be repo specific because eventually we might want to have multiple repos cloned at once

Based on feedback, note IDs should be the full path (e.g., journal/2026-01-26), not just the basename.

Changes:
- Simplified config utils: removed getNoteFilepath/getNoteIdFromFilepath
- Added buildCalendarNoteId and getCalendarNoteBasename helpers
- Updated notesAtom to use filepath as ID directly
- Updated note hooks to use simple ID + .md for filepath
- Updated command-menu to use buildCalendarNoteId for navigation
- Updated tests

Still TODO:
- Update nav-items.tsx (calendar nav item)
- Update calendar-header.tsx (today button)
- Update link rendering to display calendar note IDs nicely
- nav-items.tsx: Use useBuildCalendarNoteId for Calendar link navigation,
  update hasDailyNoteAtom to check full path, use isCalendarNoteId for
  detecting active calendar view
- calendar-header.tsx: Update Today/This week button and prev/next
  navigation to use buildCalendarNoteId
- note-link.tsx: Use isCalendarNoteId + getCalendarNoteBasename to detect
  and properly route calendar notes like 'journal/2026-01-26'
- date-link.tsx / week-link.tsx: Accept noteId (full path) and extract
  basename for formatting display
- property-value.tsx: Use useBuildCalendarNoteId for date property links

Wikilinks like [[journal/2026-01-26]] now display as formatted dates
(e.g., 'January 26, 2026') instead of showing the path.
Updated three files to use getCalendarNoteBasename() before checking
if a note ID is a valid date/week string:

- src/routes/_appRoot.notes_.$.tsx: Extract basename for isDailyNote/
  isWeeklyNote checks, template rendering, date formatting, and
  DaysOfWeek component

- src/components/days-of-week.tsx: Use useBuildCalendarNoteId() to
  create full note IDs for navigation and lookup, while keeping
  basename for date formatting

- src/components/note-preview.tsx: Use basename for birthday label
  calculation when viewing calendar notes

This fixes calendar and calendar header rendering when using the
calendarNotesDirectory config (e.g., note IDs like journal/2026-01-26).
Convert dateCompletion to useDateCompletion hook that uses
useBuildCalendarNoteId() to insert the full note path.

Now typing 'today' and hitting enter inserts [[journal/2026-01-26]]
instead of [[2026-01-26]] when calendarNotesDirectory is configured.
When notes have paths like 'journal/2026-W04', the formatWeek and formatDate
functions need just the basename (2026-W04), not the full path. This was
causing 'Invalid week: 0NaN-WNaN' errors.

Fixes:
- parse-note.ts: Use getCalendarNoteBasename() before formatDate/formatWeek
- note-preview.tsx: Use getCalendarNoteBasename() for date/week distance formatting
When using VITE_GITHUB_PAT for dev authentication, the token was not
being saved to localStorage. This meant users had to click 'Sign in'
every time the page reloaded.

The OAuth flow worked because resolveUser() saves to localStorage when
processing URL params. The PAT flow triggered SIGN_IN directly without
saving to localStorage.

Fixed by saving to localStorage in the setGitHubUser action when
handling the SIGN_IN event.
The Calendar component was receiving full note IDs like 'journal/2025-01-26'
but passing them directly to parseISO() and comparing them against date/week
strings without extracting the basename first.

This caused 'Invalid week: 0NaN-WNaN' errors when clicking calendar nav links
when a calendarNotesDirectory was configured.

Fixed by:
- Importing getCalendarNoteBasename from utils/config
- Extracting activeBasename from activeNoteId before parsing
- Using activeBasename for all date/week comparisons in Calendar,
  MonthGrid, and MonthWeekRow components
Issue 1: Root date notes should not be calendar notes when directory is configured
- Updated isCalendarNoteId() to accept optional calendarNotesDir parameter
- When directory is configured, only notes in that directory are calendar notes
- A file like 2026-01-26.md at root is NOT a calendar note when journal/ is configured
- Added useIsCalendarNoteId() hook for React components
- Updated note-link.tsx and nav-items.tsx to use the hook

Issue 2: Calendar component navigation should use configured directory
- Updated calendar.tsx to use useBuildCalendarNoteId() for all links
- Calendar dates/weeks now navigate to correct paths (e.g., journal/2026-01-26)
- Note lookups also use full IDs with directory prefix

Added comprehensive tests for isCalendarNoteId with directory parameter.
…onfig

- note-preview.tsx: Use useIsCalendarNoteId() hook to properly check if
  current note is a calendar note (respects configured directory) before
  showing birthday labels

- parse-note.ts: Use basename instead of full id when determining note
  type (daily/weekly), so notes like 'journal/2026-01-26' are correctly
  recognized as daily notes

Fixes calendar directory config not being respected in note type detection
and birthday label display.
parseNote() now accepts optional calendarNotesDir parameter to enforce
that daily/weekly notes must be in the configured calendar directory.

Examples with calendarNotesDirectory: 'journal':
- journal/2026-01-26 → daily note ✓ (correct prefix + valid date)
- 2026-01-26 → NOT a daily note ✗ (missing required prefix)
- other/2026-01-26 → NOT a daily note ✗ (wrong prefix)

Examples with no directory configured (empty string):
- 2026-01-26 → daily note ✓ (no prefix needed)
- journal/2026-01-26 → NOT a daily note ✗ (has prefix when none expected)

Backward compatible: when calendarNotesDir is undefined, only basename
is checked (previous behavior).

Fixes #642
…etection

Previously, the note route was using isValidDateString/isValidWeekString
on just the basename to determine if a note was a calendar note. This
ignored the calendarNotesDir config, causing bugs where root date notes
(e.g., /notes/2026-01-26) would incorrectly show calendar UI even when
config specified a different directory (e.g., 'journal/').

Now:
- For template selection (before note is parsed), use isCalendarNoteId()
  which checks both basename AND directory config
- For UI rendering (calendar, headers, DaysOfWeek), use parsedNote.type
  which is already correctly computed by parseNote()

This ensures consistent behavior: note.type from parseNote() is THE
source of truth for whether a note is daily/weekly/note/template.
Shorter JSON key for user-facing config file.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant