Documentation Index Fetch the complete documentation index at: https://docs.glyphformac.com/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Glyph uses React Context for all global state management. No Redux, no Zustand—just plain React.
Contexts are layered: SpaceContext wraps FileTreeContext wraps ViewContext, etc.
Context Hierarchy
function App () {
return (
< SpaceProvider > { /* Space lifecycle */ }
< FileTreeProvider > { /* Files, tags, active file */ }
< ViewProvider > { /* Active view document */ }
< UIProvider > { /* Sidebar, search state */ }
< EditorProvider > { /* TipTap editor instance */ }
< AppShell />
</ EditorProvider >
</ UIProvider >
</ ViewProvider >
</ FileTreeProvider >
</ SpaceProvider >
);
}
SpaceContext
Location : src/contexts/SpaceContext.tsx
Manages space lifecycle (create, open, close).
State Shape
interface SpaceContextValue {
// App metadata
info : AppInfo | null ; // App name, version
// Current space
spacePath : string | null ; // '/Users/me/my-space'
spaceSchemaVersion : number | null ; // 1
// History
lastSpacePath : string | null ; // For "Continue" button
recentSpaces : string []; // Up to 20 recent paths
// Index state
isIndexing : boolean ; // Index rebuild in progress
// Lifecycle
settingsLoaded : boolean ; // Settings loaded from disk
error : string ; // Error message
setError : ( error : string ) => void ;
// Actions
onOpenSpace : () => Promise < void >;
onOpenSpaceAtPath : ( path : string ) => Promise < void >;
onContinueLastSpace : () => Promise < void >;
onCreateSpace : () => Promise < void >;
closeSpace : () => Promise < void >;
startIndexRebuild : () => Promise < void >;
}
Usage
Opening a Space
Checking Space State
import { useSpace } from '@/contexts/SpaceContext' ;
function WelcomeScreen () {
const { onOpenSpace , onContinueLastSpace , lastSpacePath } = useSpace ();
return (
< div >
< Button onClick = { onOpenSpace } > Open Space </ Button >
{ lastSpacePath && (
< Button onClick = { onContinueLastSpace } >
Continue : { lastSpacePath }
</ Button >
)}
</ div >
);
}
FileTreeContext
Location : src/contexts/FileTreeContext.tsx
Manages file browser state and tag index.
State Shape
interface FileTreeContextValue {
// File tree data
rootEntries : FsEntry []; // Files/dirs at root
childrenByDir : Record < string , FsEntry []>; // Cached children by dir path
expandedDirs : Set < string >; // Which dirs are expanded
// Updaters (for hooks to modify state)
updateRootEntries : ( next : FsEntry [] | (( prev : FsEntry []) => FsEntry [])) => void ;
updateChildrenByDir : ( next : ...) => void ;
updateExpandedDirs : ( next : Set < string > | (( prev : Set < string >) => Set < string >)) => void ;
// Active file
activeFilePath : string | null ; // 'notes/example.md'
setActiveFilePath : ( path : string | null ) => void ;
// Derived state (computed from activeFilePath)
activeNoteId : string | null ; // Same as activeFilePath if .md
activeNoteTitle : string | null ; // Filename without extension
// Tag index
tags : TagCount []; // [{ tag: 'research', count: 42 }]
tagsError : string ;
refreshTags : () => Promise < void >;
}
Usage
Reading File Tree
Updating State
import { useFileTreeContext } from '@/contexts/FileTreeContext' ;
function FileTreePane () {
const { rootEntries , expandedDirs } = useFileTreeContext ();
return (
< div >
{ rootEntries . map ( entry => (
< FileTreeItem
key = {entry. rel_path }
entry = { entry }
isExpanded = {expandedDirs.has(entry.rel_path)}
/>
))}
</ div >
);
}
ViewContext
Location : src/contexts/ViewContext.tsx
Manages “view documents” (folder, tag, search, database views).
State Shape
interface ViewContextValue {
activeViewDoc : ViewDoc | null ; // Current view (folder, tag, etc.)
setActiveViewDoc : ( doc : ViewDoc | null ) => void ;
isLoadingView : boolean ;
viewError : string ;
}
type ViewDoc =
| FolderViewDoc
| TagViewDoc
| SearchViewDoc
| DatabaseViewDoc ;
interface FolderViewDoc {
type : 'folder' ;
dir : string ;
files : FsEntry [];
subfolders : FolderViewFolder [];
note_previews : ViewNotePreview [];
}
Usage
import { useViewContext } from '@/contexts/ViewContext' ;
function MainContent () {
const { activeViewDoc } = useViewContext ();
if ( activeViewDoc ?. type === 'folder' ) {
return < FolderView doc ={ activeViewDoc } />;
}
if ( activeViewDoc ?. type === 'database' ) {
return < DatabasePane doc ={ activeViewDoc } />;
}
return < MarkdownEditorPane />;
}
UIContext
Location : src/contexts/UIContext.tsx
Manages UI chrome state (sidebar, search, modals).
State Shape
interface UIContextValue {
// Sidebar
activePane : 'files' | 'tags' | 'ai' | 'tasks' ;
setActivePane : ( pane : 'files' | 'tags' | 'ai' | 'tasks' ) => void ;
isSidebarCollapsed : boolean ;
toggleSidebar : () => void ;
// Command palette (Cmd+K)
isCommandPaletteOpen : boolean ;
openCommandPalette : () => void ;
closeCommandPalette : () => void ;
// Search
searchQuery : string ;
setSearchQuery : ( query : string ) => void ;
// Preview pane
activePreviewPath : string | null ;
setActivePreviewPath : ( path : string | null ) => void ;
}
Usage
Sidebar Panes
Command Palette
function SidebarHeader () {
const { activePane , setActivePane } = useUIContext ();
return (
< div >
< IconButton
onClick = {() => setActivePane ( 'files' )}
data - active = { activePane === 'files' }
>
< FileIcon />
</ IconButton >
< IconButton
onClick = {() => setActivePane ( 'ai' )}
data - active = { activePane === 'ai' }
>
< SparkleIcon />
</ IconButton >
</ div >
);
}
EditorContext
Location : src/contexts/EditorContext.tsx
Manages TipTap editor instance.
State Shape
import type { Editor } from '@tiptap/react' ;
interface EditorContextValue {
editor : Editor | null ; // TipTap editor instance
setEditor : ( editor : Editor | null ) => void ;
isEditing : boolean ; // Focus state
saveState : 'saved' | 'saving' | 'unsaved' | 'error' ;
setSaveState : ( state : 'saved' | 'saving' | 'unsaved' | 'error' ) => void ;
}
Usage
Creating Editor
Using Editor
import { useEditor } from '@tiptap/react' ;
import { useEditorContext } from '@/contexts/EditorContext' ;
function MarkdownEditorPane () {
const { setEditor , setSaveState } = useEditorContext ();
const editor = useEditor ({
extensions: [ /* ... */ ],
onUpdate : () => {
setSaveState ( 'unsaved' );
debouncedSave ();
},
onFocus : () => setIsEditing ( true ),
onBlur : () => setIsEditing ( false ),
});
useEffect (() => {
setEditor ( editor );
return () => setEditor ( null );
}, [ editor , setEditor ]);
return < EditorContent editor ={ editor } />;
}
Custom Context Pattern
All contexts follow this pattern:
Define context value type
interface MyContextValue {
data : string ;
setData : ( data : string ) => void ;
}
Create context with null default
const MyContext = createContext < MyContextValue | null >( null );
Create provider component
export function MyProvider ({ children } : { children : ReactNode }) {
const [ data , setData ] = useState ( '' );
const value = useMemo < MyContextValue >(
() => ({ data , setData }),
[ data ]
);
return (
< MyContext . Provider value = { value } >
{ children }
</ MyContext . Provider >
);
}
Create typed hook
export function useMyContext () : MyContextValue {
const ctx = useContext ( MyContext );
if ( ! ctx ) {
throw new Error ( 'useMyContext must be used within MyProvider' );
}
return ctx ;
}
Split Contexts
Instead of one giant context:
interface AppContextValue {
user : User ;
theme : Theme ;
files : File [];
// ... 20 more fields
}
// Every component re-renders when ANY field changes!
Use multiple small contexts:
< UserProvider >
< ThemeProvider >
< FileProvider >
{ /* Components only re-render when their context changes */ }
</ FileProvider >
</ ThemeProvider >
</ UserProvider >
Memoize Context Value
Always memoize the context value:
const value = useMemo < MyContextValue >(
() => ({ data , setData }),
[ data ] // Only recompute when data changes
);
Without useMemo, context consumers re-render on every provider render.
Selector Pattern
For large contexts, expose selectors:
interface FileTreeContextValue {
// Instead of exposing entire state:
// state: { rootEntries, childrenByDir, ... }
// Expose specific selectors:
useRootEntries : () => FsEntry [];
useChildrenByDir : () => Record < string , FsEntry []>;
useExpandedDirs : () => Set < string >;
}
// Components only subscribe to what they use
function FileList () {
const rootEntries = useFileTreeContext (). useRootEntries ();
// Only re-renders when rootEntries changes
}
Next Steps
Components Component architecture