UI reference
Search (MicroSearch)
Platform-level command palette for searching within apps. Each app declares its searchable types in the manifest; the platform provides the UI shell, keyboard shortcuts, and result rendering.
JS: public/js/microsearch.js
CSS: public/js/microsearch.css
Trigger: Ctrl+K / Cmd+K, or click the navbar search bar
How it works
- User presses
Ctrl+K or clicks the search bar — the command palette opens
- User types a query — MicroSearch debounces (200ms) then hits the active app's search API
- Results render with colored dots matching the searchable type
- User navigates with arrow keys, presses
Enter to open, or Esc to close
Manifest: declaring searchables
Every app declares what it can search in manifest.py:
# apps/tasks/manifest.py
manifest = {
# ...
"searchables": [
{"type": "task", "label": "Tasks", "icon": "assignment", "color": "#F39C12"},
{"type": "board", "label": "Boards", "icon": "view_kanban", "color": "#3B82F6"},
{"type": "schedule", "label": "Schedules", "icon": "schedule", "color": "#8B5CF6"},
{"type": "deliverable", "label": "Deliverables", "icon": "task_alt", "color": "#10B981"},
{"type": "member", "label": "Members", "icon": "group", "color": "#5D9288"},
{"type": "topic", "label": "Topics", "icon": "label", "color": "#E8A838"},
],
}
Searchable fields
| Field |
Type |
Description |
type |
str |
Machine key — used in API types filter and result payloads. |
label |
str |
Human-readable label shown in the legend. |
icon |
str |
Material Symbols icon name (for future use). |
color |
str |
Hex color for the dot in legend and results. |
Search API contract
Each app must expose a search endpoint:
GET /api/{app}/search?q={query}&types={comma-separated}
Request
| Param |
Type |
Description |
q |
str |
Search query (required). |
types |
str |
Comma-separated type filter (optional). Empty = search all types. |
Response
{
"results": [
{
"type": "task",
"id": "PL-1",
"title": "Write launch blog post",
"subtitle": "Product Launch · To Do",
"url": "/board/board-1?task=PL-1"
}
]
}
Result fields
| Field |
Type |
Description |
type |
str |
Must match a type from the manifest's searchables. |
id |
str |
Short identifier shown as a badge before the title. Can be empty. |
title |
str |
Primary text. |
subtitle |
str |
Secondary text (context, status, parent). |
url |
str |
Navigation URL when the result is selected. |
Initializing MicroSearch
MicroSearch is loaded globally via base.html. Each app initializes it with its own config:
<script>
document.addEventListener('DOMContentLoaded', function() {
MicroSearch.init({
app: 'tasks',
apiBase: 'http://api.local.localhost:8000',
searchables: [
{ type: 'task', label: 'Tasks', icon: 'assignment', color: '#F39C12' },
{ type: 'board', label: 'Boards', icon: 'view_kanban', color: '#3B82F6' },
// ...
],
});
});
</script>
MicroSearch.init(opts)
| Option |
Type |
Description |
app |
str |
App slug — used to construct the API URL. |
apiBase |
str |
Base URL of the API subdomain. |
searchables |
array |
Same shape as the manifest's searchables. Defines the legend and color mapping. |
Other methods
| Method |
Description |
MicroSearch.open() |
Programmatically open the palette. |
MicroSearch.close() |
Programmatically close the palette. |
UI anatomy
┌─────────────────────────────────────────────────┐
│ 🔍 Search Tasks... Esc │ ← input row
├─────────────────────────────────────────────────┤
│ ● Tasks ● Boards ● Schedules ● Members ... │ ← legend (clickable filters)
├─────────────────────────────────────────────────┤
│ ● PL-1 Write launch blog post │ ← result (colored dot + id + title)
│ Product Launch · To Do │ ← subtitle
│ ● PL-5 API rate limiting │
│ Product Launch · In Progress │
│ ◆ PL Product Launch │ ← board result (different color)
│ 9 tasks · 2 done │
├─────────────────────────────────────────────────┤
│ ↑↓ navigate Enter open Esc close │ ← footer hints
└─────────────────────────────────────────────────┘
Legend filtering
Clicking a legend chip toggles that type on/off. When all chips are active (default), all types are searched. Deactivating a chip excludes that type from the API request via the types parameter.
CSS classes
| Class |
Element |
Purpose |
.ms-overlay |
div |
Fixed full-screen backdrop with blur. Click outside palette to close. |
.ms-palette |
div |
The command palette container. Max-width 620px, centered. |
.ms-input-row |
div |
Search input row with icon and Esc badge. |
.ms-input |
input |
The search text input. |
.ms-kbd |
kbd |
Keyboard shortcut badge. |
.ms-legend |
div |
Row of filterable type chips. |
.ms-legend-chip |
button |
Individual type filter. .active when included. |
.ms-dot |
span |
8px colored circle. Used in legend chips and results. |
.ms-results |
div |
Scrollable results container (hidden scrollbar). |
.ms-result |
a |
Single result row. |
.ms-result-selected |
.ms-result |
Keyboard-selected result (highlighted). |
.ms-result-id |
span |
ID badge before the title. |
.ms-result-subtitle |
div |
Secondary line of text. |
.ms-empty |
div |
Empty/loading state message. |
.ms-footer |
div |
Keyboard hint bar at the bottom. |
html.ms-open |
html |
Added when palette is open. Prevents body scroll. |
Implementing search for a new app
1. Add searchables to your manifest
# apps/papers/manifest.py
"searchables": [
{"type": "document", "label": "Documents", "icon": "description", "color": "#3B82F6"},
{"type": "folder", "label": "Folders", "icon": "folder", "color": "#F59E0B"},
{"type": "template", "label": "Templates", "icon": "draft", "color": "#8B5CF6"},
]
2. Add a search API route
# apps/papers/plugin.py
api.GET("/papers/search", "apps.papers.apis.api_search")
3. Implement the handler
async def api_search(req: Request, res: Response, ctx: Context):
q = (req.queries.get("q") or "").strip().lower()
types = [t for t in (req.queries.get("types") or "").split(",") if t]
results = []
if not types or "document" in types:
# search documents...
pass
if not types or "folder" in types:
# search folders...
pass
return res.out(HTTPStatus.OK, {"results": results[:50]})
4. Include the search init partial
{# In your app's templates #}
<script>
document.addEventListener('DOMContentLoaded', function() {
MicroSearch.init({
app: 'papers',
apiBase: 'http://api.local.localhost:8000',
searchables: [
{ type: 'document', label: 'Documents', icon: 'description', color: '#3B82F6' },
{ type: 'folder', label: 'Folders', icon: 'folder', color: '#F59E0B' },
{ type: 'template', label: 'Templates', icon: 'draft', color: '#8B5CF6' },
],
});
});
</script>
Keyboard shortcuts
| Shortcut |
Action |
Ctrl+K / Cmd+K |
Open/close the search palette |
Esc |
Close the palette |
↑ / ↓ |
Navigate results |
Enter |
Open the selected result |