The Problem With Command Palettes
You're working in AiFiler. You want to move a document to a folder. So you open the command palette. Then you realize there's a different command palette for documents, another for matrices, another for knowledge views. Each one has its own set of commands. Each one requires you to remember a different set of shortcuts.
This is the tax of feature accumulation. As products grow, they collect UI surfaces. Command palettes, context menus, right-click handlers, keyboard shortcuts—they fragment across the codebase. Users end up learning 15 different ways to do 5 things.
In late 2024, we decided to collapse all of that into one place: the Universal Command (Ctrl+Shift+A). Not a cosmetic unification—a genuine architectural rethinking of how AiFiler interprets what you're trying to do.
The Architecture: Intent → Handler → Action
The Universal Command works like this:
- User input (text, keyboard shortcut, or context) arrives
- Intent parser analyzes what the user is asking for
- Intent handler (one of 87) determines what to do
- Action executor performs the operation
- Result is returned to the user
This is implemented across three key files:
lib/intelligence/universalRouter.ts— The entry point that decides which handler to calllib/intelligence/intentHandlers.ts— 87 handler functions covering every major operation in AiFilerlib/intelligence/actionExecutor.ts— The execution layer that actually modifies state, calls APIs, or triggers UI changes
Here's the conceptual flow:
User Input (text or shortcut)
↓
Intent Heuristics (lib/intentHeuristics.ts)
↓
Universal Router (determine which handler)
↓
Intent Handler (87 options: move, copy, delete, share, etc.)
↓
Action Executor (apply changes)
↓
State Update + User Feedback
How Intent Recognition Works
The system doesn't rely on exact command matching. Instead, it uses intent heuristics—a set of pattern-matching rules that figure out what you mean, even if you don't phrase it perfectly.
When you type something into Universal Command, the system runs it through several heuristics:
- Keyword matching: Does the input contain "move", "copy", "delete"? → Intent identified
- Context awareness: What's currently selected? What view are you in? → Narrows possibilities
- Fuzzy matching: Did you type "mv" instead of "move"? → Still works
- Abbreviation expansion: "cr" → "create", "del" → "delete"
For example, if you type "move to client folder" with a document selected:
- The parser sees "move" and "to"
- It checks context: you have a document selected
- It identifies the intent as
MOVE_DOCUMENT - It searches for a folder matching "client"
- It executes the move
If you just type "client" with a document selected in a folder view, the system might infer you want to move it to that folder. Context matters.
The 87 Intent Handlers: What They Cover
The handlers are organized into logical groups:
Document Operations (15 handlers)
MOVE_DOCUMENT,COPY_DOCUMENT,DELETE_DOCUMENTRENAME_DOCUMENT,DUPLICATE_DOCUMENTSHARE_DOCUMENT,UNSHARE_DOCUMENTARCHIVE_DOCUMENT,RESTORE_DOCUMENTPIN_DOCUMENT,UNPIN_DOCUMENTTAG_DOCUMENT,REMOVE_TAGOPEN_DOCUMENT,PREVIEW_DOCUMENT
Matrix Operations (18 handlers)
CREATE_MATRIX,DELETE_MATRIX,RENAME_MATRIXADD_ROW,DELETE_ROW,DUPLICATE_ROWADD_COLUMN,DELETE_COLUMN,MOVE_COLUMNSORT_MATRIX,FILTER_MATRIX,GROUP_BYEXPORT_MATRIX,IMPORT_DATALOCK_MATRIX,UNLOCK_MATRIXAPPLY_TEMPLATE
Knowledge Graph Operations (12 handlers)
CREATE_ENTITY,DELETE_ENTITY,MERGE_ENTITIESCREATE_RELATIONSHIP,DELETE_RELATIONSHIPADD_EVIDENCE,REMOVE_EVIDENCEUPDATE_ENTITY_TYPE,LINK_DOCUMENTUNLINK_DOCUMENT,VIEW_GRAPH
Search & Navigation (10 handlers)
SEARCH_DOCUMENTS,SEARCH_MATRICES,SEARCH_KNOWLEDGEFILTER_BY_TAG,FILTER_BY_DATE,FILTER_BY_TYPESORT_BY_NAME,SORT_BY_DATE,SORT_BY_RELEVANCEADVANCED_SEARCH
Workspace & Settings (15 handlers)
CREATE_WORKSPACE,DELETE_WORKSPACE,RENAME_WORKSPACEINVITE_USER,REMOVE_USER,CHANGE_ROLEENABLE_FEATURE,DISABLE_FEATUREEXPORT_WORKSPACE,IMPORT_WORKSPACEMANAGE_INTEGRATIONS,VIEW_AUDIT_LOGCONFIGURE_WEBHOOKS,MANAGE_API_KEYS
AI & Intelligence (17 handlers)
GENERATE_SUMMARY,GENERATE_OUTLINEEXTRACT_ENTITIES,CLASSIFY_DOCUMENTSUGGEST_TAGS,FIND_SIMILARANSWER_QUESTION,EXPLAIN_CONCEPTTRANSLATE_DOCUMENT,REWRITE_TEXTGENERATE_REPORT,CREATE_PRESENTATIONBULK_PROCESS,SCHEDULE_TASKVIEW_AI_HISTORY,CLEAR_CACHE
Batch Operations (3 handlers)
BATCH_MOVE,BATCH_DELETE,BATCH_TAG
Each handler is a pure function that takes context (selected items, current view, user permissions) and returns an action object. The action object describes what to do—it doesn't do it immediately. This separation is critical for testability and for supporting undo/redo.
A Real Handler: How MOVE_DOCUMENT Works
Here's the conceptual implementation of one handler:
// Simplified version of MOVE_DOCUMENT handler
const handleMoveDocument = async (context) => {
const { selectedDocuments, targetFolder, userId } = context;
// Validate permissions
if (!canUserMove(userId, selectedDocuments, targetFolder)) {
return { error: "Insufficient permissions" };
}
// Build the action
const action = {
type: "MOVE_DOCUMENT",
payload: {
documentIds: selectedDocuments.map(d => d.id),
targetFolderId: targetFolder.id,
timestamp: Date.now(),
},
};
// Execute
const result = await actionExecutor.execute(action);
// Return result for UI feedback
return {
success: true,
movedCount: selectedDocuments.length,
targetFolder: targetFolder.name,
};
};
In reality, the implementation is more complex—it handles partial failures, optimistic updates, conflict resolution, and audit logging. But the pattern is the same: validate → build → execute → report.
Why This Architecture Matters
Single source of truth for commands: Every operation in AiFiler flows through the same 87 handlers. If you want to add a new feature, you add a handler. If you want to support it in the command palette, API, webhook, or mobile app—it's already there.
Testability: Each handler is a pure function. You can test "if user selects 5 documents and runs BATCH_DELETE, does it handle permissions correctly?" without spinning up a UI.
Extensibility: Adding a new intent is straightforward. You write a handler, register it in the router, and it works everywhere the router is used.
Auditability: Every action flows through the executor, which logs it. Compliance teams can see exactly what happened, when, and by whom.
Undo/Redo: Because actions are declarative, not imperative, the system can reverse them. If you move 50 documents and immediately regret it, undo works because we know exactly what changed.
The Intent Heuristics Layer
The real magic isn't in the handlers—it's in figuring out which handler to call.
When you type "move doc to Q3 folder", the system needs to:
- Extract the intent:
MOVE - Identify the subject:
document(singular or plural?) - Find the target:
Q3 folder(search for folders matching "Q3") - Determine scope: Am I moving one document or many?
This is handled in lib/intentHeuristics.ts, which uses a combination of:
- Regex patterns for common commands ("move X to Y")
- NLP-lite parsing (tokenization, part-of-speech tagging)
- Fuzzy matching for folder/document names
- Context stacking (what was I just doing?)
For example, if you type "move" with nothing selected, the system prompts you to select documents first. If you type "move" with documents selected, it prompts for a target folder. If you type "move to Q3", it searches for folders and moves the selected documents there.
Performance Considerations
With 87 handlers and complex intent parsing, you might worry about latency. Here's how we keep it fast:
Handler registry is a simple lookup table, not a graph traversal. O(1) to find the right handler.
Intent parsing is cached. If you type the same command twice, we don't re-parse it.
Handlers are lazy-loaded. The handler for "generate AI summary" doesn't load until you actually use it.
Action execution is async but non-blocking. The UI updates optimistically while the action executes in the background.
The Universal Command typically responds in under 100ms, even on slower connections.
What This Means For Users
You get one command interface that works everywhere. Ctrl+Shift+A opens the same command palette whether you're in a document view, a matrix, or the knowledge graph. You can type naturally—"move this to Q3", "delete these 5 docs", "find contracts from 2024"—and the system figures out what you mean.
You also get consistency. If a feature works in the command palette, it works in the API. If it works in the API, it works in webhooks. This consistency reduces the learning curve and makes AiFiler more predictable.
For power users, it means you can work faster. Keyboard shortcuts, batch operations, and natural language commands all flow through the same system. You're not switching between different mental models for different parts of the app.
For developers integrating with AiFiler, it means the intent system is a stable API. You can build on top of it, knowing that the 87 handlers are well-tested and well-documented.
The Audit That Led Here
This architecture emerged from a larger audit of the codebase. We had multiple command palettes, multiple context menus, multiple ways to do the same thing. The audit (documented in our CLAUDE.md) identified this fragmentation as a source of bugs, inconsistency, and confusion.
By unifying around the Universal Command and the intent handler system, we eliminated that fragmentation. We went from "how do I do X in this part of the app?" to "I just use Ctrl+Shift+A and type what I want."
That's not a small change. It's a fundamental shift in how the product works.
The Universal Command is one of the largest architectural changes we've made to AiFiler. It touches every part of the product—documents, matrices, knowledge graphs, settings, AI features. It's also one of the most invisible changes. Most users just notice that things are simpler and faster.
That's by design. Good architecture should disappear.
Enjoyed this article?
Get more articles like this delivered to your inbox. No spam, unsubscribe anytime.