Skip to content

Commit 14b6937

Browse files
committed
feat: improve phase 6
1 parent bb4b933 commit 14b6937

14 files changed

Lines changed: 516 additions & 171 deletions

docs/restart-plan/README.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# DeepNotes Restart Plan — Index
22

3-
> **Last updated:** 2026-05-31 (Phase 8 complete; marketing site builds 20 static HTML routes with vue-router + vite-ssg, theme toggle, restored legacy assets. **Phase 6 status corrected: spatial canvas polish is NOT complete.**)
3+
> **Last updated:** 2026-05-31 (Phase 8 complete. Phase 6 state screens + layout cleanup done; sidebars, toolbar, and floating UI exist. Remaining: TableContextMenu, Note/Arrow properties panels, Teleport overlay, horizontal containers, arrow labels, drag-to-reconnect.)
44
> **This document replaces `docs/RESTART_PLAN.md`.** If a prior statement conflicts with this one, this version wins.
55
66
---
@@ -75,16 +75,15 @@ A criterion is **not met** until the verification command or check passes in CI.
7575

7676
## Current gaps (high-level)
7777

78-
### Phase 6 — Spatial canvas polish (NOT complete)
78+
### Phase 6 — Spatial canvas polish (in progress)
7979

80-
- **Page editor layout is a card stack, not an immersive shell.** `PageEditorView.vue` is rendered inside `DefaultLayout.vue` (`max-w-3xl` centered column). It must become a fullscreen `q-layout`-style shell with sidebars, toolbar, and floating overlays. No sidebars, no toolbar, no floating buttons exist today.
81-
- **Left sidebar missing.** No `CurrentPath`, `RecentPages`, `FavoritePages`, or `SelectedPages` panels on `/pages/:pageId`.
82-
- **Right sidebar missing.** No `NoteProperties` (24 files in legacy), `PageProperties`, or `ArrowProperties` panels. Context-aware editing is impossible.
83-
- **Toolbar missing.** No `MainToolbar` with Basic/Formatting/Object/Alignment buttons, insert dialogs, or page-specific actions.
84-
- **Floating UI overlays missing.** No zoom indicator, undo/redo buttons, back/forward nav, screenshot, find/replace toggle, selection count, or user avatars on the canvas.
85-
- **Arrow rendering is a stub.** `DisplayArrow.vue` draws a single SVG line between note centers. Missing: curve/line body styles, arrow heads (`OpenHead`), labels (TipTap), hitboxes, drag-to-reconnect, interregional logic, anchor positioning.
86-
- **Note rendering is partial.** `DisplayNote.vue` has basic drag, resize, and head/body editors. Missing: Teleport overlay during drag, drop zones, arrow link zones, link icon, read-only opacity states, scroll handling, and advanced visual parity.
87-
- **Page state screens missing.** No dedicated `DisplayErrorScreen`, `DisplayNonExistentScreen`, `DisplayPageDeletedScreen`, etc. Inline error cards only.
80+
- **Left sidebar sections missing.** `CurrentPath` exists; `RecentPages`, `FavoritePages`, `SelectedPages` panels pending.
81+
- **Right sidebar properties missing.** Snapshots, management, backlinks exist. `NoteProperties` (24 files in legacy), `PageProperties`, `ArrowProperties` panels pending.
82+
- **Toolbar page actions missing.** `MainToolbar` shell exists with logo, breadcrumb, global nav. Missing: Basic/Formatting/Object/Alignment buttons, insert dialogs, page-specific actions.
83+
- **Floating UI overlays partial.** Zoom indicator, undo/redo, selection count exist. Missing: back/forward nav, screenshot, find/replace toggle, user avatars on canvas.
84+
- **Arrow rendering partial.** Curve + line bodies, arrow heads, hitboxes exist. Missing: labels (TipTap), drag-to-reconnect, interregional logic, anchor positioning.
85+
- **Note rendering partial.** Drag, resize, head/body editors, link icon exist. Missing: Teleport overlay during drag, drop zones, arrow link zones, read-only opacity states, scroll handling.
86+
- **Page state screens done.** `PageStateScreens.vue` switcher + 8 components created and wired. Some states (`page-deleted`, `group-deleted`, `invited`, `rejected`) require richer API error codes to be fully distinguishable.
8887
- **Context menu missing.** No `TableContextMenu` (right-click on canvas).
8988

