MiniApp UI Schema Design¶
Version: 1.0 Created: November 30, 2025 Updated: December 1, 2025 Status: ✅ Complete (Backend + Frontend)
Implementation Status¶
Backend (Complete)¶
- ✅
MiniAppResponseschema updated withweb_ui_urlfield - ✅
BaseMiniAppHandler.get_ui_config()supports view-specific configs - ✅
BaseMiniAppHandler.get_web_ui_url()helper for URL generation - ✅
GET /miniapps/ui/{session_id}?view=X&item_id=YAPI endpoint - ✅
POST /miniapps/action/{session_id}action handler - ✅ Trip Planner: overview, map, venue, share views
- ✅ Bill Split: overview, settlement views
- ✅ Todo List: overview view
- ✅ Poll: overview, results views
- ✅ Web URLs included in session creation responses
- ✅ WebSocket endpoint
/ws/miniapp/{session_id}for real-time sync
Frontend (Complete - December 2025)¶
- ✅ Created
/app/[appType]/[sessionId]/page.tsxin archety-web - ✅ Built
ConfigRenderer.tsxcomponent router - ✅ Implemented 15+ components (VenueCard, TaskCard, ItemCard, MapView, etc.)
- ✅ Added API client for fetching UI configs
- ✅ Toast notification system - success/error/warning/info toasts
- ✅ Optimistic updates - instant UI feedback with rollback on failure
- ✅ Real Google Maps -
@react-google-maps/apiwith marker clustering - ✅ WebSocket real-time sync - multiplayer collaboration
- ✅ Offline support - action queue with auto-sync
- ✅ OpenGraph meta tags - rich link previews for sharing
Frontend File Structure¶
archety-web/
├── app/app/[appType]/[sessionId]/
│ ├── page.tsx # Main miniapp page
│ └── layout.tsx # OpenGraph metadata
├── components/miniapp/
│ ├── ConfigRenderer.tsx # Component router
│ ├── MiniAppLayout.tsx # App shell with navigation
│ ├── OfflineIndicator.tsx # Offline status banner
│ ├── core/ # Core components
│ │ ├── QuickAdd.tsx
│ │ ├── FilterBar.tsx
│ │ ├── CardList.tsx
│ │ ├── ActionPanel.tsx
│ │ ├── Poll.tsx
│ │ └── ... (12 more)
│ ├── trip/ # Trip Planner components
│ │ ├── VenueCard.tsx
│ │ ├── MapView.tsx
│ │ └── ShareCard.tsx
│ └── split/ # Bill Split components
│ ├── ItemCard.tsx
│ ├── SplitSummary.tsx
│ ├── SettlementList.tsx
│ └── PersonCard.tsx
├── lib/miniapp/
│ ├── optimistic.ts # Optimistic update logic
│ ├── websocket.ts # WebSocket hook
│ └── offline.ts # Offline queue
├── components/ui/
│ └── Toast.tsx # Toast notification system
└── types/miniapp.ts # TypeScript definitions
Decisions Made¶
- Authentication: Public read-only with session token in URL
- Real-time: ✅ WebSocket sync implemented for multiplayer
- Maps: Google Maps with graceful fallback when API key not configured
- Offline: Action queue persisted to localStorage, auto-syncs when online
Overview¶
This document defines the universal UI schema for all MiniApps, enabling dynamic web pages to be generated from JSON configurations. The frontend (archety-web) will be a config-driven renderer that interprets these schemas.
Architecture Decision¶
Recommendation: Hybrid Approach¶
┌─────────────────────────────────────────────────────────────────┐
│ archety-web (Next.js 14) │
│ │
│ Routes: │
│ /app/[appType]/[sessionId]/page.tsx → Main view │
│ /app/[appType]/[sessionId]/[view]/page.tsx → Specific views │
│ │
│ Components: │
│ ConfigRenderer.tsx → Routes JSON to React components │
│ Shared components for all mini-apps │
└─────────────────────────────────────────────────────────────────┘
│
│ GET /miniapps/ui/{session_id}?view=X
▼
┌─────────────────────────────────────────────────────────────────┐
│ archety-backend (FastAPI) │
│ │
│ Returns UIConfig JSON based on: │
│ - Session state (venues, tasks, items, votes) │
│ - Requested view (overview, map, detail, share) │
│ - User context (permissions, votes cast, etc.) │
└─────────────────────────────────────────────────────────────────┘
Why Hybrid:
1. Backend already has get_ui_config() implemented for all handlers
2. Frontend renders based on JSON - no backend changes for UI tweaks
3. Single source of truth (session state) lives in backend
4. Frontend can be cached/CDN'd, backend handles business logic
Universal UI Schema¶
Root Schema¶
interface UIConfig {
// Metadata
app_id: string; // "trip_planner", "bill_split", etc.
session_id: string;
version: string; // Schema version for compatibility
// Display
title: string;
theme: ThemeType;
// Layout
header?: HeaderConfig;
navigation?: NavigationConfig;
components: ComponentConfig[];
footer?: FooterConfig;
// Behavior
actions?: GlobalAction[];
refresh_interval?: number; // Auto-refresh in seconds
offline_capable?: boolean;
// Sharing
share_url?: string;
share_config?: ShareConfig;
}
type ThemeType =
| "travel" // Trip Planner - purple/blue gradient
| "finance" // Bill Split - green accents
| "productivity" // Todo List - clean blue
| "voting" // Poll - purple
| "neutral"; // Generic
Header Config¶
interface HeaderConfig {
title: string;
subtitle?: string;
// Progress indicator (Trip Planner, Todo)
progress?: {
current: number;
total: number;
label?: string; // "3/10 spots visited"
};
// Stats row (Bill Split)
stats?: StatItem[];
// Dates (Trip Planner)
dates?: {
start?: string; // ISO date
end?: string;
};
// Hero image (Trip detail)
image_url?: string;
// Back navigation
back_url?: string;
back_label?: string;
}
interface StatItem {
label: string;
value: string;
icon?: string;
}
Navigation Config¶
interface NavigationConfig {
type: "tabs" | "segmented" | "none";
items: NavItem[];
current: string;
}
interface NavItem {
id: string;
label: string;
icon?: string;
badge?: number; // Notification count
view: string; // View to load when tapped
}
Component Library¶
1. Quick Add (quick_add)¶
Text input with action button for adding items.
interface QuickAddComponent {
type: "quick_add";
id: string;
props: {
placeholder: string;
action: string; // Action to trigger
button_label?: string; // Default: "Add"
disabled?: boolean;
multiline?: boolean; // For longer input
suggestions?: string[]; // Autocomplete suggestions
};
}
Used by: All mini-apps
2. Filter Bar (filter_bar)¶
Horizontal scrolling filter chips.
interface FilterBarComponent {
type: "filter_bar";
id: string;
props: {
sections: FilterSection[];
show_all_option?: boolean; // Default: true
};
}
interface FilterSection {
id: string;
label: string;
options: FilterOption[];
multi_select?: boolean;
}
interface FilterOption {
id: string;
label: string;
icon?: string;
emoji?: string;
count?: number;
color?: string;
}
Used by: Trip Planner (category, district, status)
3. Card List (card_list)¶
Grouped list of cards with sections.
interface CardListComponent {
type: "card_list";
id: string;
props: {
cards: CardItem[];
group_by?: string; // Field to group by
empty_message?: string;
show_group_headers?: boolean;
collapsible_groups?: boolean;
};
}
interface CardItem {
id: string;
type: CardType;
data: VenueCard | TaskCard | ItemCard | PersonCard;
}
type CardType = "venue" | "task" | "item" | "person" | "option";
Used by: All mini-apps
4. Venue Card (venue_card)¶
Rich card for trip destinations.
interface VenueCard {
id: string;
name: string;
category: string;
category_emoji: string;
// Location
district?: string;
address?: string;
coordinates?: {
lat: number;
lng: number;
};
// Details
vibe?: string[]; // ["cozy", "instagrammable"]
must_try?: string[]; // ["xiaolongbao", "dandan noodles"]
best_for?: string[]; // ["brunch", "date night"]
price_level?: 1 | 2 | 3 | 4; // $ to $$$$
// Status
status: "planned" | "visited" | "skipped";
user_rating?: 1 | 2 | 3 | 4 | 5;
user_notes?: string;
// Attribution
added_by_name?: string;
added_at?: string;
source_url?: string;
// Actions available
actions?: CardAction[];
}
interface CardAction {
action: string;
label: string;
icon?: string;
style?: "primary" | "secondary" | "danger";
confirm?: string; // Confirmation message
}
5. Task Card (task_card)¶
Checkbox-based task item.
interface TaskCard {
id: string;
content: string;
status: "pending" | "completed";
priority: "high" | "normal" | "low";
// Assignment
assigned_to?: string;
created_by?: string;
// Timestamps
created_at?: string;
completed_at?: string;
due_date?: string;
// Actions
actions?: CardAction[];
}
6. Item Card (item_card)¶
Bill split expense item.
interface ItemCard {
id: string;
description: string;
amount: number;
// Splitting
shared_by?: string[]; // Person IDs
split_type?: "equal" | "custom" | "full";
custom_splits?: { [personId: string]: number };
// Attribution
paid_by?: string;
added_by?: string;
added_at?: string;
}
7. Person Card (person_card)¶
Participant in bill split.
interface PersonCard {
id: string;
name: string;
avatar_url?: string;
// Balance
amount_paid: number;
amount_owed: number;
balance: number; // Positive = gets back, negative = owes
status: "owes" | "owed" | "settled";
}
8. Poll Component (poll)¶
Voting interface with results.
interface PollComponent {
type: "poll";
id: string;
props: {
question: string;
options: PollOption[];
total_votes: number;
user_vote?: string; // Option ID user voted for
state: "collecting" | "voting" | "closed";
anonymous?: boolean;
show_results?: boolean; // Show results while voting
allow_change_vote?: boolean;
};
}
interface PollOption {
id: string;
text: string;
votes: number;
percentage: number;
voters?: string[]; // If not anonymous
}
9. Split Summary (split_summary)¶
Bill split calculation results.
interface SplitSummaryComponent {
type: "split_summary";
id: string;
props: {
total: number;
per_person: number;
num_people: number;
balances: PersonBalance[];
settlements?: Settlement[]; // Optimized payment suggestions
};
}
interface PersonBalance {
person_id: string;
name: string;
paid: number;
owes: number;
balance: number;
}
interface Settlement {
from: string;
to: string;
amount: number;
}
10. Map View (map_view)¶
Interactive map with venue pins.
interface MapViewComponent {
type: "map_view";
id: string;
props: {
center: {
lat: number;
lng: number;
};
zoom: number;
markers: MapMarker[];
show_route?: boolean;
cluster_markers?: boolean;
};
}
interface MapMarker {
id: string;
lat: number;
lng: number;
label: string;
emoji?: string;
color?: string;
status?: string;
on_click?: {
action: string;
payload: any;
};
}
11. Action Panel (action_panel)¶
Row of action buttons.
interface ActionPanelComponent {
type: "action_panel";
id: string;
props: {
actions: ActionButton[];
layout?: "row" | "stack";
sticky?: boolean; // Stick to bottom
};
}
interface ActionButton {
action: string;
label: string;
icon?: string;
style?: "primary" | "secondary" | "danger" | "outline";
disabled?: boolean;
loading?: boolean;
confirm?: string;
}
12. Empty State (empty_state)¶
Placeholder when no data.
interface EmptyStateComponent {
type: "empty_state";
id: string;
props: {
icon?: string;
emoji?: string;
title: string;
message?: string;
action?: ActionButton;
};
}
13. Stats Grid (stats_grid)¶
Grid of stat cards.
interface StatsGridComponent {
type: "stats_grid";
id: string;
props: {
stats: StatCard[];
columns?: 2 | 3 | 4;
};
}
interface StatCard {
label: string;
value: string | number;
icon?: string;
trend?: "up" | "down" | "neutral";
color?: string;
}
14. Section Header (section_header)¶
Divider with title.
interface SectionHeaderComponent {
type: "section_header";
id: string;
props: {
title: string;
subtitle?: string;
action?: ActionButton; // e.g., "See all"
collapsible?: boolean;
collapsed?: boolean;
};
}
15. Info Banner (info_banner)¶
Informational message.
interface InfoBannerComponent {
type: "info_banner";
id: string;
props: {
type: "info" | "success" | "warning" | "error";
title?: string;
message: string;
dismissible?: boolean;
action?: ActionButton;
};
}
View Configurations by Mini-App¶
Trip Planner Views¶
1. Overview (Default)¶
{
"app_id": "trip_planner",
"title": "Shanghai Trip",
"theme": "travel",
"header": {
"title": "Shanghai",
"subtitle": "Dec 15-22, 2025",
"progress": {
"current": 3,
"total": 10,
"label": "3/10 spots visited"
}
},
"navigation": {
"type": "tabs",
"items": [
{ "id": "overview", "label": "List", "icon": "list", "view": "overview" },
{ "id": "map", "label": "Map", "icon": "map", "view": "map" },
{ "id": "share", "label": "Share", "icon": "share", "view": "share" }
],
"current": "overview"
},
"components": [
{
"type": "quick_add",
"id": "add_venue",
"props": {
"placeholder": "Add a spot (e.g., Din Tai Fung, Jing'an)",
"action": "add_venue"
}
},
{
"type": "filter_bar",
"id": "filters",
"props": {
"sections": [
{
"id": "category",
"label": "Category",
"options": [
{ "id": "restaurant", "label": "Restaurant", "emoji": "🍴", "count": 5 },
{ "id": "bar", "label": "Bar", "emoji": "🍸", "count": 2 },
{ "id": "cafe", "label": "Cafe", "emoji": "☕", "count": 3 }
]
},
{
"id": "district",
"label": "District",
"options": [
{ "id": "jingan", "label": "Jing'an", "count": 4 },
{ "id": "french-concession", "label": "French Concession", "count": 3 }
]
},
{
"id": "status",
"label": "Status",
"options": [
{ "id": "planned", "label": "Planned", "count": 7 },
{ "id": "visited", "label": "Visited", "count": 3 }
]
}
]
}
},
{
"type": "card_list",
"id": "venues",
"props": {
"cards": [],
"group_by": "district",
"show_group_headers": true,
"empty_message": "No venues yet! Add spots by typing above."
}
}
]
}
2. Map View¶
{
"app_id": "trip_planner",
"title": "Shanghai Trip - Map",
"theme": "travel",
"header": {
"title": "Shanghai",
"subtitle": "10 spots"
},
"navigation": {
"type": "tabs",
"items": [
{ "id": "overview", "label": "List", "view": "overview" },
{ "id": "map", "label": "Map", "view": "map" }
],
"current": "map"
},
"components": [
{
"type": "map_view",
"id": "trip_map",
"props": {
"center": { "lat": 31.2304, "lng": 121.4737 },
"zoom": 12,
"markers": [
{
"id": "venue_1",
"lat": 31.2244,
"lng": 121.4555,
"label": "Din Tai Fung",
"emoji": "🍴",
"status": "planned"
}
],
"cluster_markers": true
}
},
{
"type": "card_list",
"id": "venue_preview",
"props": {
"cards": [],
"empty_message": "Tap a pin to see details"
}
}
]
}
3. Venue Detail View¶
{
"app_id": "trip_planner",
"title": "Din Tai Fung",
"theme": "travel",
"header": {
"title": "Din Tai Fung",
"subtitle": "Jing'an • Restaurant",
"image_url": "https://...",
"back_url": "/app/trip/abc123",
"back_label": "Back to trip"
},
"components": [
{
"type": "stats_grid",
"id": "venue_stats",
"props": {
"stats": [
{ "label": "Category", "value": "🍴 Restaurant" },
{ "label": "District", "value": "Jing'an" },
{ "label": "Price", "value": "$$" },
{ "label": "Status", "value": "Planned" }
]
}
},
{
"type": "section_header",
"id": "vibe_header",
"props": { "title": "Vibe" }
},
{
"type": "tag_list",
"id": "vibe_tags",
"props": {
"tags": ["cozy", "instagrammable", "date-night"]
}
},
{
"type": "section_header",
"id": "must_try_header",
"props": { "title": "Must Try" }
},
{
"type": "bullet_list",
"id": "must_try",
"props": {
"items": ["Xiaolongbao", "Dandan Noodles", "Shrimp Fried Rice"]
}
},
{
"type": "section_header",
"id": "notes_header",
"props": { "title": "Notes" }
},
{
"type": "text_block",
"id": "notes",
"props": {
"text": "Make reservations 2 days ahead. Best to go for lunch.",
"editable": true,
"action": "update_notes"
}
},
{
"type": "action_panel",
"id": "actions",
"props": {
"actions": [
{ "action": "mark_visited", "label": "Mark Visited", "style": "primary", "icon": "check" },
{ "action": "get_directions", "label": "Directions", "style": "secondary", "icon": "navigation" },
{ "action": "delete_venue", "label": "Remove", "style": "danger", "icon": "trash", "confirm": "Remove this venue?" }
],
"sticky": true
}
}
]
}
4. Share View¶
{
"app_id": "trip_planner",
"title": "Share Trip",
"theme": "travel",
"header": {
"title": "Share Your Trip",
"subtitle": "Shanghai • 10 spots"
},
"components": [
{
"type": "info_banner",
"id": "share_info",
"props": {
"type": "info",
"message": "Anyone with this link can view your trip (read-only)"
}
},
{
"type": "share_card",
"id": "share_link",
"props": {
"url": "https://app.archety.ai/trip/abc123/share",
"title": "Shanghai Trip",
"description": "10 spots to visit",
"show_qr": true,
"copy_action": "copy_share_link"
}
},
{
"type": "action_panel",
"id": "share_actions",
"props": {
"actions": [
{ "action": "copy_link", "label": "Copy Link", "icon": "copy" },
{ "action": "share_native", "label": "Share", "icon": "share" },
{ "action": "export_pdf", "label": "Export PDF", "icon": "download" }
]
}
}
]
}
Bill Split Views¶
1. Overview (Default)¶
{
"app_id": "bill_split",
"title": "Dinner Split",
"theme": "finance",
"header": {
"title": "Dinner at Izakaya",
"stats": [
{ "label": "Total", "value": "$156.00" },
{ "label": "People", "value": "4" },
{ "label": "Per Person", "value": "$39.00" }
]
},
"components": [
{
"type": "quick_add",
"id": "add_item",
"props": {
"placeholder": "Add item (e.g., Pizza $25)",
"action": "add_item"
}
},
{
"type": "section_header",
"id": "items_header",
"props": {
"title": "Items",
"subtitle": "5 items"
}
},
{
"type": "card_list",
"id": "items",
"props": {
"cards": [
{
"id": "item_1",
"type": "item",
"data": {
"id": "item_1",
"description": "Edamame",
"amount": 8.00,
"shared_by": ["alice", "bob", "charlie", "diana"]
}
}
],
"empty_message": "No items yet"
}
},
{
"type": "section_header",
"id": "people_header",
"props": {
"title": "People",
"action": { "action": "add_person", "label": "+ Add", "style": "outline" }
}
},
{
"type": "split_summary",
"id": "split",
"props": {
"total": 156.00,
"per_person": 39.00,
"num_people": 4,
"balances": [
{ "person_id": "alice", "name": "Alice", "paid": 156.00, "owes": 39.00, "balance": 117.00 },
{ "person_id": "bob", "name": "Bob", "paid": 0, "owes": 39.00, "balance": -39.00 },
{ "person_id": "charlie", "name": "Charlie", "paid": 0, "owes": 39.00, "balance": -39.00 },
{ "person_id": "diana", "name": "Diana", "paid": 0, "owes": 39.00, "balance": -39.00 }
],
"settlements": [
{ "from": "Bob", "to": "Alice", "amount": 39.00 },
{ "from": "Charlie", "to": "Alice", "amount": 39.00 },
{ "from": "Diana", "to": "Alice", "amount": 39.00 }
]
}
},
{
"type": "action_panel",
"id": "actions",
"props": {
"actions": [
{ "action": "calculate", "label": "Recalculate", "icon": "calculator" },
{ "action": "share_split", "label": "Share Split", "icon": "share", "style": "primary" }
],
"sticky": true
}
}
]
}
2. Settlement View¶
{
"app_id": "bill_split",
"title": "Settlement",
"theme": "finance",
"header": {
"title": "Who Pays Who",
"subtitle": "$156.00 total"
},
"components": [
{
"type": "settlement_list",
"id": "settlements",
"props": {
"settlements": [
{
"from": { "id": "bob", "name": "Bob" },
"to": { "id": "alice", "name": "Alice" },
"amount": 39.00,
"status": "pending"
}
],
"allow_mark_paid": true
}
},
{
"type": "action_panel",
"id": "actions",
"props": {
"actions": [
{ "action": "send_reminders", "label": "Send Reminders", "icon": "bell" },
{ "action": "mark_all_settled", "label": "All Settled", "style": "primary" }
]
}
}
]
}
Todo List Views¶
1. Overview (Default)¶
{
"app_id": "todo_list",
"title": "Project Tasks",
"theme": "productivity",
"header": {
"title": "Project Tasks",
"progress": {
"current": 5,
"total": 12,
"label": "5/12 completed"
}
},
"components": [
{
"type": "quick_add",
"id": "add_task",
"props": {
"placeholder": "Add a task (! for high priority)",
"action": "add_task"
}
},
{
"type": "filter_bar",
"id": "filters",
"props": {
"sections": [
{
"id": "status",
"label": "Status",
"options": [
{ "id": "pending", "label": "To Do", "count": 7 },
{ "id": "completed", "label": "Done", "count": 5 }
]
},
{
"id": "priority",
"label": "Priority",
"options": [
{ "id": "high", "label": "High", "color": "red", "count": 2 },
{ "id": "normal", "label": "Normal", "count": 8 },
{ "id": "low", "label": "Low", "color": "blue", "count": 2 }
]
}
]
}
},
{
"type": "section_header",
"id": "pending_header",
"props": {
"title": "To Do",
"subtitle": "7 tasks"
}
},
{
"type": "card_list",
"id": "pending_tasks",
"props": {
"cards": [],
"empty_message": "All done! 🎉"
}
},
{
"type": "section_header",
"id": "completed_header",
"props": {
"title": "Completed",
"subtitle": "5 tasks",
"collapsible": true,
"collapsed": true
}
},
{
"type": "card_list",
"id": "completed_tasks",
"props": {
"cards": []
}
}
]
}
Poll Views¶
1. Voting View (Default)¶
{
"app_id": "poll",
"title": "Team Vote",
"theme": "voting",
"header": {
"title": "Where should we eat?",
"subtitle": "8 votes • Voting open"
},
"components": [
{
"type": "poll",
"id": "poll_options",
"props": {
"question": "Where should we eat?",
"options": [
{ "id": "opt1", "text": "Italian", "votes": 3, "percentage": 37.5 },
{ "id": "opt2", "text": "Japanese", "votes": 4, "percentage": 50 },
{ "id": "opt3", "text": "Mexican", "votes": 1, "percentage": 12.5 }
],
"total_votes": 8,
"user_vote": "opt2",
"state": "voting",
"show_results": true
}
},
{
"type": "quick_add",
"id": "add_option",
"props": {
"placeholder": "Suggest another option...",
"action": "add_option",
"disabled": false
}
},
{
"type": "action_panel",
"id": "actions",
"props": {
"actions": [
{ "action": "close_poll", "label": "Close Voting", "style": "primary" },
{ "action": "share_poll", "label": "Share Poll", "icon": "share" }
]
}
}
]
}
2. Results View¶
{
"app_id": "poll",
"title": "Poll Results",
"theme": "voting",
"header": {
"title": "Where should we eat?",
"subtitle": "Poll closed • 8 votes"
},
"components": [
{
"type": "info_banner",
"id": "winner_banner",
"props": {
"type": "success",
"title": "Winner: Japanese",
"message": "4 votes (50%)"
}
},
{
"type": "poll",
"id": "poll_results",
"props": {
"question": "Where should we eat?",
"options": [
{ "id": "opt2", "text": "Japanese", "votes": 4, "percentage": 50, "winner": true },
{ "id": "opt1", "text": "Italian", "votes": 3, "percentage": 37.5 },
{ "id": "opt3", "text": "Mexican", "votes": 1, "percentage": 12.5 }
],
"total_votes": 8,
"state": "closed",
"show_voters": true
}
}
]
}
URL Structure¶
Frontend Routes (archety-web)¶
/app/trip/[sessionId] → Trip overview
/app/trip/[sessionId]/map → Map view
/app/trip/[sessionId]/venue/[id] → Venue detail
/app/trip/[sessionId]/share → Shareable view
/app/split/[sessionId] → Bill split overview
/app/split/[sessionId]/settle → Settlement view
/app/todo/[sessionId] → Todo list
/app/poll/[sessionId] → Poll voting
/app/poll/[sessionId]/results → Poll results
Backend API¶
GET /miniapps/ui/{session_id} → Default view config
GET /miniapps/ui/{session_id}?view=map → Specific view
GET /miniapps/ui/{session_id}?view=venue&id=xyz → Item detail view
POST /miniapps/action/{session_id} → Handle actions
Actions Reference¶
Trip Planner Actions¶
| Action | Payload | Description |
|---|---|---|
add_venue |
{ raw_input: string } |
Add venue from text |
mark_visited |
{ venue_id: string, rating?: number, notes?: string } |
Mark as visited |
update_venue |
{ venue_id: string, updates: object } |
Update venue details |
delete_venue |
{ venue_id: string } |
Remove venue |
set_filter |
{ filter_type: string, value: string } |
Apply filter |
copy_share_link |
{} |
Copy shareable link |
Bill Split Actions¶
| Action | Payload | Description |
|---|---|---|
add_item |
{ raw_input: string } |
Add expense item |
add_person |
{ name: string } |
Add participant |
record_payment |
{ person_id: string, amount: number } |
Record payment |
calculate |
{} |
Recalculate splits |
mark_settled |
{ from: string, to: string } |
Mark settlement done |
Todo List Actions¶
| Action | Payload | Description |
|---|---|---|
add_task |
{ raw_input: string } |
Add task |
complete_task |
{ task_id: string } |
Mark complete |
delete_task |
{ task_id: string } |
Delete task |
set_priority |
{ task_id: string, priority: string } |
Change priority |
assign_task |
{ task_id: string, assignee: string } |
Assign to person |
Poll Actions¶
| Action | Payload | Description |
|---|---|---|
add_option |
{ raw_input: string } |
Add poll option |
vote |
{ option_id: string } |
Cast vote |
start_voting |
{} |
Open voting |
close_poll |
{} |
Close and show results |
Implementation Phases¶
Phase 1: Core Infrastructure ✅ COMPLETE¶
- Create
/app/[appType]/[sessionId]/page.tsxin archety-web - Build
ConfigRenderer.tsxcomponent router - Implement base components: Header, QuickAdd, ActionPanel
- Add API client for fetching UI configs
Phase 2: Trip Planner UI ✅ COMPLETE¶
- VenueCard component
- FilterBar component
- CardList with grouping
- Map view with Google Maps
Phase 3: Other Mini-Apps ✅ COMPLETE¶
- TaskCard component
- Poll component
- SplitSummary component
- SettlementList component
Phase 4: Polish & Sharing ✅ COMPLETE¶
- Share view with QR codes
- PDF export (future enhancement)
- Deep linking from chat
- Offline support with action queue
Open Questions (Resolved)¶
- Authentication: How do we handle auth for web views?
- ✅ Decision: Public read-only with session token in URL
- Session ID in URL acts as capability token
- No login required to view
-
Future: Add write authentication if needed
-
Real-time Updates: Should we use WebSockets for live updates?
- ✅ Decision: Not initially - refreshes are acceptable
- Users can refresh to see updates
- Simpler implementation, faster to ship
-
Can add WebSockets later if needed
-
Offline Mode: Which mini-apps need offline support?
- ✅ Implemented: Action queue with auto-sync
- All mini-apps queue actions when offline
- Auto-sync when connection restored
-
Persisted to localStorage via Zustand
-
Map Provider: Mapbox vs Google Maps?
- ✅ Decision: Google Maps
- Implemented with
@react-google-maps/api - Graceful fallback when API key not configured
- Marker clustering for dense venues