@agent-native/core 0.151.2 → 0.152.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +24 -0
  4. package/corpus/templates/analytics/actions/bigquery-query.ts +21 -0
  5. package/corpus/templates/analytics/app/components/ConnectionTestStatus.tsx +65 -0
  6. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +3 -3
  7. package/corpus/templates/analytics/app/pages/DataSources.tsx +15 -37
  8. package/corpus/templates/analytics/app/root.tsx +0 -1
  9. package/corpus/templates/analytics/changelog/2026-08-11-analytics-custom-blocks-keep-loading-with-legacy-bigquery-action.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-08-11-analytics-routes-company-knowledge-and-slack-context-questio.md +6 -0
  11. package/corpus/templates/analytics/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  12. package/corpus/templates/analytics/public/manifest.json +0 -1
  13. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +5 -0
  14. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  15. package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +1 -1
  16. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +24 -0
  17. package/corpus/templates/assets/app/root.tsx +0 -1
  18. package/corpus/templates/assets/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  19. package/corpus/templates/assets/public/manifest.json +0 -1
  20. package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +1 -1
  21. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +24 -0
  22. package/corpus/templates/brain/app/root.tsx +0 -1
  23. package/corpus/templates/brain/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  24. package/corpus/templates/brain/public/manifest.json +0 -1
  25. package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +1 -1
  26. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +24 -0
  27. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +0 -53
  28. package/corpus/templates/calendar/app/i18n-data.ts +0 -80
  29. package/corpus/templates/calendar/app/root.tsx +0 -1
  30. package/corpus/templates/calendar/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  31. package/corpus/templates/calendar/changelog/2026-08-11-google-calendar-connection-buttons-now-open-the-sign-in-flow.md +6 -0
  32. package/corpus/templates/calendar/public/manifest.json +0 -1
  33. package/corpus/templates/calendar/server/plugins/auth.ts +0 -10
  34. package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  35. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  36. package/corpus/templates/chat/app/root.tsx +0 -1
  37. package/corpus/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  38. package/corpus/templates/chat/public/manifest.json +0 -1
  39. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +24 -0
  40. package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +1 -1
  41. package/corpus/templates/clips/app/components/player/share-dialog.tsx +6 -0
  42. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +20 -3
  43. package/corpus/templates/clips/app/i18n/en-US.ts +4 -4
  44. package/corpus/templates/clips/app/root.tsx +0 -1
  45. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +1 -3
  46. package/corpus/templates/clips/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-08-11-clips-desktop-settings-are-now-easier-to-scan-with-a-wider-t.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-08-11-google-calendar-connections-from-meetings-now-open-directly.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-08-11-recording-viewers-can-comment-and-react.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-08-11-system-audio-recording-now-lives-inside-the-microphone-menu-.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-08-11-timestamped-comments-stay-on-the-video-for-three-seconds-wh.md +6 -0
  52. package/corpus/templates/clips/desktop/src/app.tsx +773 -1001
  53. package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +15 -27
  54. package/corpus/templates/clips/desktop/src/styles.css +364 -5
  55. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +1 -1
  56. package/corpus/templates/clips/public/manifest.json +0 -1
  57. package/corpus/templates/content/.agents/skills/sharing/SKILL.md +1 -1
  58. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +24 -0
  59. package/corpus/templates/content/AGENTS.md +3 -0
  60. package/corpus/templates/content/actions/resolve-content-landing.ts +120 -0
  61. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +196 -30
  62. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +9 -0
  63. package/corpus/templates/content/app/i18n-data.ts +58 -0
  64. package/corpus/templates/content/app/lib/content-landing.ts +22 -0
  65. package/corpus/templates/content/app/root.tsx +0 -1
  66. package/corpus/templates/content/app/routes/_app._index.tsx +36 -26
  67. package/corpus/templates/content/changelog/2026-08-03-choose-text-or-a-heading-from-one-compact-formatting-control.md +6 -0
  68. package/corpus/templates/content/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  69. package/corpus/templates/content/changelog/2026-08-11-content-opens-your-last-page-on-return-and-gives-new-users-a.md +6 -0
  70. package/corpus/templates/content/docs/product/capabilities/content.workspace.session-restore.md +7 -1
  71. package/corpus/templates/content/docs/solutions/evidence/content-text-selector/README.md +26 -0
  72. package/corpus/templates/content/parity/matrix.md +1 -0
  73. package/corpus/templates/content/parity/matrix.ts +22 -0
  74. package/corpus/templates/content/public/manifest.json +0 -1
  75. package/corpus/templates/content/shared/content-landing.ts +17 -0
  76. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +1 -1
  77. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +24 -0
  78. package/corpus/templates/crm/app/root.tsx +0 -1
  79. package/corpus/templates/crm/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  80. package/corpus/templates/crm/public/manifest.json +0 -1
  81. package/corpus/templates/design/.agents/skills/sharing/SKILL.md +1 -1
  82. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +24 -0
  83. package/corpus/templates/design/app/i18n/en-US.ts +5 -0
  84. package/corpus/templates/design/app/root.tsx +0 -1
  85. package/corpus/templates/design/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  86. package/corpus/templates/design/changelog/2026-08-11-design-sharing-now-identifies-viewers-who-can-add-review-com.md +6 -0
  87. package/corpus/templates/design/public/manifest.json +0 -1
  88. package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +1 -1
  89. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +24 -0
  90. package/corpus/templates/dispatch/AGENTS.md +9 -0
  91. package/corpus/templates/dispatch/actions/list-dispatch-usage-metrics.ts +5 -19
  92. package/corpus/templates/dispatch/actions/view-screen.ts +10 -3
  93. package/corpus/templates/dispatch/app/root.tsx +0 -1
  94. package/corpus/templates/dispatch/app/routes/settings.tsx +13 -1
  95. package/corpus/templates/dispatch/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  96. package/corpus/templates/dispatch/changelog/2026-08-11-clicking-the-dispatch-logo-returns-to-the-overview-page.md +6 -0
  97. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-no-longer-links-to-itself-from-the-app-switcher.md +6 -0
  98. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-settings-now-link-directly-to-admin.md +6 -0
  99. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-shows-usage-trends-recent-prompts-and-an-agent-revi.md +6 -0
  100. package/corpus/templates/dispatch/changelog/2026-08-11-human-friendly-app-names.md +6 -0
  101. package/corpus/templates/dispatch/public/manifest.json +0 -1
  102. package/corpus/templates/dispatch/server/lib/identity-sso.ts +432 -158
  103. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +204 -123
  104. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +1 -0
  105. package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +1 -1
  106. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +24 -0
  107. package/corpus/templates/factory/app/root.tsx +0 -1
  108. package/corpus/templates/factory/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  109. package/corpus/templates/factory/public/manifest.json +0 -1
  110. package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +1 -1
  111. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +24 -0
  112. package/corpus/templates/forms/app/root.tsx +0 -1
  113. package/corpus/templates/forms/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  114. package/corpus/templates/forms/public/manifest.json +0 -1
  115. package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +1 -1
  116. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +24 -0
  117. package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +1 -1
  118. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +24 -0
  119. package/corpus/templates/mail/app/root.tsx +0 -1
  120. package/corpus/templates/mail/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  121. package/corpus/templates/mail/changelog/2026-08-11-google-sign-in-now-opens-the-gmail-connection-flow-directly.md +6 -0
  122. package/corpus/templates/mail/public/manifest.json +0 -1
  123. package/corpus/templates/mail/server/plugins/auth.ts +0 -10
  124. package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +1 -1
  125. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +24 -0
  126. package/corpus/templates/plan/app/i18n/en-US.ts +2 -0
  127. package/corpus/templates/plan/app/pages/PlansPage.tsx +6 -0
  128. package/corpus/templates/plan/app/root.tsx +0 -1
  129. package/corpus/templates/plan/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  130. package/corpus/templates/plan/changelog/2026-08-11-plan-sharing-now-identifies-viewers-who-can-add-comments.md +6 -0
  131. package/corpus/templates/plan/public/manifest.json +0 -1
  132. package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +1 -1
  133. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +24 -0
  134. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -0
  135. package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
  136. package/corpus/templates/slides/app/root.tsx +0 -1
  137. package/corpus/templates/slides/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  138. package/corpus/templates/slides/changelog/2026-08-11-share-dialog-uses-commenter-terminology.md +6 -0
  139. package/corpus/templates/slides/public/manifest.json +0 -1
  140. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +1 -1
  141. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +24 -0
  142. package/corpus/templates/tasks/app/root.tsx +0 -1
  143. package/corpus/templates/tasks/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  144. package/corpus/templates/tasks/public/manifest.json +0 -1
  145. package/dist/agent/production-agent.d.ts +8 -0
  146. package/dist/agent/production-agent.js +67 -22
  147. package/dist/agent/run-manager.d.ts +7 -0
  148. package/dist/agent/run-manager.js +46 -3
  149. package/dist/agent/tool-approval-migrations.d.ts +40 -0
  150. package/dist/agent/tool-approval-migrations.js +51 -0
  151. package/dist/agent/tool-approval-store.js +5 -20
  152. package/dist/cli/templates-meta.js +2 -2
  153. package/dist/client/AgentPanel.js +10 -1
  154. package/dist/client/AssistantChat.d.ts +3 -0
  155. package/dist/client/AssistantChat.js +151 -141
  156. package/dist/client/agent-page/AgentTabsPage.js +2 -2
  157. package/dist/client/chat/attachment-adapters.js +8 -2
  158. package/dist/client/chat/tool-call-display.d.ts +1 -0
  159. package/dist/client/chat/tool-call-display.js +3 -1
  160. package/dist/client/chat-first.js +11 -5
  161. package/dist/client/guided-questions.d.ts +15 -1
  162. package/dist/client/guided-questions.js +33 -15
  163. package/dist/client/hooks/index.d.ts +1 -1
  164. package/dist/client/hooks/index.js +1 -1
  165. package/dist/client/org/OrgSwitcher.d.ts +3 -1
  166. package/dist/client/org/OrgSwitcher.js +22 -16
  167. package/dist/client/org/WorkspaceNotice.js +3 -1
  168. package/dist/client/resources/index.d.ts +1 -1
  169. package/dist/client/resources/index.js +1 -1
  170. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  171. package/dist/client/resources/use-mcp-servers.js +14 -0
  172. package/dist/client/settings/AccountSettingsCard.js +8 -4
  173. package/dist/client/settings/SettingsPanel.js +46 -13
  174. package/dist/client/settings/SettingsTabsPage.js +8 -3
  175. package/dist/client/sharing/ShareButton.d.ts +7 -1
  176. package/dist/client/sharing/ShareButton.js +14 -7
  177. package/dist/client/sharing/ShareDialog.js +5 -1
  178. package/dist/client/sharing/useShareButtonController.d.ts +4 -0
  179. package/dist/client/sharing/useShareButtonController.js +15 -0
  180. package/dist/client/sharing/useShareDialogController.d.ts +7 -0
  181. package/dist/client/sharing/useShareDialogController.js +14 -0
  182. package/dist/client/sse-event-processor.js +37 -0
  183. package/dist/client/use-session.d.ts +2 -0
  184. package/dist/client/use-session.js +69 -5
  185. package/dist/deploy/build.js +1 -1
  186. package/dist/file-upload/pre-upload-attachments.d.ts +3 -4
  187. package/dist/file-upload/pre-upload-attachments.js +65 -17
  188. package/dist/ingestion/index.d.ts +1 -0
  189. package/dist/ingestion/index.js +1 -0
  190. package/dist/ingestion/office.d.ts +3 -3
  191. package/dist/ingestion/office.js +24 -2
  192. package/dist/ingestion/spreadsheet.d.ts +41 -0
  193. package/dist/ingestion/spreadsheet.js +172 -0
  194. package/dist/integrations/adapters/slack.js +7 -3
  195. package/dist/integrations/webhook-handler.js +11 -3
  196. package/dist/localization/default-messages.d.ts +3 -0
  197. package/dist/localization/default-messages.js +3 -0
  198. package/dist/mcp/embed-app.js +20 -9
  199. package/dist/notifications/routes.d.ts +3 -3
  200. package/dist/oauth-tokens/migrations.d.ts +8 -0
  201. package/dist/oauth-tokens/migrations.js +36 -0
  202. package/dist/observability/routes.d.ts +5 -5
  203. package/dist/org/workspace-url.d.ts +8 -0
  204. package/dist/org/workspace-url.js +26 -2
  205. package/dist/progress/routes.d.ts +1 -1
  206. package/dist/secrets/routes.d.ts +6 -6
  207. package/dist/server/agent-chat/context-tools.js +27 -6
  208. package/dist/server/agent-chat-plugin.js +16 -8
  209. package/dist/server/auth.d.ts +0 -11
  210. package/dist/server/auth.js +37 -20
  211. package/dist/server/better-auth-migrations.js +23 -0
  212. package/dist/server/email-template.d.ts +5 -0
  213. package/dist/server/email-template.js +9 -0
  214. package/dist/server/embed-route.js +17 -5
  215. package/dist/server/identity-sso-store.d.ts +40 -66
  216. package/dist/server/identity-sso-store.js +187 -150
  217. package/dist/server/identity-sso.d.ts +17 -73
  218. package/dist/server/identity-sso.js +294 -297
  219. package/dist/server/onboarding-html.d.ts +0 -12
  220. package/dist/server/onboarding-html.js +46 -269
  221. package/dist/server/realtime-token.d.ts +1 -1
  222. package/dist/server/release-migrations.js +8 -0
  223. package/dist/server/transcribe-voice.d.ts +1 -1
  224. package/dist/shared/index.d.ts +1 -1
  225. package/dist/shared/index.js +1 -1
  226. package/dist/shared/workspace-app-id.d.ts +2 -0
  227. package/dist/shared/workspace-app-id.js +10 -0
  228. package/dist/sharing/actions/share-resource.d.ts +1 -0
  229. package/dist/sharing/actions/share-resource.js +20 -5
  230. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  231. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  232. package/dist/templates/chat/app/root.tsx +0 -1
  233. package/dist/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  234. package/dist/templates/chat/public/manifest.json +0 -1
  235. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +24 -0
  236. package/dist/templates/default/app/root.tsx +0 -1
  237. package/dist/templates/default/public/manifest.json +0 -1
  238. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +24 -0
  239. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +22 -11
  240. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -1
  241. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +24 -0
  242. package/docs/content/cross-app-sso.mdx +85 -27
  243. package/docs/content/template-mail.mdx +0 -6
  244. package/package.json +4 -3
  245. package/src/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  246. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  247. package/src/templates/chat/app/root.tsx +0 -1
  248. package/src/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  249. package/src/templates/chat/public/manifest.json +0 -1
  250. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +24 -0
  251. package/src/templates/default/app/root.tsx +0 -1
  252. package/src/templates/default/public/manifest.json +0 -1
  253. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +24 -0
  254. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +22 -11
  255. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -1
  256. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +24 -0
  257. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +0 -6
  258. package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +0 -51
  259. package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +0 -6
