The Problem With Command Fragmentation
You're working in AiFiler. You need to create a document, tag it, share it with a colleague, and trigger an AI analysis—all within the same workflow. Without a unified command system, you'd need to navigate menus, click buttons, and switch contexts. That's friction.
Most document management tools solve this with scattered commands: a menu here, a button there, a modal somewhere else. AiFiler took a different approach. Instead of spreading intent handling across the UI, we built a single routing layer that understands 50+ different user intentions and dispatches them to the right handler.
The result: Universal Command (Ctrl+Shift+A on Windows, Cmd+Shift+A on Mac). One interface. Fifty different capabilities.
Architecture: The Three-Layer System
Universal Command works through three layers:
Layer 1: Intent Recognition
When you open Universal Command and type, the system doesn't just do string matching. It analyzes what you're asking for. "Create a meeting note" and "new meeting doc" both map to the same intent: CREATE_DOCUMENT_MEETING. This is handled by lib/intelligence/universalRouter.ts, which maintains a mapping of natural language patterns to structured intents.
Layer 2: Intent Handlers
Once an intent is identified, it's routed to one of 50+ specialized handlers in lib/intelligence/intentHandlers.ts. Each handler knows how to execute its specific action. The CREATE_DOCUMENT handler knows about templates, defaults, and workspace context. The SHARE_DOCUMENT handler understands permissions and notification preferences. They don't interfere with each other.
Layer 3: Action Execution
The handler passes structured data to lib/intelligence/actionExecutor.ts, which actually performs the action. This separation means handlers can be tested independently, and execution logic can be updated without touching routing.
Here's how the data flows:
User Input ("Create a contract")
↓
Intent Recognition (universalRouter.ts)
↓
Intent Identified: CREATE_DOCUMENT_CONTRACT
↓
Handler Lookup (intentHandlers.ts)
↓
Handler Execution (actionExecutor.ts)
↓
Action Complete (document created, context updated)
The Intent Handler Pattern
Each handler follows a consistent pattern. Here's what a simplified handler looks like conceptually:
const handleCreateDocument = async (intent, context) => {
// 1. Validate context (workspace, permissions)
// 2. Extract parameters from intent
// 3. Apply defaults and templates
// 4. Execute the action
// 5. Update application state
// 6. Return result
}
This consistency is critical. When you add a new intent—say, "Export to PDF"—you're not rewriting the routing system. You're adding one handler that follows the established pattern. The system scales without accumulating complexity.
The contextManager.tsx provides each handler with the information it needs: current workspace, selected documents, user permissions, recent actions, and more. Handlers don't have to figure out context—it's passed in.
Why 50+ Intents?
We didn't arbitrarily pick 50. We analyzed how users actually work:
- Document operations: Create, duplicate, rename, move, delete, archive, restore
- Collaboration: Share, unshare, change permissions, invite, mention
- Organization: Tag, untag, add to collection, remove from collection
- AI features: Summarize, generate, analyze, extract, rewrite
- Navigation: Go to workspace, open recent, search, filter
- Workspace management: Create workspace, switch workspace, invite member
- Settings: Change theme, update profile, manage integrations
- Knowledge graph: Link documents, view relationships, explore connections
Each intent represents a complete user action. Not every possible variation ("create a document" vs. "create a new document"), but every distinct thing a user might want to do.
The Universal Router: Intent Matching
universalRouter.ts is the brain. It takes free-form input and maps it to structured intents. This isn't regex matching—it's semantic understanding.
The router maintains patterns like:
{
intent: 'CREATE_DOCUMENT',
patterns: [
/^(?:new|create|make)\s+(?:a\s+)?document/i,
/^(?:new|create)\s+(?:a\s+)?(?:blank\s+)?doc/i,
],
aliases: ['new doc', 'create doc', 'make document']
}
But it also uses fuzzy matching and context. If you've recently been working with contracts, "create" might default to CREATE_DOCUMENT_CONTRACT instead of the generic CREATE_DOCUMENT. The router learns from your workflow.
This is where the sessionIntelligence.ts module comes in. It tracks what you've been doing in the current session and weights intent matching accordingly. You're more likely to want to "share" a document you just created than one from three weeks ago.
Execution: From Intent to Reality
Once a handler is invoked, it needs to actually do something. That's where actionExecutor.ts comes in.
The executor handles the messy parts:
- Async operations: Creating a document might involve database writes, file creation, and cache updates. The executor orchestrates these.
- Error handling: What if the user doesn't have permission? What if the database is temporarily unavailable? The executor catches these and provides meaningful feedback.
- State updates: After an action completes, the application state needs to reflect the change. The executor updates workspace state, table state, and the knowledge graph.
- Undo/Redo: The executor logs actions so they can be undone. Universal Command actions are first-class citizens in the undo stack.
This is why handlers are thin. They describe what to do. The executor handles how to do it reliably.
Why This Architecture Matters For You
As a user, you never see this complexity. You press Ctrl+Shift+A, type what you want, and it happens. But the architecture underneath enables three things:
1. Speed: You're not navigating menus. You're not switching contexts. You're typing a command and moving on. Keyboard-first workflows are faster.
2. Consistency: Every intent works the same way. Permissions are checked consistently. Errors are handled consistently. State updates are consistent. There's no "this feature works differently than that one."
3. Extensibility: When we add new features, we don't rewrite Universal Command. We add a handler. The system grows without becoming fragile.
The knowledge graph integration (knowledgeGraph.ts) means Universal Command understands relationships. "Link this to the Q3 planning docs" works because the system knows what documents exist and how they relate to each other. "Share with the marketing team" works because the system understands team structure.
The Real Cost: Maintaining Intent Clarity
With 50+ intents, the biggest challenge isn't technical—it's semantic. How do you ensure that "Create a document" and "New doc" both map to the same intent? How do you prevent intent overlap, where two handlers could plausibly handle the same request?
We solved this with a strict intent taxonomy, documented in code comments. Each intent has:
- A clear definition of what it does
- The parameters it requires
- The context it needs
- Patterns that trigger it
- Aliases and variations
When we add a new intent, we check this taxonomy first. Does it already exist under a different name? Can it be handled by an existing handler with a parameter? Or is it genuinely new?
This discipline prevents the system from becoming a tangled mess of overlapping handlers.
What's Next
Right now, Universal Command is deterministic—you type a command, it matches an intent, it executes. We're exploring probabilistic routing: when multiple intents match, the system could ask for clarification or make an educated guess based on your history.
We're also building intent composition. "Create a contract, tag it as Q4, and share it with legal" should be one command, not three. The router would break this into a sequence of intents and execute them atomically.
But the foundation is solid. Fifty intents, one routing system, infinite possibility.
Next time you press Ctrl+Shift+A and type a command, you're not just using a feature. You're using a routing system that understands your intent, dispatches it to a specialized handler, and executes it reliably. That's the difference between a command palette and a truly intelligent interface.
Enjoyed this article?
Get more articles like this delivered to your inbox. No spam, unsubscribe anytime.