9089
### Other gaps

docs/restart-plan/phase-6-spatial-polish.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ Achieve parity with the legacy `/pages/:pageId` immersive spatial canvas experie
5555
| `LeftSidebar` (shadcn) — Recent, Favorites, Selected, Current path | **Partial** | Resizable collapsible sidebar with path + collab status; Recent/Favorites/Selected sections pending |
5656
| `RightSidebar` (shadcn) — Note/Page/Arrow properties | **Partial** | Collapsible sidebar with snapshots, management, backlinks; Note/Arrow properties pending |
5757
| `TableContextMenu` (shadcn) — right-click on canvas | **Not started** | No context menu on canvas |
58-
| `LoadingOverlay` during page bootstrap | **Partial** | Inline loading text in cards only |
59-
| Global CSS for spatial routes (`user-select: none`, `overflow: hidden`, `touch-action: none`) | **Not started** | `DefaultLayout.vue` does the opposite |
60-
| Remove `PageEditorTiptapCard.vue` from page route | **Not started** | Still rendered at bottom of card stack |
61-
| Dedicated fullscreen state screens (8 total) | **Not started** | No `DisplayErrorScreen`, `DisplayNonExistentScreen`, etc. |
58+
| `LoadingOverlay` during page bootstrap | **Partial** | Inline loading text in cards only; state screens handle loading/error |
59+
| Global CSS for spatial routes (`user-select: none`, `overflow: hidden`, `touch-action: none`) | **Done** | `PageLayout.vue` applies `select-none overflow-hidden` on the shell |
60+
| Remove `PageEditorTiptapCard.vue` from page route | **Done** | File deleted; no longer imported or rendered |
61+
| Dedicated fullscreen state screens (8 total) | **Done** | `PageStateScreens.vue` switcher + 8 components in `features/pages/screens/`. Detectable states: `loading`, `error`, `page-nonexistent`, `unauthorized`, `password`. `page-deleted`, `group-deleted`, `invited`, `rejected` require richer API error codes to distinguish. |
6262

6363
### 7. Note visual parity (legacy style, no Quasar)
6464
| Item | Status | Notes |

new-deepnotes/apps/web/src/features/pages/PageEditorTiptapCard.vue

Lines changed: 0 additions & 156 deletions
This file was deleted.