@@ -1,15 +1,13 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
2
  import {
3
3
  IconBold,
4
+ IconCheck,
5
+ IconChevronDown,
4
6
  IconItalic,
5
7
  IconStrikethrough,
6
8
  IconCode,
7
9
  IconLink,
8
10
  IconMessageCircle,
9
- IconH1,
10
- IconH2,
11
- IconH3,
12
- IconH4,
13
11
  } from "@tabler/icons-react";
14
12
  import {
15
13
  NodeSelection,
@@ -20,8 +18,13 @@ import {
20
18
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
21
19
  import type { Editor } from "@tiptap/react";
22
20
  import { BubbleMenu } from "@tiptap/react/menus";
23
- import { useCallback, useEffect, useState } from "react";
21
+ import { useCallback, useEffect, useRef, useState } from "react";
24
22
 
23
+ import {
24
+ Popover,
25
+ PopoverContent,
26
+ PopoverTrigger,
27
+ } from "@/components/ui/popover";
25
28
  import {
26
29
  Tooltip,
27
30
  TooltipContent,
@@ -56,6 +59,15 @@ type SelectionFillRange = {
56
59
  to: number;
57
60
  };
58
61
 
62
+ type TextStyle = "paragraph" | 1 | 2 | 3 | 4 | 5 | 6;
63
+
64
+ function activeTextStyle(editor: Editor): TextStyle {
65
+ for (const level of [1, 2, 3, 4, 5, 6] as const) {
66
+ if (editor.isActive("heading", { level })) return level;
67
+ }
68
+ return "paragraph";
69
+ }
70
+
59
71
  const selectionFillPluginKey = new PluginKey<SelectionFillRange | null>(
60
72
  "contentSelectionFill",
61
73
  );
@@ -106,6 +118,86 @@ export function BubbleToolbar({ editor, onComment }: BubbleToolbarProps) {
106
118
  const t = useT();
107
119
  const [showLinkInput, setShowLinkInput] = useState(false);
108
120
  const [linkUrl, setLinkUrl] = useState("");
121
+ const [textStyleOpen, setTextStyleOpen] = useState(false);
122
+ const textStyleSelection = useRef<{ from: number; to: number } | null>(null);
123
+ const textStyleApplied = useRef(false);
124
+ const restoreEditorFocusOnClose = useRef(false);
125
+ const [textStyle, setTextStyle] = useState<TextStyle>(() =>
126
+ activeTextStyle(editor),
127
+ );
128
+
129
+ useEffect(() => {
130
+ const syncTextStyle = () => {
131
+ setTextStyle(activeTextStyle(editor));
132
+ const { from, to } = editor.state.selection;
133
+ if (from !== to) textStyleSelection.current = { from, to };
134
+ };
135
+ editor.on("selectionUpdate", syncTextStyle);
136
+ editor.on("transaction", syncTextStyle);
137
+ syncTextStyle();
138
+ return () => {
139
+ editor.off("selectionUpdate", syncTextStyle);
140
+ editor.off("transaction", syncTextStyle);
141
+ };
142
+ }, [editor]);
143
+
144
+ const textStyles = [
145
+ {
146
+ value: "paragraph" as const,
147
+ shortLabel: t("editor.slash.text"),
148
+ menuLabel: "T",
149
+ label: t("editor.slash.text"),
150
+ },
151
+ {
152
+ value: 1 as const,
153
+ shortLabel: "H1",
154
+ menuLabel: "H1",
155
+ label: t("editor.heading1"),
156
+ },
157
+ {
158
+ value: 2 as const,
159
+ shortLabel: "H2",
160
+ menuLabel: "H2",
161
+ label: t("editor.heading2"),
162
+ },
163
+ {
164
+ value: 3 as const,
165
+ shortLabel: "H3",
166
+ menuLabel: "H3",
167
+ label: t("editor.heading3"),
168
+ },
169
+ {
170
+ value: 4 as const,
171
+ shortLabel: "H4",
172
+ menuLabel: "H4",
173
+ label: t("editor.heading4"),
174
+ },
175
+ ];
176
+ const selectedTextStyle =
177
+ textStyles.find((style) => style.value === textStyle) ??
178
+ (typeof textStyle === "number"
179
+ ? {
180
+ value: textStyle,
181
+ shortLabel: `H${textStyle}`,
182
+ menuLabel: `H${textStyle}`,
183
+ label: textStyle === 5 ? t("editor.heading5") : t("editor.heading6"),
184
+ }
185
+ : textStyles[0]);
186
+
187
+ const applyTextStyle = (style: TextStyle) => {
188
+ const chain = editor.chain();
189
+ if (textStyleSelection.current) {
190
+ chain.setTextSelection(textStyleSelection.current);
191
+ }
192
+ if (style === "paragraph") {
193
+ chain.setParagraph().focus().run();
194
+ } else {
195
+ chain.setHeading({ level: style }).focus().run();
196
+ }
197
+ textStyleApplied.current = true;
198
+ setTextStyle(style);
199
+ setTextStyleOpen(false);
200
+ };
109
201
 
110
202
  const openLinkInput = useCallback(() => {
111
203
  setLinkUrl(editor.getAttributes("link").href || "");
@@ -251,30 +343,7 @@ export function BubbleToolbar({ editor, onComment }: BubbleToolbarProps) {
251
343
  isActive: () => editor.isActive("code"),
252
344
  },
253
345
  { type: "divider" as const },
254
- {
255
- icon: IconH1,
256
- title: t("editor.heading1"),
257
- action: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
258
- isActive: () => editor.isActive("heading", { level: 1 }),
259
- },
260
- {
261
- icon: IconH2,
262
- title: t("editor.heading2"),
263
- action: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
264
- isActive: () => editor.isActive("heading", { level: 2 }),
265
- },
266
- {
267
- icon: IconH3,
268
- title: t("editor.heading3"),
269
- action: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
270
- isActive: () => editor.isActive("heading", { level: 3 }),
271
- },
272
- {
273
- icon: IconH4,
274
- title: t("editor.heading4"),
275
- action: () => editor.chain().focus().toggleHeading({ level: 4 }).run(),
276
- isActive: () => editor.isActive("heading", { level: 4 }),
277
- },
346
+ { type: "text-style" as const },
278
347
  { type: "divider" as const },
279
348
  {
280
349
  icon: IconLink,
@@ -354,7 +423,7 @@ export function BubbleToolbar({ editor, onComment }: BubbleToolbarProps) {
354
423
  </div>
355
424
  ) : (
356
425
  <div
357
- className="flex items-center gap-0.5 overflow-x-auto"
426
+ className="flex max-w-[calc(100vw-1rem)] items-center gap-0.5"
358
427
  onMouseDown={(e) => e.preventDefault()}
359
428
  >
360
429
  {items.map((item, i) => {
@@ -363,6 +432,103 @@ export function BubbleToolbar({ editor, onComment }: BubbleToolbarProps) {
363
432
  <div key={`d-${i}`} className="w-px h-5 bg-border mx-0.5" />
364
433
  );
365
434
  }
435
+ if ("type" in item && item.type === "text-style") {
436
+ return (
437
+ <Popover
438
+ key="text-style"
439
+ open={textStyleOpen}
440
+ onOpenChange={(open) => {
441
+ if (open) {
442
+ textStyleApplied.current = false;
443
+ restoreEditorFocusOnClose.current = false;
444
+ const { from, to } = editor.state.selection;
445
+ if (from !== to)
446
+ textStyleSelection.current = { from, to };
447
+ }
448
+ setTextStyleOpen(open);
449
+ }}
450
+ >
451
+ <PopoverTrigger asChild>
452
+ <button
453
+ type="button"
454
+ aria-label={`${t("editor.slash.turnInto")}: ${selectedTextStyle.label}`}
455
+ className="flex h-8 min-w-14 items-center justify-between gap-1 rounded px-2 text-sm font-medium text-popover-foreground/85 hover:bg-accent hover:text-accent-foreground"
456
+ >
457
+ <span>{selectedTextStyle.shortLabel}</span>
458
+ <IconChevronDown size={14} strokeWidth={2} />
459
+ </button>
460
+ </PopoverTrigger>
461
+ <PopoverContent
462
+ portalled={false}
463
+ align="start"
464
+ sideOffset={24}
465
+ className="w-44 p-1"
466
+ onEscapeKeyDown={() => {
467
+ restoreEditorFocusOnClose.current = true;
468
+ window.setTimeout(() => editor.commands.focus(), 0);
469
+ }}
470
+ onCloseAutoFocus={(event) => {
471
+ if (
472
+ textStyleApplied.current ||
473
+ restoreEditorFocusOnClose.current
474
+ ) {
475
+ event.preventDefault();
476
+ }
477
+ if (restoreEditorFocusOnClose.current) {
478
+ editor.commands.focus();
479
+ }
480
+ textStyleApplied.current = false;
481
+ restoreEditorFocusOnClose.current = false;
482
+ }}
483
+ >
484
+ <div className="px-2 py-1 text-xs font-medium text-muted-foreground">
485
+ {t("editor.slash.turnInto")}
486
+ </div>
487
+ <div
488
+ role="menu"
489
+ aria-label={t("editor.slash.turnInto")}
490
+ className="flex flex-col gap-0.5"
491
+ >
492
+ {textStyles.map((style) => {
493
+ const isSelected = style.value === textStyle;
494
+ return (
495
+ <button
496
+ key={style.value}
497
+ type="button"
498
+ role="menuitemradio"
499
+ aria-checked={isSelected}
500
+ onPointerDown={(event) => {
501
+ event.preventDefault();
502
+ event.stopPropagation();
503
+ applyTextStyle(style.value);
504
+ }}
505
+ onClick={(event) => {
506
+ if (event.detail === 0) {
507
+ applyTextStyle(style.value);
508
+ }
509
+ }}
510
+ className={cn(
511
+ "flex h-8 w-full items-center gap-2 rounded px-2 text-left text-sm",
512
+ isSelected
513
+ ? "bg-accent text-accent-foreground"
514
+ : "text-popover-foreground hover:bg-accent hover:text-accent-foreground",
515
+ )}
516
+ >
517
+ <span className="w-6 shrink-0 text-xs font-semibold text-muted-foreground">
518
+ {style.menuLabel}
519
+ </span>
520
+ <span className="flex-1">{style.label}</span>
521
+ {isSelected ? (
522
+ <IconCheck size={15} strokeWidth={2.25} />
523
+ ) : null}
524
+ </button>
525
+ );
526
+ })}
527
+ </div>
528
+ </PopoverContent>
529
+ </Popover>
530
+ );
531
+ }
366
532
  const {
367
533
  icon: Icon,
368
534
  title,
@@ -78,6 +78,7 @@ import {
78
78
  useDocumentSyncStatus,
79
79
  usePushDocumentToNotion,
80
80
  } from "@/hooks/use-notion";
81
+ import { rememberContentLandingDocument } from "@/lib/content-landing";
81
82
  import {
82
83
  canWriteLinkedLocalSource,
83
84
  writeDocumentToLinkedLocalSource,
@@ -538,6 +539,14 @@ function DocumentEditorBody({
538
539
  databaseDocumentId,
539
540
  }: DocumentEditorBodyProps) {
540
541
  const t = useT();
542
+ useEffect(() => {
543
+ void rememberContentLandingDocument(documentId).catch((error) => {
544
+ toast.error(t("landing.saveFailed"), {
545
+ description:
546
+ error instanceof Error ? error.message : t("empty.genericError"),
547
+ });
548
+ });
549
+ }, [documentId, t]);
541
550
  const updateDocument = useUpdateDocument();
542
551
  const createDatabase = useCreateContentDatabase(documentId);
543
552
  const deleteContentDatabase = useDeleteContentDatabase();
@@ -3041,6 +3041,11 @@ const enUS = {
3041
3041
  agent: "Agent",
3042
3042
  settings: "Settings",
3043
3043
  },
3044
+ landing: {
3045
+ previousPageUnavailable:
3046
+ "Your previous page is no longer available, so we opened Welcome.",
3047
+ saveFailed: "Your place could not be saved",
3048
+ },
3044
3049
  team: {
3045
3050
  metaTitle: "Workspace access - Content",
3046
3051
  pageTitle: "Workspace access",
@@ -8959,11 +8964,63 @@ const commentMessagesByLocale = {
8959
8964
  "ar-SA": { unanchored: "التمييز غير متاح" },
8960
8965
  } satisfies Partial<Record<LocaleCode, Partial<typeof enUS.comments>>>;
8961
8966
 
8967
+ const landingMessagesByLocale = {
8968
+ "zh-CN": {
8969
+ previousPageUnavailable: "您之前的页面已不可用,因此我们打开了欢迎页面。",
8970
+ saveFailed: "无法保存您的位置",
8971
+ },
8972
+ "zh-TW": {
8973
+ previousPageUnavailable: "您先前的頁面已無法使用,因此我們開啟了歡迎頁面。",
8974
+ saveFailed: "無法儲存您的位置",
8975
+ },
8976
+ "es-ES": {
8977
+ previousPageUnavailable:
8978
+ "Tu página anterior ya no está disponible, así que abrimos la página de bienvenida.",
8979
+ saveFailed: "No se pudo guardar tu ubicación",
8980
+ },
8981
+ "fr-FR": {
8982
+ previousPageUnavailable:
8983
+ "Votre page précédente n’est plus disponible. Nous avons donc ouvert la page de bienvenue.",
8984
+ saveFailed: "Votre emplacement n’a pas pu être enregistré",
8985
+ },
8986
+ "de-DE": {
8987
+ previousPageUnavailable:
8988
+ "Deine vorherige Seite ist nicht mehr verfügbar. Daher wurde die Willkommensseite geöffnet.",
8989
+ saveFailed: "Deine Position konnte nicht gespeichert werden",
8990
+ },
8991
+ "ja-JP": {
8992
+ previousPageUnavailable:
8993
+ "前回のページを利用できないため、ようこそページを開きました。",
8994
+ saveFailed: "現在位置を保存できませんでした",
8995
+ },
8996
+ "ko-KR": {
8997
+ previousPageUnavailable:
8998
+ "이전 페이지를 더 이상 사용할 수 없어 시작 페이지를 열었습니다.",
8999
+ saveFailed: "현재 위치를 저장하지 못했습니다",
9000
+ },
9001
+ "pt-BR": {
9002
+ previousPageUnavailable:
9003
+ "Sua página anterior não está mais disponível, então abrimos a página de boas-vindas.",
9004
+ saveFailed: "Não foi possível salvar sua localização",
9005
+ },
9006
+ "hi-IN": {
9007
+ previousPageUnavailable:
9008
+ "आपका पिछला पेज अब उपलब्ध नहीं है, इसलिए हमने स्वागत पेज खोल दिया है।",
9009
+ saveFailed: "आपकी जगह सेव नहीं की जा सकी",
9010
+ },
9011
+ "ar-SA": {
9012
+ previousPageUnavailable:
9013
+ "لم تعد صفحتك السابقة متاحة، لذلك فتحنا صفحة الترحيب.",
9014
+ saveFailed: "تعذر حفظ موضعك",
9015
+ },
9016
+ } satisfies Record<Exclude<LocaleCode, "en-US">, typeof enUS.landing>;
9017
+
8962
9018
  function mergeMessages(overrides: PartialMessages): Messages {
8963
9019
  return {
8964
9020
  root: { ...enUS.root, ...overrides.root },
8965
9021
  theme: { ...enUS.theme, ...overrides.theme },
8966
9022
  navigation: { ...enUS.navigation, ...overrides.navigation },
9023
+ landing: { ...enUS.landing, ...overrides.landing },
8967
9024
  team: { ...enUS.team, ...overrides.team },
8968
9025
  settings: { ...enUS.settings, ...overrides.settings },
8969
9026
  chat: { ...enUS.chat, ...overrides.chat },
@@ -9061,6 +9118,7 @@ function mergeMessagesForLocale(
9061
9118
  return {
9062
9119
  ...base,
9063
9120
  comments: { ...base.comments, ...commentMessagesByLocale[locale] },
9121
+ landing: { ...base.landing, ...landingMessagesByLocale[locale] },
9064
9122
  root: { ...base.root, ...rawLiteralOverrides.root },
9065
9123
  team: { ...base.team, ...rawLiteralOverrides.team },
9066
9124
  settings: { ...base.settings, ...rawLiteralOverrides.settings },
@@ -0,0 +1,22 @@
1
+ import { writeClientAppState } from "@agent-native/core/client/application-state";
2
+ import {
3
+ CONTENT_LAST_LOCATION_STATE_KEY,
4
+ type ContentLastLocationState,
5
+ } from "@shared/content-landing";
6
+
7
+ let landingWriteQueue = Promise.resolve();
8
+
9
+ export function rememberContentLandingDocument(documentId: string) {
10
+ const write = landingWriteQueue.then(() =>
11
+ writeClientAppState<ContentLastLocationState>(
12
+ CONTENT_LAST_LOCATION_STATE_KEY,
13
+ { documentId },
14
+ { requestSource: "content-landing" },
15
+ ),
16
+ );
17
+ landingWriteQueue = write.then(
18
+ () => undefined,
19
+ () => undefined,
20
+ );
21
+ return write;
22
+ }
@@ -212,7 +212,6 @@ export function Layout({ children }: { children: React.ReactNode }) {
212
212
  suppressHydrationWarning
213
213
  dangerouslySetInnerHTML={{ __html: localeInitScript }}
214
214
  />
215
- <link rel="manifest" href={appPath("/manifest.json")} />
216
215
  <meta name="theme-color" content="#10B981" />
217
216
  <meta name="mobile-web-app-capable" content="yes" />
218
217
  <meta
@@ -1,11 +1,12 @@
1
- import type { Document } from "@shared/api";
2
- import { useEffect } from "react";
1
+ import { useActionMutation } from "@agent-native/core/client/hooks";
2
+ import { useT } from "@agent-native/core/client/i18n";
3
+ import type { ContentLandingResult } from "@shared/content-landing";
4
+ import { useCallback, useEffect, useRef } from "react";
3
5
  import { useNavigate } from "react-router";
6
+ import { toast } from "sonner";
4
7
 
5
- import { EmptyState } from "@/components/EmptyState";
6
8
  import { QueryErrorState } from "@/components/QueryErrorState";
7
9
  import { Skeleton } from "@/components/ui/skeleton";
8
- import { useDocuments } from "@/hooks/use-documents";
9
10
 
10
11
  const SEO_TITLE = "Content - Open Source, agent-friendly Obsidian alternative";
11
12
  const SEO_DESCRIPTION =
@@ -46,36 +47,45 @@ function DocumentSkeleton() {
46
47
  }
47
48
 
48
49
  export default function IndexRoute() {
50
+ const t = useT();
49
51
  const navigate = useNavigate();
50
- const documentsQuery = useDocuments();
51
- const documents: Document[] = documentsQuery.data ?? [];
52
- const isLoading = documentsQuery.isLoading;
52
+ const startedRef = useRef(false);
53
+ const resolveLanding = useActionMutation<
54
+ ContentLandingResult,
55
+ Record<string, never>
56
+ >("resolve-content-landing");
53
57
 
54
- // Auto-select the first favorite, or the first document if no favorites
55
- useEffect(() => {
56
- if (documents && documents.length > 0) {
57
- const openableDocuments = documents.filter(
58
- (document) => document.source?.kind !== "folder",
59
- );
60
- const firstFavorite = openableDocuments.find((d) => d.isFavorite);
61
- const target = firstFavorite ?? openableDocuments[0];
62
- if (!target) return;
63
- navigate(`/page/${target.id}`, { replace: true });
58
+ const openLanding = useCallback(async () => {
59
+ if (startedRef.current) return;
60
+ startedRef.current = true;
61
+ try {
62
+ const result = await resolveLanding.mutateAsync({});
63
+ if (result.fallbackReason === "saved-document-unavailable") {
64
+ toast.info(t("landing.previousPageUnavailable"));
65
+ }
66
+ navigate(`/page/${result.documentId}`, { replace: true });
67
+ } catch (error) {
68
+ // Keep the typed mutation error available to QueryErrorState. Retrying
69
+ // starts a fresh resolver attempt rather than pretending arrival worked.
70
+ console.error("Failed to resolve the Content landing page", error);
64
71
  }
65
- }, [documents, navigate]);
72
+ }, [navigate, resolveLanding, t]);
66
73
 
67
- // While loading, or when we have documents but haven't navigated yet,
68
- // show a skeleton instead of the "no page selected" empty state.
69
- const showSkeleton = isLoading || (documents && documents.length > 0);
74
+ useEffect(() => {
75
+ void openLanding();
76
+ }, [openLanding]);
70
77
 
71
- if (showSkeleton) return <DocumentSkeleton />;
72
- if (documentsQuery.isError) {
78
+ if (resolveLanding.isError) {
73
79
  return (
74
80
  <QueryErrorState
75
- onRetry={() => void documentsQuery.refetch()}
76
- retrying={documentsQuery.isFetching}
81
+ onRetry={() => {
82
+ resolveLanding.reset();
83
+ startedRef.current = false;
84
+ void openLanding();
85
+ }}
86
+ retrying={resolveLanding.isPending}
77
87
  />
78
88
  );
79
89
  }
80
- return <EmptyState />;
90
+ return <DocumentSkeleton />;
81
91
  }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-03
4
+ ---
5
+
6
+ Choose Text or a heading from one compact formatting control.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-11
4
+ ---
5
+
6
+ Chrome no longer offers to install Content as a desktop app.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-11
4
+ ---
5
+
6
+ Content opens your last page on return and gives new users a private welcome page.
@@ -42,6 +42,8 @@ If the Page was deleted or access changed, Content opens a safe fallback and exp
42
42
  - Restoration reauthorizes every target before reopening it and drops invalid state safely.
43
43
  - Saved or personal View focus restores when still authorized; ephemeral renderer state is bounded.
44
44
  - Recovery distinguishes normal restoration, missing target, denied target, and unavailable source.
45
+ - At the root route, Content restores the last authorized Page. When no saved Page exists or the target is no longer available, it creates or reuses one private, editable Personal `Welcome to Agent-Native Content` Page.
46
+ - Explicit Page links take precedence over root-route restoration. A fallback does not reveal the unavailable Page's title, preview, or owning context.
45
47
 
46
48
  ## Boundaries and non-goals
47
49
 
@@ -60,6 +62,10 @@ Content reopens that object and View with a usable focus target.
60
62
  Given saved state for an object whose access is revoked, when the session restores, then
61
63
  Content omits the object and does not reveal its title, preview, or prior selection.
62
64
 
65
+ ### Give a new person an honest starting place
66
+
67
+ Given a person with no saved Content location but with organization-visible Pages, when they open the root route, then Content opens one private Personal welcome Page instead of selecting an arbitrary organization Page.
68
+
63
69
  ## Current evidence
64
70
 
65
71
  Persisted navigation donors do not prove reauthorization, fallback, and recovery. This
@@ -73,4 +79,4 @@ Capability remains `approved_shape`.
73
79
 
74
80
  ## Open questions
75
81
 
76
- The exact safe fallback order needs interaction design.
82
+ The first Page-level fallback order is settled. Full saved View, selection, and renderer-state restoration remains to be designed and proven.
@@ -0,0 +1,26 @@
1
+ # Content text selector visual evidence
2
+
3
+ Matched desktop captures for the compact Text/H1-H4 selector in the Content editor.
4
+
5
+ ## Before
6
+
7
+ - Source revision: `dcc028cb7d67a0f9279f9235cdc4c16710ce5689`
8
+ - Route: `http://127.0.0.1:3317/page/j4a5KAEDBHXY`
9
+ - Viewport: `1496 x 846`, desktop Chrome
10
+ - Fixture account: disposable local account `toolbar-demo@example.com`
11
+ - Fixture page: `Toolbar comparison`
12
+ - Fixture paragraph: `Select this sentence to compare the formatting toolbar.`
13
+ - UI state: Personal workspace expanded, agent panel closed, full paragraph selected, formatting bubble open, text-style submenu closed
14
+
15
+ ![Before: separate H1-H4 toolbar buttons](./before.png)
16
+
17
+ ## After
18
+
19
+ - Source: this pull request's production build
20
+ - Route: `http://127.0.0.1:3317/page/vDisteHisXMq`
21
+ - Viewport: `1496 x 846`, desktop Chrome
22
+ - Fixture account, page title, paragraph, sidebar state, and agent-panel state match the before capture
23
+ - UI state: full paragraph selected, formatting bubble open, Text selector expanded, Text checked, H1-H4 visible
24
+ - The route differs because the isolated worktree uses its own disposable local fixture database; the rendered fixture and viewport are matched
25
+
26
+ ![After: compact Text selector with H1-H4 menu](./after.png)
@@ -30,4 +30,5 @@ This generated matrix tracks whether high-value Content UI operations use the sa
30
30
  | source-sync.database-source-bindings | source-sync | Attach, inspect, refresh, disconnect, join, and bind database sources | action-backed | `add-content-database-source-field-property`, `attach-content-database-source`, `bind-content-database-source-field`, `change-content-database-source-role`, `disconnect-content-database-source`, `get-content-database-source`, `list-builder-cms-models`, `list-notion-database-sources`, `preview-content-database-source-attach`, `refresh-content-database-source`, `suggest-source-join-key` | `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/DocumentProperties.tsx`, `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-content-database.ts` | Mounted database source metadata, fields, source role, join keys, and source-field/property bindings are stored and refreshed. | - | - | P0 | covered | `actions/bind-content-database-source-field.db.test.ts`, `actions/content-database-source-actions.test.ts`, `actions/resync-content-database-source.db.test.ts` | `database-source-scope` | - |
31
31
  | source-sync.provider-api-and-staged-datasets | source-sync | Inspect provider APIs and stage/query/delete large provider datasets | action-backed | `delete-staged-dataset`, `list-staged-datasets`, `provider-api-catalog`, `provider-api-docs`, `provider-api-request`, `query-staged-dataset` | `actions/provider-api-catalog.ts`, `actions/provider-api-docs.ts`, `actions/provider-api-request.ts`, `actions/query-staged-dataset.ts` | Provider API metadata and staged dataset scratch storage support scoped agent/source analysis. | - | - | P1 | seeded | - | - | - |
32
32
  | versions.history-and-restore | versions | Open version history and restore a previous document version | action-backed | `list-document-versions`, `restore-document-version` | `app/components/editor/VersionHistoryPanel.tsx`, `app/hooks/use-document-versions.ts` | Document versions are listed and selected versions can restore the document while snapshotting current state. | - | - | P0 | seeded | - | - | - |
33
+ | workspace.root-landing-resolver | workspace | Resolve the app root to the caller's last authorized page or a private welcome page | action-backed | `resolve-content-landing` | `app/routes/_app._index.tsx`, `app/lib/content-landing.ts` | The root route restores the most recent authorized page when possible and otherwise converges on one private personal welcome page while preserving last-location state. | - | - | P0 | covered | `actions/resolve-content-landing.db.test.ts`, `app/lib/content-landing.test.ts` | - | - |
33
34
  | workspace.spaces-and-files-catalog | workspace | Provision, navigate, and delete Content spaces through Files and Workspaces with personal expansion state | action-backed | `backfill-content-files`, `create-content-space`, `delete-content-space`, `ensure-content-spaces`, `get-content-sidebar-state`, `list-content-spaces`, `update-content-sidebar-state` | `app/components/sidebar/DocumentSidebar.tsx`, `app/hooks/use-content-spaces.ts` | Personal and organization spaces, user-created workspaces, their canonical Files databases, the personal Workspaces catalog, and each user's sidebar expansion state are stored and reconciled in SQL; deleting a user-created workspace atomically removes its catalog row and contents. | - | - | P0 | covered | `actions/content-spaces.db.test.ts`, `actions/content-files.db.test.ts`, `actions/content-sidebar-state.test.ts` | - | - |
@@ -95,6 +95,28 @@ export const parityMatrix: ParityRow[] = [
95
95
  testCoverage: "seeded",
96
96
  followUpPR: null,
97
97
  },
98
+ {
99
+ id: "workspace.root-landing-resolver",
100
+ surface: "workspace",
101
+ label:
102
+ "Resolve the app root to the caller's last authorized page or a private welcome page",
103
+ uiEntrypoints: ["app/routes/_app._index.tsx", "app/lib/content-landing.ts"],
104
+ durableEffect:
105
+ "The root route restores the most recent authorized page when possible and otherwise converges on one private personal welcome page while preserving last-location state.",
106
+ uiImplementation:
107
+ "The index route invokes the shared landing resolver on first load, then navigates to the resolved page and records the landing document in application state.",
108
+ status: "action-backed",
109
+ actions: ["resolve-content-landing"],
110
+ exception: null,
111
+ reliabilityRisk: "none",
112
+ spinePriority: "P0",
113
+ testCoverage: "covered",
114
+ followUpPR: null,
115
+ coverageRefs: [
116
+ "actions/resolve-content-landing.db.test.ts",
117
+ "app/lib/content-landing.test.ts",
118
+ ],
119
+ },
98
120
  {
99
121
  id: "sidebar.chrome-state",
100
122
  surface: "sidebar",
@@ -3,7 +3,6 @@
3
3
  "short_name": "Content",
4
4
  "description": "Open-source Obsidian for MDX",
5
5
  "start_url": ".",
6
- "display": "standalone",
7
6
  "background_color": "#111111",
8
7
  "theme_color": "#10B981",
9
8
  "icons": [
@@ -0,0 +1,17 @@
1
+ export const CONTENT_LAST_LOCATION_STATE_KEY = "content-last-location-v1";
2
+
3
+ export type ContentLastLocationState = {
4
+ documentId: string;
5
+ };
6
+
7
+ export type ContentLandingResolution =
8
+ | "restored"
9
+ | "welcome-created"
10
+ | "welcome-reused"
11
+ | "fallback";
12
+
13
+ export type ContentLandingResult = {
14
+ documentId: string;
15
+ resolution: ContentLandingResolution;
16
+ fallbackReason?: "saved-document-unavailable";
17
+ };