Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,55 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"automerge": true,
"automergeType": "pr",
"branchConcurrentLimit": 4,
"extends": ["config:base"],

"lockFileMaintenance": {
"automerge": true,
"enabled": true,
"schedule": ["after 9pm on Sunday"]
},

"npm": {
"fileMatch": ["(^|/)package\\.json$", "(^|/)package\\.json\\.hbs$"]
},

"packageRules": [
{
"enabled": false,
"matchPackagePatterns": ["^@acme/"]
},
{
"automerge": true,
"groupName": "all non-major dependencies",
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"stabilityDays": 7
},
{
"automerge": true,
"groupName": "dev tooling (weekly batch)",
"matchDepTypes": ["devDependencies"],
"stabilityDays": 7
},
{
"automerge": false,
"groupName": "major upgrades (manual)",
"matchUpdateTypes": ["major"]
},
{
"groupName": "TS/ESLint toolchain",
"matchPackagePatterns": ["^typescript$", "^eslint", "^@types/"],
"stabilityDays": 3
}
],
"prConcurrentLimit": 4,

"prHourlyLimit": 2,
"rangeStrategy": "bump",
"rebaseWhen": "never",

"schedule": ["after 9pm on Sunday"],
"timezone": "America/New_York",
"updateInternalDeps": true
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ export default function ChatSidebarTrigger() {
data-slot="sidebar-trigger"
onClick={toggleSidebar}
size="icon"
variant="ghost"
className="fixed right-2 bottom-2 hidden size-12 cursor-pointer rounded-full border bg-sidebar md:flex"
className="fixed right-2 bottom-2 hidden size-10 cursor-pointer rounded-full border md:flex"
>
<Brain className="size-6" />
<span className="sr-only">Toggle Chat Sidebar</span>
Expand Down
57 changes: 39 additions & 18 deletions apps/web/src/app/(app)/journal/_components/journal-entry-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import {
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useDebouncedCallback } from "use-debounce";
import { useJournlAgentAwareness } from "~/ai/agents/use-journl-agent-awareness";
import { BlockEditor } from "~/components/editor/block-editor";
import { BlockEditorErrorOverlay } from "~/components/editor/block-editor-error-overlay";
import {
BlockEditorFormattingToolbar,
BlockEditorSlashMenu,
Expand Down Expand Up @@ -153,30 +155,46 @@ export function JournalEntryContent({

type JournalEntryEditorProps = {
debounceTime?: number;
onCreate?: (newEntry: TimelineEntry) => void;
onCreateAction?: (newEntry: TimelineEntry) => void;
};

export function JournalEntryEditor({
debounceTime = DEFAULT_DEBOUNCE_TIME,
onCreate,
onCreateAction,
}: JournalEntryEditorProps) {
const trpc = useTRPC();
const pendingChangesRef = useRef<BlockTransaction[]>([]);
const { initialBlocks, documentId, date } = useJournalEntry();
const { rememberEditor, forgetEditor } = useJournlAgentAwareness();
const editor = useBlockEditor({ initialBlocks });
const [isOverlayOpen, setOverlayOpen] = useState(false);

/**
* Handles the error state of the editor.
*
* @privateRemarks
*
* Using replace() to avoid creating a new history entry
*/
function handleError() {
setOverlayOpen(true);
requestAnimationFrame(() => {
location.replace(location.href);
});
}

const { mutate, isPending } = useMutation({
...trpc.journal.saveTransactions.mutationOptions({}),
// ! TODO: When the mutation fails we need to revert the changes to the editor just like Notion does.
// ! To do this we can use `onError` and `editor.undo()`, without calling the transactions. We might have to get creative.
// ! Maybe we can refetch the blocks after an error instead of `undo`?
onError: (error) => {
console.error("[JournalEntryEditor] error 👀", error);
handleError();
},
onSuccess: (data) => {
if (pendingChangesRef.current.length > 0) {
debouncedMutate();
}
if (!documentId && data) {
onCreate?.(data);
onCreateAction?.(data);
}
},
});
Expand All @@ -202,18 +220,21 @@ export function JournalEntryEditor({
}, [date, editor, rememberEditor, forgetEditor]);

return (
<BlockEditor
editor={editor}
initialBlocks={initialBlocks}
onChange={handleEditorChange}
// Disabling the default because we're using a formatting toolbar with the AI option.
formattingToolbar={false}
// Disabling the default because we're using a slash menu with the AI option.
slashMenu={false}
>
<BlockEditorFormattingToolbar />
<BlockEditorSlashMenu />
</BlockEditor>
<>
<BlockEditor
editor={editor}
initialBlocks={initialBlocks}
onChange={handleEditorChange}
// Disabling the default because we're using a formatting toolbar with the AI option.
formattingToolbar={false}
// Disabling the default because we're using a slash menu with the AI option.
slashMenu={false}
>
<BlockEditorFormattingToolbar />
<BlockEditorSlashMenu />
</BlockEditor>
<BlockEditorErrorOverlay isOpen={isOverlayOpen} />
</>
);
}

Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/app/(app)/journal/_components/journal-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { useJournlAgentAwareness } from "~/ai/agents/use-journl-agent-awareness"
import { useTRPC } from "~/trpc/react";
import {
JournalEntryContent,
JournalEntryEditor,
JournalEntryHeader,
JournalEntryLink,
JournalEntryProvider,
JournalEntryWrapper,
} from "./journal-entry-editor";
import { DynamicJournalEntryEditor } from "./journal-entry-editor.dynamic";
import { JournalEntryLoader } from "./journal-entry-loader";
import { JournalListSkeleton } from "./journal-list-skeleton";

Expand Down Expand Up @@ -108,8 +108,8 @@ export function JournalList({
<JournalEntryHeader className="px-13.5" />
</JournalEntryLink>
<JournalEntryContent>
<DynamicJournalEntryEditor
onCreate={(newEntry) => {
<JournalEntryEditor
onCreateAction={(newEntry) => {
queryClient.setQueryData(queryOptions.queryKey, (old) => ({
...old,
pageParams: [...(old?.pageParams ?? [])],
Expand Down
52 changes: 36 additions & 16 deletions apps/web/src/app/(app)/pages/_components/page-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import type { BlockTransaction } from "@acme/api";
import type { Page } from "@acme/db/schema";
import type { PartialBlock } from "@blocknote/core";
import { useMutation } from "@tanstack/react-query";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import { useJournlAgentAwareness } from "~/ai/agents/use-journl-agent-awareness";
import { BlockEditor } from "~/components/editor/block-editor";
import { BlockEditorErrorOverlay } from "~/components/editor/block-editor-error-overlay";
import {
BlockEditorFormattingToolbar,
BlockEditorSlashMenu,
Expand Down Expand Up @@ -35,12 +36,28 @@ export function PageEditor({
const { rememberEditor, forgetEditor, rememberView } =
useJournlAgentAwareness();
const editor = useBlockEditor({ initialBlocks });
const [isOverlayOpen, setOverlayOpen] = useState(false);

/**
* Handles the error state of the editor.
*
* @privateRemarks
*
* Using replace() to avoid creating a new history entry
*/
function handleError() {
setOverlayOpen(true);
requestAnimationFrame(() => {
location.replace(location.href);
});
}

const { mutate, isPending } = useMutation({
...trpc.pages.saveTransactions.mutationOptions({}),
// ! TODO: When the mutation fails we need to revert the changes to the editor just like Notion does.
// ! To do this we can use `onError` and `editor.undo()`, without calling the transactions. We might have to get creative.
// ! Maybe we can refetch the blocks after an error instead of `undo`?
onError: (error) => {
console.error("[PageEditor] error 👀", error);
handleError();
},
onSuccess: () => {
if (pendingChangesRef.current.length > 0) {
debouncedMutate();
Expand Down Expand Up @@ -93,17 +110,20 @@ export function PageEditor({
);

return (
<BlockEditor
editor={editor}
initialBlocks={initialBlocks}
onChange={handleEditorChange}
// Disabling the default because we're using a formatting toolbar with the AI option.
formattingToolbar={false}
// Disabling the default because we're using a slash menu with the AI option.
slashMenu={false}
>
<BlockEditorFormattingToolbar />
<BlockEditorSlashMenu />
</BlockEditor>
<>
<BlockEditor
editor={editor}
initialBlocks={initialBlocks}
onChange={handleEditorChange}
// Disabling the default because we're using a formatting toolbar with the AI option.
formattingToolbar={false}
// Disabling the default because we're using a slash menu with the AI option.
slashMenu={false}
>
<BlockEditorFormattingToolbar />
<BlockEditorSlashMenu />
</BlockEditor>
<BlockEditorErrorOverlay isOpen={isOverlayOpen} />
</>
);
}
25 changes: 25 additions & 0 deletions apps/web/src/components/editor/block-editor-error-overlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
DocumentOverlay,
DocumentOverlayContent,
} from "../ui/document-overlay";

type BlockEditorErrorOverlayProps = {
isOpen: boolean;
};

export function BlockEditorErrorOverlay({
isOpen,
}: BlockEditorErrorOverlayProps) {
return (
<DocumentOverlay
className="bg-background/30 backdrop-blur-md"
isOpen={isOpen}
>
<DocumentOverlayContent className="bg-background/70">
<span className="block font-medium text-muted-foreground text-sm">
Something went wrong while saving your changes.
</span>
</DocumentOverlayContent>
</DocumentOverlay>
);
}
7 changes: 4 additions & 3 deletions apps/web/src/components/editor/block-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export function BlockEditor({
* @privateRemarks
*
* The biggest challenge is the computation of the edges rather than the blocks.
*
* An intuitive approach is to compute the edges for the previous state and the current state and then compute the diff.
* For example, a brute force approach would be to loop over all blocks and detect if their adjacent blocks are different.
* If they are we need to remove the previous edge and insert the new edge.
Expand Down Expand Up @@ -115,7 +116,7 @@ export function BlockEditor({
* - Moves are more complex than inserts and deletes because we need to compute edges for the blocks that moved and the blocks that are adjacent to the moved blocks (assuming they didn't move).
*/
function handleEditorChange(currentEditor: EditorPrimitiveOnChangeParams[0]) {
if (!onChange || isEditorAgentProcessing(currentEditor)) return;
if (!onChange || isAgenticEditorChange(currentEditor)) return;

const oldBlocks = getEditorBlocks(previousEditorRef.current);
const currentBlocks = getEditorBlocks(currentEditor.document);
Expand Down Expand Up @@ -259,7 +260,7 @@ export function BlockEditor({

// Leaving this here for debugging purposes because this logic is the wild west.
if (debug) {
console.debug("saveTransactions 👀", {
console.debug("[BlockEditor] transactions 👀", {
transactions: transactions.map((t) =>
t.type === "block_remove" || t.type === "block_upsert"
? {
Expand Down Expand Up @@ -338,7 +339,7 @@ function getEditorBlocks(blocks: BlockPrimitive[], parent?: BlockPrimitive) {
return flattened;
}

function isEditorAgentProcessing(editor: EditorPrimitive) {
function isAgenticEditorChange(editor: EditorPrimitive) {
const aiExtension = getAIExtension(editor);
const state = aiExtension.store.getState().aiMenuState;

Expand Down
48 changes: 48 additions & 0 deletions apps/web/src/components/ui/document-overlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { ComponentProps } from "react";
import { createPortal } from "react-dom";
import { cn } from "../utils";

type DocumentOverlayProps = ComponentProps<"div"> & {
isOpen: boolean;
};

export function DocumentOverlay({
isOpen,
children,
className,
...rest
}: DocumentOverlayProps) {
if (!isOpen) return null;
return createPortal(
<div
aria-live="polite"
aria-busy="true"
className={cn(
"fixed inset-0 z-[9999] flex items-center justify-center",
className,
)}
{...rest}
>
{children}
</div>,
document.body,
);
}

export function DocumentOverlayContent({
children,
className,
...rest
}: ComponentProps<"div">) {
return (
<div
className={cn(
"rounded-2xl border border-border px-5 py-3 shadow-lg",
className,
)}
{...rest}
>
{children}
</div>
);
}
1 change: 1 addition & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ languageSettings:
allowCompoundWords: false
useGitignore: true
words:
- Agentic
- AISDK
- autohide
- CLIENTVAR
Expand Down
Loading
Loading