fix: update local greetings and stabilize account navigation
This commit is contained in:
+21
-91
@@ -19,9 +19,7 @@
|
|||||||
"react-perf",
|
"react-perf",
|
||||||
"jsx-a11y"
|
"jsx-a11y"
|
||||||
],
|
],
|
||||||
"jsPlugins": [
|
"jsPlugins": ["@shadcn/lint"],
|
||||||
"@shadcn/lint"
|
|
||||||
],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "error",
|
"no-console": "error",
|
||||||
"no-unused-vars": [
|
"no-unused-vars": [
|
||||||
@@ -50,9 +48,7 @@
|
|||||||
"shadcn/no-arbitrary-values": [
|
"shadcn/no-arbitrary-values": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"allow": [
|
"allow": ["layout"]
|
||||||
"layout"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"shadcn/no-inline-styles": "error",
|
"shadcn/no-inline-styles": "error",
|
||||||
@@ -60,114 +56,59 @@
|
|||||||
"shadcn/no-restyle": [
|
"shadcn/no-restyle": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"allow": [
|
"allow": ["layout"],
|
||||||
"layout"
|
|
||||||
],
|
|
||||||
"contracts": [
|
"contracts": [
|
||||||
{
|
{
|
||||||
"pattern": "^Sidebar$",
|
"pattern": "^Sidebar$",
|
||||||
"allow": [
|
"allow": ["layout", "border-r", "border-r-0", "border-border", "transition-none"]
|
||||||
"layout",
|
|
||||||
"border-r",
|
|
||||||
"border-r-0",
|
|
||||||
"border-border",
|
|
||||||
"transition-none"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^SidebarHeader$",
|
"pattern": "^SidebarHeader$",
|
||||||
"allow": [
|
"allow": ["layout", "py-4", "gap-4", "border-b", "p-5"]
|
||||||
"layout",
|
|
||||||
"py-4",
|
|
||||||
"gap-4",
|
|
||||||
"border-b",
|
|
||||||
"p-5"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^SidebarGroup$",
|
"pattern": "^SidebarGroup$",
|
||||||
"allow": [
|
"allow": ["layout", "px-3"]
|
||||||
"layout",
|
|
||||||
"px-3"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^SidebarFooter$",
|
"pattern": "^SidebarFooter$",
|
||||||
"allow": [
|
"allow": ["layout", "px-3", "pb-4"]
|
||||||
"layout",
|
|
||||||
"px-3",
|
|
||||||
"pb-4"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^SidebarMenuButton$",
|
"pattern": "^SidebarMenuButton$",
|
||||||
"allow": [
|
"allow": ["layout", "p-0"]
|
||||||
"layout",
|
|
||||||
"p-0"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^Label$",
|
"pattern": "^Label$",
|
||||||
"allow": [
|
"allow": ["layout", "gap-2", "text-sm", "text-xs", "text-muted-foreground"]
|
||||||
"layout",
|
|
||||||
"gap-2",
|
|
||||||
"text-sm",
|
|
||||||
"text-xs",
|
|
||||||
"text-muted-foreground"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^MessageScrollerContent$",
|
"pattern": "^MessageScrollerContent$",
|
||||||
"allow": [
|
"allow": ["layout", "gap-5", "p-4"]
|
||||||
"layout",
|
|
||||||
"gap-5",
|
|
||||||
"p-4"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^(Marker|Collapsible)$",
|
"pattern": "^(Marker|Collapsible)$",
|
||||||
"allow": [
|
"allow": ["layout", "text-research-label", "text-muted-foreground"]
|
||||||
"layout",
|
|
||||||
"text-research-label",
|
|
||||||
"text-muted-foreground"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^SidebarProvider$",
|
"pattern": "^SidebarProvider$",
|
||||||
"allow": [
|
"allow": ["layout", "bg-vital-bg", "text-vital-text", "font-arial"]
|
||||||
"layout",
|
|
||||||
"bg-vital-bg",
|
|
||||||
"text-vital-text",
|
|
||||||
"font-arial"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^Button$",
|
"pattern": "^Button$",
|
||||||
"allow": [
|
"allow": ["layout", "px-2"]
|
||||||
"layout",
|
|
||||||
"px-2"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^Input$",
|
"pattern": "^Input$",
|
||||||
"allow": [
|
"allow": ["layout", "pl-9"]
|
||||||
"layout",
|
|
||||||
"pl-9"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^SidebarContent$",
|
"pattern": "^SidebarContent$",
|
||||||
"allow": [
|
"allow": ["layout", "gap-0"]
|
||||||
"layout",
|
|
||||||
"gap-0"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^Badge$",
|
"pattern": "^Badge$",
|
||||||
"allow": [
|
"allow": ["layout", "gap-1"]
|
||||||
"layout",
|
|
||||||
"gap-1"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -177,17 +118,13 @@
|
|||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": [
|
"files": ["scripts/**"],
|
||||||
"scripts/**"
|
|
||||||
],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "off"
|
"no-console": "off"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": [
|
"files": ["src/components/ui/**"],
|
||||||
"src/components/ui/**"
|
|
||||||
],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"shadcn/no-arbitrary-values": "off",
|
"shadcn/no-arbitrary-values": "off",
|
||||||
"shadcn/no-restyle": "off",
|
"shadcn/no-restyle": "off",
|
||||||
@@ -195,13 +132,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": [
|
"files": ["src/**/*.{test,spec}.{ts,tsx}", "tests/**/*.{ts,tsx}"],
|
||||||
"src/**/*.{test,spec}.{ts,tsx}",
|
"plugins": ["vitest"],
|
||||||
"tests/**/*.{ts,tsx}"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"vitest"
|
|
||||||
],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"typescript/unbound-method": "error",
|
"typescript/unbound-method": "error",
|
||||||
"vitest/expect-expect": "error",
|
"vitest/expect-expect": "error",
|
||||||
@@ -211,9 +143,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": [
|
"files": ["e2e/**/*.{ts,tsx}"],
|
||||||
"e2e/**/*.{ts,tsx}"
|
|
||||||
],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"react/rules-of-hooks": "off"
|
"react/rules-of-hooks": "off"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ SQLite records across Home, Messages, and MCP. User edits and decisions survive
|
|||||||
agent updates and reloads. Routines and Journal remain session-only prototypes;
|
agent updates and reloads. Routines and Journal remain session-only prototypes;
|
||||||
Beeper history synchronization and actual message sending remain unconnected.
|
Beeper history synchronization and actual message sending remain unconnected.
|
||||||
|
|
||||||
|
Home's greeting follows the device's local time: morning from 05:00, afternoon
|
||||||
|
from 12:00, and evening from 18:00 until 05:00. It refreshes while the page is
|
||||||
|
open and when returning to the app.
|
||||||
|
|
||||||
## Workspaces and current status
|
## Workspaces and current status
|
||||||
|
|
||||||
| Workspace | Route | Current implementation |
|
| Workspace | Route | Current implementation |
|
||||||
|
|||||||
@@ -151,12 +151,8 @@
|
|||||||
"name": "auth_sessions_owner_id_workspace_owner_id_fk",
|
"name": "auth_sessions_owner_id_workspace_owner_id_fk",
|
||||||
"tableFrom": "auth_sessions",
|
"tableFrom": "auth_sessions",
|
||||||
"tableTo": "workspace_owner",
|
"tableTo": "workspace_owner",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["owner_id"],
|
||||||
"owner_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
@@ -239,12 +235,8 @@
|
|||||||
"name": "connection_credentials_connection_id_connections_id_fk",
|
"name": "connection_credentials_connection_id_connections_id_fk",
|
||||||
"tableFrom": "connection_credentials",
|
"tableFrom": "connection_credentials",
|
||||||
"tableTo": "connections",
|
"tableTo": "connections",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["connection_id"],
|
||||||
"connection_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
@@ -252,12 +244,8 @@
|
|||||||
"name": "connection_credentials_app_id_oauth_apps_id_fk",
|
"name": "connection_credentials_app_id_oauth_apps_id_fk",
|
||||||
"tableFrom": "connection_credentials",
|
"tableFrom": "connection_credentials",
|
||||||
"tableTo": "oauth_apps",
|
"tableTo": "oauth_apps",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["app_id"],
|
||||||
"app_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "restrict",
|
"onDelete": "restrict",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
@@ -336,19 +324,12 @@
|
|||||||
"indexes": {
|
"indexes": {
|
||||||
"connections_account": {
|
"connections_account": {
|
||||||
"name": "connections_account",
|
"name": "connections_account",
|
||||||
"columns": [
|
"columns": ["platform", "origin", "account_id"],
|
||||||
"platform",
|
|
||||||
"origin",
|
|
||||||
"account_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"connections_relay_profile": {
|
"connections_relay_profile": {
|
||||||
"name": "connections_relay_profile",
|
"name": "connections_relay_profile",
|
||||||
"columns": [
|
"columns": ["origin", "relay_profile"],
|
||||||
"origin",
|
|
||||||
"relay_profile"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -406,10 +387,7 @@
|
|||||||
"indexes": {
|
"indexes": {
|
||||||
"deck_columns_position": {
|
"deck_columns_position": {
|
||||||
"name": "deck_columns_position",
|
"name": "deck_columns_position",
|
||||||
"columns": [
|
"columns": ["deck_id", "position"],
|
||||||
"deck_id",
|
|
||||||
"position"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -418,12 +396,8 @@
|
|||||||
"name": "deck_columns_deck_id_decks_id_fk",
|
"name": "deck_columns_deck_id_decks_id_fk",
|
||||||
"tableFrom": "deck_columns",
|
"tableFrom": "deck_columns",
|
||||||
"tableTo": "decks",
|
"tableTo": "decks",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["deck_id"],
|
||||||
"deck_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
@@ -431,22 +405,15 @@
|
|||||||
"name": "deck_columns_connection_id_connections_id_fk",
|
"name": "deck_columns_connection_id_connections_id_fk",
|
||||||
"tableFrom": "deck_columns",
|
"tableFrom": "deck_columns",
|
||||||
"tableTo": "connections",
|
"tableTo": "connections",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["connection_id"],
|
||||||
"connection_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "restrict",
|
"onDelete": "restrict",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"deck_columns_deck_id_id_pk": {
|
"deck_columns_deck_id_id_pk": {
|
||||||
"columns": [
|
"columns": ["deck_id", "id"],
|
||||||
"deck_id",
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"name": "deck_columns_deck_id_id_pk"
|
"name": "deck_columns_deck_id_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -603,11 +570,7 @@
|
|||||||
"indexes": {
|
"indexes": {
|
||||||
"oauth_apps_configuration": {
|
"oauth_apps_configuration": {
|
||||||
"name": "oauth_apps_configuration",
|
"name": "oauth_apps_configuration",
|
||||||
"columns": [
|
"columns": ["origin", "redirect_uri", "scopes"],
|
||||||
"origin",
|
|
||||||
"redirect_uri",
|
|
||||||
"scopes"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -675,12 +638,8 @@
|
|||||||
"name": "oauth_attempts_app_id_oauth_apps_id_fk",
|
"name": "oauth_attempts_app_id_oauth_apps_id_fk",
|
||||||
"tableFrom": "oauth_attempts",
|
"tableFrom": "oauth_attempts",
|
||||||
"tableTo": "oauth_apps",
|
"tableTo": "oauth_apps",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["app_id"],
|
||||||
"app_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
@@ -688,12 +647,8 @@
|
|||||||
"name": "oauth_attempts_connection_id_connections_id_fk",
|
"name": "oauth_attempts_connection_id_connections_id_fk",
|
||||||
"tableFrom": "oauth_attempts",
|
"tableFrom": "oauth_attempts",
|
||||||
"tableTo": "connections",
|
"tableTo": "connections",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["connection_id"],
|
||||||
"connection_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,12 +151,8 @@
|
|||||||
"name": "auth_sessions_owner_id_workspace_owner_id_fk",
|
"name": "auth_sessions_owner_id_workspace_owner_id_fk",
|
||||||
"tableFrom": "auth_sessions",
|
"tableFrom": "auth_sessions",
|
||||||
"tableTo": "workspace_owner",
|
"tableTo": "workspace_owner",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["owner_id"],
|
||||||
"owner_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
@@ -251,10 +247,7 @@
|
|||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"beeper_chat_checkpoints_target_id_chat_id_pk": {
|
"beeper_chat_checkpoints_target_id_chat_id_pk": {
|
||||||
"columns": [
|
"columns": ["target_id", "chat_id"],
|
||||||
"target_id",
|
|
||||||
"chat_id"
|
|
||||||
],
|
|
||||||
"name": "beeper_chat_checkpoints_target_id_chat_id_pk"
|
"name": "beeper_chat_checkpoints_target_id_chat_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -297,11 +290,7 @@
|
|||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"beeper_seen_messages_target_id_chat_id_message_id_pk": {
|
"beeper_seen_messages_target_id_chat_id_message_id_pk": {
|
||||||
"columns": [
|
"columns": ["target_id", "chat_id", "message_id"],
|
||||||
"target_id",
|
|
||||||
"chat_id",
|
|
||||||
"message_id"
|
|
||||||
],
|
|
||||||
"name": "beeper_seen_messages_target_id_chat_id_message_id_pk"
|
"name": "beeper_seen_messages_target_id_chat_id_message_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -346,12 +335,8 @@
|
|||||||
"name": "connection_credentials_connection_id_connections_id_fk",
|
"name": "connection_credentials_connection_id_connections_id_fk",
|
||||||
"tableFrom": "connection_credentials",
|
"tableFrom": "connection_credentials",
|
||||||
"tableTo": "connections",
|
"tableTo": "connections",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["connection_id"],
|
||||||
"connection_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
@@ -359,12 +344,8 @@
|
|||||||
"name": "connection_credentials_app_id_oauth_apps_id_fk",
|
"name": "connection_credentials_app_id_oauth_apps_id_fk",
|
||||||
"tableFrom": "connection_credentials",
|
"tableFrom": "connection_credentials",
|
||||||
"tableTo": "oauth_apps",
|
"tableTo": "oauth_apps",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["app_id"],
|
||||||
"app_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "restrict",
|
"onDelete": "restrict",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
@@ -443,19 +424,12 @@
|
|||||||
"indexes": {
|
"indexes": {
|
||||||
"connections_account": {
|
"connections_account": {
|
||||||
"name": "connections_account",
|
"name": "connections_account",
|
||||||
"columns": [
|
"columns": ["platform", "origin", "account_id"],
|
||||||
"platform",
|
|
||||||
"origin",
|
|
||||||
"account_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"connections_relay_profile": {
|
"connections_relay_profile": {
|
||||||
"name": "connections_relay_profile",
|
"name": "connections_relay_profile",
|
||||||
"columns": [
|
"columns": ["origin", "relay_profile"],
|
||||||
"origin",
|
|
||||||
"relay_profile"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -513,10 +487,7 @@
|
|||||||
"indexes": {
|
"indexes": {
|
||||||
"deck_columns_position": {
|
"deck_columns_position": {
|
||||||
"name": "deck_columns_position",
|
"name": "deck_columns_position",
|
||||||
"columns": [
|
"columns": ["deck_id", "position"],
|
||||||
"deck_id",
|
|
||||||
"position"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -525,12 +496,8 @@
|
|||||||
"name": "deck_columns_deck_id_decks_id_fk",
|
"name": "deck_columns_deck_id_decks_id_fk",
|
||||||
"tableFrom": "deck_columns",
|
"tableFrom": "deck_columns",
|
||||||
"tableTo": "decks",
|
"tableTo": "decks",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["deck_id"],
|
||||||
"deck_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
@@ -538,22 +505,15 @@
|
|||||||
"name": "deck_columns_connection_id_connections_id_fk",
|
"name": "deck_columns_connection_id_connections_id_fk",
|
||||||
"tableFrom": "deck_columns",
|
"tableFrom": "deck_columns",
|
||||||
"tableTo": "connections",
|
"tableTo": "connections",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["connection_id"],
|
||||||
"connection_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "restrict",
|
"onDelete": "restrict",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"deck_columns_deck_id_id_pk": {
|
"deck_columns_deck_id_id_pk": {
|
||||||
"columns": [
|
"columns": ["deck_id", "id"],
|
||||||
"deck_id",
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"name": "deck_columns_deck_id_id_pk"
|
"name": "deck_columns_deck_id_id_pk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -732,17 +692,12 @@
|
|||||||
"indexes": {
|
"indexes": {
|
||||||
"mcp_event_outbox_identity": {
|
"mcp_event_outbox_identity": {
|
||||||
"name": "mcp_event_outbox_identity",
|
"name": "mcp_event_outbox_identity",
|
||||||
"columns": [
|
"columns": ["subscription_id", "event_id"],
|
||||||
"subscription_id",
|
|
||||||
"event_id"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
},
|
},
|
||||||
"mcp_event_outbox_due": {
|
"mcp_event_outbox_due": {
|
||||||
"name": "mcp_event_outbox_due",
|
"name": "mcp_event_outbox_due",
|
||||||
"columns": [
|
"columns": ["next_attempt_at"],
|
||||||
"next_attempt_at"
|
|
||||||
],
|
|
||||||
"isUnique": false
|
"isUnique": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -751,12 +706,8 @@
|
|||||||
"name": "mcp_event_outbox_subscription_id_mcp_event_subscriptions_id_fk",
|
"name": "mcp_event_outbox_subscription_id_mcp_event_subscriptions_id_fk",
|
||||||
"tableFrom": "mcp_event_outbox",
|
"tableFrom": "mcp_event_outbox",
|
||||||
"tableTo": "mcp_event_subscriptions",
|
"tableTo": "mcp_event_subscriptions",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["subscription_id"],
|
||||||
"subscription_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
@@ -915,11 +866,7 @@
|
|||||||
"indexes": {
|
"indexes": {
|
||||||
"oauth_apps_configuration": {
|
"oauth_apps_configuration": {
|
||||||
"name": "oauth_apps_configuration",
|
"name": "oauth_apps_configuration",
|
||||||
"columns": [
|
"columns": ["origin", "redirect_uri", "scopes"],
|
||||||
"origin",
|
|
||||||
"redirect_uri",
|
|
||||||
"scopes"
|
|
||||||
],
|
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -987,12 +934,8 @@
|
|||||||
"name": "oauth_attempts_app_id_oauth_apps_id_fk",
|
"name": "oauth_attempts_app_id_oauth_apps_id_fk",
|
||||||
"tableFrom": "oauth_attempts",
|
"tableFrom": "oauth_attempts",
|
||||||
"tableTo": "oauth_apps",
|
"tableTo": "oauth_apps",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["app_id"],
|
||||||
"app_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
@@ -1000,12 +943,8 @@
|
|||||||
"name": "oauth_attempts_connection_id_connections_id_fk",
|
"name": "oauth_attempts_connection_id_connections_id_fk",
|
||||||
"tableFrom": "oauth_attempts",
|
"tableFrom": "oauth_attempts",
|
||||||
"tableTo": "connections",
|
"tableTo": "connections",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["connection_id"],
|
||||||
"connection_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ test("recovers from empty search and shares navigation with Research", async ({
|
|||||||
await expect(readerPage.editInterestsButton).toHaveCount(0);
|
await expect(readerPage.editInterestsButton).toHaveCount(0);
|
||||||
await workspacePage.navigateTo("Research");
|
await workspacePage.navigateTo("Research");
|
||||||
await expect(page).toHaveURL(/\/deck$/);
|
await expect(page).toHaveURL(/\/deck$/);
|
||||||
|
await expect(workspacePage.researchReady).toBeEnabled();
|
||||||
await workspacePage.navigateTo("Reader");
|
await workspacePage.navigateTo("Reader");
|
||||||
await expect(page).toHaveURL(/\/inbox$/);
|
await expect(page).toHaveURL(/\/inbox$/);
|
||||||
await expect(readerPage.rows).toHaveCount(9);
|
await expect(readerPage.rows).toHaveCount(9);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class HomePage {
|
|||||||
return this.page.getByRole("button", { name: `Record weight Recorded · ${weight} kg` });
|
return this.page.getByRole("button", { name: `Record weight Recorded · ${weight} kg` });
|
||||||
}
|
}
|
||||||
greeting(name: string) {
|
greeting(name: string) {
|
||||||
return this.page.getByRole("heading", { name: `Good morning, ${name}` });
|
return this.page.getByRole("heading", { level: 1 }).filter({ hasText: `, ${name}` });
|
||||||
}
|
}
|
||||||
async goTo(search = "") {
|
async goTo(search = "") {
|
||||||
await this.page.goto(`/${search}`);
|
await this.page.goto(`/${search}`);
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import type { KnipConfig } from "knip";
|
import type { KnipConfig } from "knip";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
entry: ["playwright.config.ts", "e2e/**/*.test.ts", "e2e/mock-relay.mjs"],
|
entry: [
|
||||||
|
"playwright.config.ts",
|
||||||
|
"e2e/**/*.{test,spec}.ts",
|
||||||
|
"e2e/mock-relay.mjs",
|
||||||
|
"public/sw.js",
|
||||||
|
"scripts/events-worker.ts",
|
||||||
|
],
|
||||||
playwright: false,
|
playwright: false,
|
||||||
} satisfies KnipConfig;
|
} satisfies KnipConfig;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Link, useLocation, useRouteContext } from "@tanstack/react-router";
|
import { Link, useHydrated, useLocation, useRouteContext } from "@tanstack/react-router";
|
||||||
import { Activity, House, Inbox, Layers3, MessagesSquare, NotebookPen, Search } from "lucide-react";
|
import { Activity, House, Inbox, Layers3, MessagesSquare, NotebookPen, Search } from "lucide-react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { Avatar, AvatarFallback } from "#/components/ui/avatar";
|
import { Avatar, AvatarFallback } from "#/components/ui/avatar";
|
||||||
@@ -29,6 +29,7 @@ export function WorkspaceNavigation({
|
|||||||
hasTemporaryDecks,
|
hasTemporaryDecks,
|
||||||
}: WorkspaceNavigationProps) {
|
}: WorkspaceNavigationProps) {
|
||||||
const { auth } = useRouteContext({ from: "__root__" });
|
const { auth } = useRouteContext({ from: "__root__" });
|
||||||
|
const hydrated = useHydrated();
|
||||||
const [search, updateSearch] = useUrlState();
|
const [search, updateSearch] = useUrlState();
|
||||||
const managingAccounts = search.accounts;
|
const managingAccounts = search.accounts;
|
||||||
const setManagingAccounts = (open: boolean) => updateSearch({ accounts: open || undefined });
|
const setManagingAccounts = (open: boolean) => updateSearch({ accounts: open || undefined });
|
||||||
@@ -116,7 +117,9 @@ export function WorkspaceNavigation({
|
|||||||
<SidebarMenu className="mt-2">
|
<SidebarMenu className="mt-2">
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
aria-label="Manage connected accounts"
|
render={
|
||||||
|
<button type="button" aria-label="Manage connected accounts" disabled={!hydrated} />
|
||||||
|
}
|
||||||
tooltip={{ children: "Manage connected accounts", hidden: false }}
|
tooltip={{ children: "Manage connected accounts", hidden: false }}
|
||||||
className="size-10 justify-center p-0"
|
className="size-10 justify-center p-0"
|
||||||
onClick={() => setManagingAccounts(true)}
|
onClick={() => setManagingAccounts(true)}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const required = (max: number) =>
|
|||||||
.max(max)
|
.max(max)
|
||||||
.refine((value) => value.trim().length > 0);
|
.refine((value) => value.trim().length > 0);
|
||||||
const section = z.enum(["focus", "optional"]);
|
const section = z.enum(["focus", "optional"]);
|
||||||
export const activityState = z.enum([
|
const activityState = z.enum([
|
||||||
"available",
|
"available",
|
||||||
"inProgress",
|
"inProgress",
|
||||||
"waiting",
|
"waiting",
|
||||||
@@ -48,7 +48,7 @@ const base = {
|
|||||||
recommendation: z.strictObject({ section, reason: z.string().max(2000).optional() }).optional(),
|
recommendation: z.strictObject({ section, reason: z.string().max(2000).optional() }).optional(),
|
||||||
sources: z.array(z.discriminatedUnion("kind", [messageSource, lifeSource])).max(20),
|
sources: z.array(z.discriminatedUnion("kind", [messageSource, lifeSource])).max(20),
|
||||||
};
|
};
|
||||||
export const activityProposal = z
|
const activityProposal = z
|
||||||
.discriminatedUnion("kind", [
|
.discriminatedUnion("kind", [
|
||||||
z.strictObject({ ...base, kind: z.literal("task") }),
|
z.strictObject({ ...base, kind: z.literal("task") }),
|
||||||
z.strictObject({
|
z.strictObject({
|
||||||
@@ -89,7 +89,7 @@ export const publishActivitiesInput = z
|
|||||||
new Set(value.activities.map((activity) => activity.id)).size === value.activities.length,
|
new Set(value.activities.map((activity) => activity.id)).size === value.activities.length,
|
||||||
"Activity IDs must be unique.",
|
"Activity IDs must be unique.",
|
||||||
);
|
);
|
||||||
export const activityOverrides = z.strictObject({
|
const activityOverrides = z.strictObject({
|
||||||
title: required(300).optional(),
|
title: required(300).optional(),
|
||||||
nextAction: required(2000).optional(),
|
nextAction: required(2000).optional(),
|
||||||
detail: z.string().max(8000).optional(),
|
detail: z.string().max(8000).optional(),
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||||
|
import { renderToString } from "react-dom/server";
|
||||||
|
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
||||||
|
import { Greeting } from "./greeting";
|
||||||
|
|
||||||
|
beforeEach(() => vi.useFakeTimers());
|
||||||
|
afterEach(() => vi.useRealTimers());
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[0, "Good evening"],
|
||||||
|
[4, "Good evening"],
|
||||||
|
[5, "Good morning"],
|
||||||
|
[11, "Good morning"],
|
||||||
|
[12, "Good afternoon"],
|
||||||
|
[17, "Good afternoon"],
|
||||||
|
[18, "Good evening"],
|
||||||
|
[23, "Good evening"],
|
||||||
|
])("greets using the device's local hour %i", (hour, greeting) => {
|
||||||
|
vi.setSystemTime(new Date(2026, 9, 8, hour));
|
||||||
|
render(<Greeting name="Yuta" />);
|
||||||
|
expect(screen.getByText(`${greeting}, Yuta`)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates when an open page crosses noon", async () => {
|
||||||
|
vi.setSystemTime(new Date(2026, 9, 8, 11, 59, 50));
|
||||||
|
render(<Greeting name="Yuta" />);
|
||||||
|
expect(screen.getByText("Good morning, Yuta")).toBeVisible();
|
||||||
|
await act(() => vi.advanceTimersByTime(30_000));
|
||||||
|
expect(screen.getByText("Good afternoon, Yuta")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("refreshes after returning to a suspended page", () => {
|
||||||
|
vi.setSystemTime(new Date(2026, 9, 8, 10));
|
||||||
|
render(<Greeting name="Yuta" />);
|
||||||
|
vi.setSystemTime(new Date(2026, 9, 8, 20));
|
||||||
|
fireEvent(document, new Event("visibilitychange"));
|
||||||
|
expect(screen.getByText("Good evening, Yuta")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not use the server's timezone in the initial HTML", () => {
|
||||||
|
vi.setSystemTime(new Date(2026, 9, 8, 10));
|
||||||
|
expect(renderToString(<Greeting name="Yuta" />)).toContain("Hello");
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
function currentGreeting() {
|
||||||
|
const hour = new Date().getHours();
|
||||||
|
if (hour >= 5 && hour < 12) return "Good morning";
|
||||||
|
if (hour >= 12 && hour < 18) return "Good afternoon";
|
||||||
|
return "Good evening";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Greeting({ name }: { name: string | undefined }) {
|
||||||
|
// The server cannot know the device's timezone. Keep hydration consistent.
|
||||||
|
const [greeting, setGreeting] = useState("Hello");
|
||||||
|
useEffect(() => {
|
||||||
|
const update = () => setGreeting(currentGreeting());
|
||||||
|
update();
|
||||||
|
const interval = window.setInterval(update, 30_000);
|
||||||
|
document.addEventListener("visibilitychange", update);
|
||||||
|
return () => {
|
||||||
|
window.clearInterval(interval);
|
||||||
|
document.removeEventListener("visibilitychange", update);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{greeting}, {name}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import { useActivities } from "#/features/activities/activity-state";
|
|||||||
import { entryKindLabels, useWorkspaceState } from "#/features/workspace/workspace-state";
|
import { entryKindLabels, useWorkspaceState } from "#/features/workspace/workspace-state";
|
||||||
import { DailyBrief } from "./daily-brief";
|
import { DailyBrief } from "./daily-brief";
|
||||||
import { ExecutionWorkbench } from "./execution-workbench";
|
import { ExecutionWorkbench } from "./execution-workbench";
|
||||||
|
import { Greeting } from "./greeting";
|
||||||
import { type HomeAction, interpretAction } from "./interpret-action";
|
import { type HomeAction, interpretAction } from "./interpret-action";
|
||||||
|
|
||||||
export function HomePage() {
|
export function HomePage() {
|
||||||
@@ -191,7 +192,7 @@ export function HomePage() {
|
|||||||
<header className="mb-px-32">
|
<header className="mb-px-32">
|
||||||
<p className="text-sm text-muted-foreground">Today</p>
|
<p className="text-sm text-muted-foreground">Today</p>
|
||||||
<h1 className="mt-px-12 text-home-greeting font-book tracking-home leading-normal max-md:text-ui-25">
|
<h1 className="mt-px-12 text-home-greeting font-book tracking-home leading-normal max-md:text-ui-25">
|
||||||
Good morning, {auth.owner?.name}
|
<Greeting name={auth.owner?.name} />
|
||||||
</h1>
|
</h1>
|
||||||
<DailyBrief onOpenContact={openContact} />
|
<DailyBrief onOpenContact={openContact} />
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
type WebhookSender,
|
type WebhookSender,
|
||||||
} from "./webhook.server";
|
} from "./webhook.server";
|
||||||
|
|
||||||
export const BEEPER_MESSAGE_EVENT = "beeper.message.received";
|
const BEEPER_MESSAGE_EVENT = "beeper.message.received";
|
||||||
// Explicit single-owner prototype trust boundary: callers can reach the private workspace MCP tunnel.
|
// Explicit single-owner prototype trust boundary: callers can reach the private workspace MCP tunnel.
|
||||||
// This is a storage namespace, not an authenticated user identity.
|
// This is a storage namespace, not an authenticated user identity.
|
||||||
const OWNER = "private-workspace";
|
const OWNER = "private-workspace";
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function isPublicAddress(address: string) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
export type WebhookResponse = { status: number; body: string };
|
type WebhookResponse = { status: number; body: string };
|
||||||
export type WebhookSender = (
|
export type WebhookSender = (
|
||||||
url: string,
|
url: string,
|
||||||
body: string,
|
body: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user