The Problem: Command Sprawl
You're working in AiFiler. You need to search for a document. You press Ctrl+Shift+A. A moment later, you need to generate a summary. Same shortcut. Then you want to batch-tag five documents. Same shortcut again.
Most tools would break here. Either they'd force you to memorize different shortcuts for each action, or they'd build a monolithic command that tries to do everything and ends up doing nothing well. We chose a third path: a universal command router that understands intent, not just keystrokes.
This is the story of how we built it—and why the architecture matters for how you work.
The Architecture: Intent-First Routing
When you press Ctrl+Shift+A, you're not triggering a specific function. You're opening a command palette that listens to what you type and routes your request to one of 50+ intent handlers. The data flow looks like this:
User Input → Natural Language Parser → Intent Classifier → Context Manager → Action Executor → Result
This isn't just a search box with autocomplete. Each component does real work.
The Universal Router
The core lives in lib/intelligence/universalRouter.ts. When you open the command palette, it doesn't wait for you to finish typing. It's already running your input through a lightweight intent classifier that scores your request against known patterns.
Type "summarize this" and the router knows you want the summarizeDocument intent. Type "tag these 5 docs" and it knows you want batchTag. Type "find contracts from Q3" and it routes to advancedSearch.
The trick is that the router doesn't rely on exact keyword matching. It uses a combination of:
- Keyword signals — presence of action words like "summarize," "batch," "find"
- Context signals — what you have selected, what workspace you're in, what documents are visible
- Recency signals — what you did last (if you just searched, "next" might mean "refine search" not "create document")
- Confidence scoring — if the top intent scores below a threshold, show you multiple options instead of guessing
The Intent Handler System
We have 50+ intent handlers in lib/intelligence/intentHandlers.ts. Each one knows how to execute a specific user goal. Here's what a few look like:
- searchDocuments — full-text search with filters
- summarizeDocument — AI summary of selected doc
- batchTag — apply tags to multiple documents at once
- generateFromTemplate — create new document from template
- shareWithTeam — permission management
- scheduleMatrix — set up recurring batch operations
- exportDocuments — download as PDF/CSV
- askKnowledge — query your knowledge base with AI
Each handler is a function that takes context (what's selected, what workspace you're in, user permissions) and parameters (what the user typed), validates both, and executes.
The validation is strict. If you don't have Pro+ and you try to run a matrix, the handler rejects it before it touches the database. If you try to batch-tag 500 documents and you're on the free plan, it caps you at 50. This is plan-gating, and it happens at the handler level, not the UI level.
// Simplified handler structure
const batchTag = async (context, params) => {
// 1. Validate permissions
if (!context.user.canBatchTag) throw new Error("Feature not available");
// 2. Validate plan limits
if (params.documentIds.length > PLAN_LIMITS[context.plan].batchSize) {
params.documentIds = params.documentIds.slice(0, PLAN_LIMITS[context.plan].batchSize);
}
// 3. Execute
const result = await supabase.rpc('batch_tag_documents', {
doc_ids: params.documentIds,
tags: params.tags,
user_id: context.user.id
});
// 4. Log for analytics
await logAction('batch_tag', { count: params.documentIds.length });
return result;
};
The Context Manager
Here's where it gets interesting. The router doesn't just pass raw user input to handlers. It builds a context object that includes everything a handler might need.
The context manager (lib/intelligence/contextManager.tsx) is a React context that tracks:
- Selected documents — what's highlighted right now
- Active workspace — which workspace you're in
- User permissions — what you can and can't do
- Plan tier — free, Pro, Pro+, Teams
- Recent actions — what you did in the last 5 minutes
- Current filters — if you're in a filtered view, handlers know about it
- AI model preference — Claude, GPT-4, etc.
When you type "summarize," the handler doesn't ask "which document?" It looks at the context. If you have one document selected, it summarizes that. If you have five selected, it asks if you want individual summaries or a combined summary. If you have none selected, it tells you to select a document first.
This context is the difference between a command palette that feels smart and one that feels dumb.
The Action Executor: From Intent to Outcome
Once the router has classified your intent and the handler has validated it, the action executor (lib/intelligence/actionExecutor.ts) takes over. This is where the actual work happens.
The executor handles:
- Sequencing — if you ask to "tag and share these documents," it runs both operations in the right order
- Error recovery — if tagging fails on document 3 of 10, it doesn't crash; it logs the failure and continues
- Rate limiting — prevents you from hammering the API (checked in
lib/rateLimit.ts) - Audit logging — every action is logged with who did it, when, and what changed
The executor also manages side effects. When you batch-tag documents, it doesn't just update the database. It:
- Updates the local UI immediately (optimistic update)
- Syncs the change to Supabase
- Invalidates the relevant SWR cache so other components re-fetch
- Logs the action for analytics
- Triggers any downstream workflows (e.g., if you tagged something "urgent," maybe it notifies your team)
Why This Matters for You
The Universal Command isn't just a convenience. It's a statement about how we think about your workflow.
First, it's discoverable. You don't need to memorize 50 shortcuts. You press Ctrl+Shift+A and start typing what you want to do. If you type something the router doesn't recognize, it shows you the closest matches. Over time, you learn the patterns.
Second, it's contextual. The command palette isn't the same in every part of AiFiler. If you're viewing a single document, the palette offers document-specific actions. If you're in the table view with multiple documents selected, it offers batch actions. If you're in settings, it offers configuration commands. The same interface, but the available intents change based on context.
Third, it's safe. Every intent handler validates permissions and plan limits before executing. You can't accidentally exceed your batch operation quota. You can't access documents you don't have permission to see. The system fails safely and tells you why.
Fourth, it's extensible. When we add a new feature to AiFiler, we don't rebuild the command palette. We add a new intent handler. The router automatically discovers it. This is how we've gone from 20 intents to 50+ without the system becoming fragile.
The Implementation Details That Matter
Intent Classification
We don't use a separate ML model for intent classification. That would be overkill and slow. Instead, we use a scoring system that weighs multiple signals:
- Keyword presence (highest weight)
- Semantic similarity to known intent descriptions (using embeddings cached in memory)
- Context signals (what you have selected, where you are)
- Historical patterns (what you usually do after the current action)
This runs in milliseconds. By the time you've finished typing "summarize," the router has already scored the top 5 intents and is ready to execute the top match.
Error Handling
When something goes wrong, the handler doesn't just throw an error. It returns a structured response:
{
success: false,
intent: "batchTag",
error: "PLAN_LIMIT_EXCEEDED",
message: "Your plan allows 50 batch operations per day. You've used 50.",
suggestion: "Upgrade to Pro+ for unlimited batch operations",
retryable: false
}
The UI reads this and shows you a helpful message instead of a generic error. If the error is retryable (like a network timeout), the UI offers a retry button.
Offline Handling
The command palette works offline. If you're on a plane and press Ctrl+Shift+A, it still opens. You can search your local cache, create new documents, or organize your workspace. When you're back online, changes sync automatically.
This works because the context manager uses SWR with localStorage prefixing (as documented in our architecture). Your recent documents, tags, and workspaces are cached locally. The router can work with stale data and reconcile when the network returns.
Lessons Learned
We've iterated on this system three times. Here's what we learned:
Intent overlap is real. "Find contracts" could mean search, or it could mean "show me the contracts dashboard." We solved this by adding a confidence threshold. If the top intent scores only 60% confident, we show you multiple options instead of guessing.
Context is fragile. If the context manager gets out of sync with the actual database state, handlers make bad decisions. We fixed this by validating context at handler execution time, not just at classification time.
Logging is critical. When a handler fails, we log the full context, the input, and the error. This has been invaluable for debugging and understanding how users actually use the command palette.
Plan limits need to be generous for the first use. We learned this the hard way. When we first implemented plan-gating on batch operations, free users could only batch-tag 5 documents. We got complaints. We raised it to 50. Complaints stopped. The limit isn't about preventing abuse; it's about preventing accidental overuse.
What's Next
We're working on command chaining. Right now, each command is independent. In the next version, you'll be able to type something like "find contracts from Q3, summarize them, and tag as reviewed." The router will break this into three intents, execute them in sequence, and pass the output of each to the next.
We're also building custom intents. Teams will be able to define their own commands. "Prepare weekly report" could be a custom intent that searches for weekly documents, generates a summary, and exports to PDF—all in one command.
The Universal Command is one of the core reasons AiFiler feels different. It's not that we have more features than other tools. It's that we've made those features discoverable and accessible through a single, intelligent interface. That's what happens when you design for intent instead of just features.
Press Ctrl+Shift+A and try it. You might be surprised by what you can do.
Enjoyed this article?
Get more articles like this delivered to your inbox. No spam, unsubscribe anytime.