new-deepnotes/apps/web/src/features/pages/PageEditorView.vue

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { usePageManagement } from "./usePageManagement";
2323
import { usePagePathAndPrefs } from "./usePagePathAndPrefs";
2424
import { usePagePathRealtimeTitles } from "./usePagePathRealtimeTitles";
2525
import { usePageSnapshots } from "./usePageSnapshots";
26+
import PageStateScreens from "./screens/PageStateScreens.vue";
27+
import { usePageStatus } from "./usePageStatus";
2628
2729
import type { SnapshotRow } from "./page-snapshot-list";
2830
@@ -88,6 +90,13 @@ const {
8890
unlockKeyringWithPassword,
8991
} = collab;
9092
93+
const { status: pageStatus } = usePageStatus({
94+
collabLoading,
95+
loadError,
96+
cryptoError,
97+
pageId,
98+
});
99+
91100
const {
92101
pathPageIds,
93102
pathError,
@@ -152,11 +161,12 @@ const {
152161
saveSnapshotManual,
153162
} = snapshotsApi;
154163
155-
async function onUnlockWithPassword(password: string) {
164+
async function onUnlockWithPassword(password: string): Promise<boolean> {
156165
const ok = await unlockKeyringWithPassword(password);
157166
if (ok) {
158167
collabReloadNonce.value++;
159168
}
169+
return ok;
160170
}
161171
162172
onMounted(() => {
@@ -174,7 +184,18 @@ onMounted(() => {
174184

175185
<template v-else>
176186
<!-- === Main canvas slot === -->
187+
<PageStateScreens
188+
v-if="pageStatus !== 'success'"
189+
:status="pageStatus"
190+
:page-id="pageId"
191+
:group-id="collabGroupId"
192+
:client="client"
193+
:load-error="loadError"
194+
:crypto-error="cryptoError"
195+
:on-unlock-password="onUnlockWithPassword"
196+
/>
177197
<SpatialPageView
198+
v-else
178199
:ydoc="ydoc"
179200
:default-note-template="noteTemplate"
180201
:default-arrow-template="arrowTemplate"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
message?: string;
4+
}>();
5+
</script>
6+
7+
<template>
8+
<div class="flex flex-col items-center justify-center gap-3 text-center">
9+
<div class="text-destructive text-lg font-semibold">Error</div>
10+
<p class="text-muted-foreground max-w-md text-sm">
11+
{{ message || "An unexpected error occurred while loading this page." }}
12+
</p>
13+
</div>
14+
</template>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<script setup lang="ts">
2+
import { ref } from "vue";
3+
4+
import { Button } from "@/components/ui/button";
5+
import type { DeepnotesApiClient } from "@/api/client";
6+
7+
const props = defineProps<{
8+
groupId: string;
9+
client: DeepnotesApiClient;
10+
}>();
11+
12+
const restoring = ref(false);
13+
const purging = ref(false);
14+
const message = ref<string | null>(null);
15+
16+
async function restoreGroup() {
17+
restoring.value = true;
18+
message.value = null;
19+
try {
20+
const { error } = await props.client.POST("/api/groups/{groupId}/restore", {
21+
params: { path: { groupId: props.groupId } },
22+
});
23+
if (error) {
24+
message.value =
25+
typeof error === "object" && "message" in error
26+
? String(error.message)
27+
: "Failed to restore group.";
28+
} else {
29+
message.value = "Group restored. Reloading…";
30+
location.reload();
31+
}
32+
} catch (e) {
33+
message.value = e instanceof Error ? e.message : "Failed to restore group.";
34+
} finally {
35+
restoring.value = false;
36+
}
37+
}
38+
39+
async function purgeGroup() {
40+
if (!confirm("Are you sure you want to delete this group permanently?")) return;
41+
purging.value = true;
42+
message.value = null;
43+
try {
44+
const { error } = await props.client.POST("/api/groups/{groupId}/purge", {
45+
params: { path: { groupId: props.groupId } },
46+
});
47+
if (error) {
48+
message.value =
49+
typeof error === "object" && "message" in error
50+
? String(error.message)
51+
: "Failed to purge group.";
52+
} else {
53+
message.value = "Group deleted permanently.";
54+
}
55+
} catch (e) {
56+
message.value = e instanceof Error ? e.message : "Failed to purge group.";
57+
} finally {
58+
purging.value = false;
59+
}
60+
}
61+
</script>
62+
63+
<template>
64+
<div class="flex flex-col items-center justify-center gap-3 text-center">
65+
<div class="text-foreground text-lg font-semibold">Group deleted</div>
66+
<p class="text-muted-foreground max-w-md text-sm">
67+
This group has been deleted.
68+
</p>
69+
70+
<div class="flex flex-wrap justify-center gap-2">
71+
<Button
72+
variant="secondary"
73+
:disabled="restoring || purging"
74+
@click="restoreGroup"
75+
>
76+
{{ restoring ? "Restoring…" : "Restore group" }}
77+
</Button>
78+
<Button
79+
variant="destructive"
80+
:disabled="restoring || purging"
81+
@click="purgeGroup"
82+
>
83+
{{ purging ? "Purging…" : "Delete permanently" }}
84+
</Button>
85+
</div>
86+
87+
<p v-if="message" class="text-muted-foreground text-xs">
88+
{{ message }}
89+
</p>
90+
</div>
91+
</template>

0 commit comments

Comments
 (0)