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
6 changes: 6 additions & 0 deletions .changeset/shared-console-warnings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@dashfoo/core": minor
"@dashfoo/react": patch
---

Export `warn` and `warnOnce` from `@dashfoo/core`. Both packages now report degraded paths through them, so every developer-console warning carries the same `[dashfoo]` prefix, and a missing tab component warns once per component key across layouts.
5 changes: 3 additions & 2 deletions apps/docs/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { RootProvider } from "fumadocs-ui/provider/next";
import type { Metadata, Viewport } from "next";
import type { ReactNode } from "react";

import { DocsSearchDialog } from "@/components/search-dialog";

export const metadata: Metadata = {
description:
"Documentation for dashfoo: a headless React docking-layout library with tiled, resizable, tabbed regions, a serializable model, and zero imposed styling.",
Expand All @@ -28,8 +30,7 @@ const RootLayout = ({ children }: { children: ReactNode }) => (
aria-hidden="true"
className="reading-progress-bar reading-progress-range fixed top-0 left-0 z-50 h-0.5 w-0 bg-(--primary)"
/>
{/* oxlint-disable-next-line typescript/no-deprecated -- fumadocs still supports the static preset; swapping to a hand-rolled Orama dialog is out of scope for the lint rollout. */}
<RootProvider search={{ options: { type: "static" } }}>{children}</RootProvider>
<RootProvider search={{ SearchDialog: DocsSearchDialog }}>{children}</RootProvider>
</body>
</html>
);
Expand Down
37 changes: 37 additions & 0 deletions apps/docs/components/search-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use client";

import { useDocsSearch } from "fumadocs-core/search/client";
import { staticClient } from "fumadocs-core/search/client/orama-static";
import type { SharedProps } from "fumadocs-ui/components/dialog/search";
import {
SearchDialog,
SearchDialogClose,
SearchDialogContent,
SearchDialogHeader,
SearchDialogIcon,
SearchDialogInput,
SearchDialogList,
SearchDialogOverlay,
} from "fumadocs-ui/components/dialog/search";
import type { ReactNode } from "react";

const client = staticClient();

const DocsSearchDialog = (props: SharedProps): ReactNode => {
const { query, search, setSearch } = useDocsSearch({ client });
return (
<SearchDialog isLoading={query.isLoading} onSearchChange={setSearch} search={search} {...props}>
<SearchDialogOverlay />
<SearchDialogContent>
<SearchDialogHeader>
<SearchDialogIcon />
<SearchDialogInput />
<SearchDialogClose />
</SearchDialogHeader>
<SearchDialogList items={query.data === "empty" ? null : query.data} />
</SearchDialogContent>
</SearchDialog>
);
};

export { DocsSearchDialog };
3 changes: 3 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,9 @@ types `SnapGrid`, `SnapDecision`.
`machines`: `dragDockMachine`, `dropAction`; types `DragState`, `DragSubject`,
`DropResolution`.

`warn`: `warn`, `warnOnce`. Both prefix the message with `[dashfoo]` and write it
with `console.warn`; `@dashfoo/react` reports its degraded paths through them.

## License

MIT
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export * from "./state/reducer";
export * from "./model/schema";
export * from "./model/serialize";
export * from "./model/tree";
export { warn, warnOnce } from "./lib/warn";
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
const warned = new Set<string>();

const warn = (message: string, cause?: unknown): void => {
const text = `[dashfoo] ${message}`;
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn(...(cause === undefined ? [text] : [text, cause]));
};

const warnOnce = (key: string, message: string): void => {
if (warned.has(key)) {
return;
}
warned.add(key);
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn(`[dashfoo] ${message}`);
warn(message);
};

export { warnOnce };
export { warn, warnOnce };
7 changes: 4 additions & 3 deletions packages/core/src/model/builders.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { warn } from "../lib/warn";

import { createNodeId } from "./ids";
import { clampSelected } from "./invariants";
import { DEFAULT_WEIGHT } from "./schema";
Expand Down Expand Up @@ -122,9 +124,8 @@ const model = (layout: RowNode, options: ModelOptions = {}): Dashfoo => {
};
const duplicates = findDuplicateIds(built);
if (duplicates.length > 0) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn(
`[dashfoo] builder produced duplicate node ids: ${duplicates.join(", ")}. Pass explicit ids when reusing a component`,
warn(
`builder produced duplicate node ids: ${duplicates.join(", ")}. Pass explicit ids when reusing a component`,
);
}
return built;
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/model/serialize.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { warn } from "../lib/warn";

import { normalize } from "./invariants";
import type { Dashfoo } from "./schema";
import { dashfooSchema, jsonValueSchema } from "./schema";
Expand All @@ -8,8 +10,7 @@ const parseModel = (value: unknown): Dashfoo => {
const model = normalize(dashfooSchema.parse(value));
const duplicates = findDuplicateIds(model);
if (duplicates.length > 0) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn(`[dashfoo] duplicate node ids in the loaded layout: ${duplicates.join(", ")}`);
warn(`duplicate node ids in the loaded layout: ${duplicates.join(", ")}`);
}
return model;
};
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/state/docking.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { assertNever } from "../lib/assert-never";
import { warn } from "../lib/warn";
import { floatNode, row, tabset } from "../model/builders";
import type { Dashfoo, Geometry, RowNode, TabNode, TabsetNode } from "../model/schema";
import {
Expand Down Expand Up @@ -145,9 +146,8 @@ const addTab = (
{ index, location, tab, targetId }: Extract<DockingAction, { type: "addNode" }>,
): TabsetNode | undefined => {
if (isIdInUse(draft, tab.id)) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn(
`[dashfoo] addNode ignored: node id "${tab.id}" is already in the layout. Pass a fresh id, e.g. createTabId()`,
warn(
`addNode ignored: node id "${tab.id}" is already in the layout. Pass a fresh id, e.g. createTabId()`,
);
return undefined;
}
Expand Down
14 changes: 6 additions & 8 deletions packages/react/src/components/dashfoo-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import type { Action, Dashfoo, DockLocation, SnapConfig, TabNode, TabsetNode } from "@dashfoo/core";
import { findTabset } from "@dashfoo/core";
import { findTabset, warnOnce } from "@dashfoo/core";
import type { ComponentType, ReactNode } from "react";
import { forwardRef, useCallback, useImperativeHandle, useMemo } from "react";

Expand All @@ -14,8 +14,6 @@ import { Layout } from "./layout";

type TabComponent = ComponentType<{ node: TabNode }>;

const warnedComponents = new Set<string>();

type DashfooHandle = {
addTab: (
tab: TabNode,
Expand Down Expand Up @@ -155,11 +153,11 @@ const DashfooLayout = forwardRef<DashfooHandle, DashfooLayoutProps>((props, ref)
}
const Component = components?.[tab.component];
if (!Component) {
if (components && !warnedComponents.has(tab.component)) {
warnedComponents.add(tab.component);

// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn(`[dashfoo] no component registered for "${tab.component}"`);
if (components) {
warnOnce(
`unregistered-component:${tab.component}`,
`no component registered for "${tab.component}"`,
);
}
return null;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/components/floats.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ describe("floating panels", () => {

panel.setPointerCapture = () => {};
panel.releasePointerCapture = () => {};
panel.hasPointerCapture = () => true;

fireEvent.pointerDown(titleBar, { buttons: 1, clientX: 0, clientY: 0, pointerId: 1 });
fireEvent.pointerMove(panel, { buttons: 1, clientX: 40, clientY: 30, pointerId: 1 });
Expand Down Expand Up @@ -240,6 +241,7 @@ describe("floating panels", () => {

panel.setPointerCapture = () => {};
panel.releasePointerCapture = () => {};
panel.hasPointerCapture = () => true;
const titleBar = panel.querySelector('[data-dashfoo="float-titlebar"]')!;
const startLeft = panel.style.getPropertyValue("--dashfoo-float-left");
const startTop = panel.style.getPropertyValue("--dashfoo-float-top");
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/tabset/tabset-store.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client";

import type { Action, TabNode, TabsetNode } from "@dashfoo/core";
import { warnOnce } from "@dashfoo/core";
import { createContext, useContext } from "react";
import type { StoreApi } from "zustand";
import { createStore, useStore } from "zustand";

import { tabDomId } from "../../lib/tab-ids";
import { warnOnce } from "../../lib/warn-once";
import type { OverflowItem } from "../tab-overflow-menu";

type TabsetState = {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/tabset/tabset-tab.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use client";

import type { TabNode } from "@dashfoo/core";
import { warnOnce } from "@dashfoo/core";
import type { ComponentProps, KeyboardEvent, MouseEvent, ReactNode } from "react";
import { forwardRef, useContext, useEffect, useMemo, useRef } from "react";

import { useDragSubject, useTabDraggable } from "../../drag/drag-hooks";
import { useLayout } from "../../hooks/layout-store";
import { mergeRefs } from "../../lib/merge-refs";
import { panelDomId, tabDomId } from "../../lib/tab-ids";
import { warnOnce } from "../../lib/warn-once";

import { TabContext, TabsetStoreContext, useTab, useTabset } from "./tabset-store";

Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/tabset/tabset-toolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"use client";

import { warnOnce } from "@dashfoo/core";
import type { ComponentProps, MouseEvent, ReactNode } from "react";
import { forwardRef, useMemo } from "react";

import { useTabsetDraggable } from "../../drag/drag-hooks";
import { useLayout } from "../../hooks/layout-store";
import { measureFloatRect } from "../../lib/float-geometry";
import { mergeRefs } from "../../lib/merge-refs";
import { warnOnce } from "../../lib/warn-once";
import { FloatIcon, GripIcon, MaximizeIcon } from "../close-icon";
import { useHasFloatLayer } from "../float-context";

Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/drag/drag-hooks.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"use client";

import type { DragSubject, DropIntent, TabNode } from "@dashfoo/core";
import { warnOnce } from "@dashfoo/core";
import { Draggable, Droppable } from "@dnd-kit/dom";
import { useSelector } from "@xstate/react";
import { useCallback, useContext, useEffect, useId, useMemo, useRef } from "react";

import { warnOnce } from "../lib/warn-once";

import { DragContext } from "./drag-context";
import type { DashfooDragManager } from "./drag-manager";
import type { DashfooDragData } from "./drag-subject";
Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/drag/drag-layer.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"use client";

import { resolveDropIntent } from "@dashfoo/core";
import { resolveDropIntent, warnOnce } from "@dashfoo/core";
import type { ReactNode } from "react";
import { useContext, useEffect, useId, useMemo } from "react";

import type { LayoutStore } from "../hooks/layout-store";
import { LayoutStoreContext } from "../hooks/layout-store";
import { warnOnce } from "../lib/warn-once";

import { DockIndicator } from "./dock-indicator";
import type { DragContextValue, RegisteredLayer } from "./drag-context";
Expand Down
11 changes: 4 additions & 7 deletions packages/react/src/drag/drag-subject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DragSubject, TabNode } from "@dashfoo/core";
import { tabNodeSchema } from "@dashfoo/core";
import { tabNodeSchema, warn } from "@dashfoo/core";
import type { Data } from "@dnd-kit/abstract";
import type { Draggable } from "@dnd-kit/dom";

Expand All @@ -20,22 +20,19 @@ const subjectFor = (source: DragSource): DragSubject | null => {
}
if (data.type === "external") {
if (!isTabFactory(data.createTab)) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn("[dashfoo] external drag source is missing its createTab function");
warn("external drag source is missing its createTab function");
return null;
}
let candidate: ReturnType<typeof data.createTab>;
try {
candidate = data.createTab();
} catch (error) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn("[dashfoo] external drag source createTab threw", error);
warn("external drag source createTab threw", error);
return null;
}
const parsed = tabNodeSchema.safeParse(candidate);
if (!parsed.success) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn("[dashfoo] external drag source returned an invalid tab", parsed.error);
warn("external drag source returned an invalid tab", parsed.error);
return null;
}
return { id: String(source.id), kind: "external", tab: parsed.data };
Expand Down
28 changes: 10 additions & 18 deletions packages/react/src/hooks/persistence.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import type { Dashfoo } from "@dashfoo/core";
import { fromJSON, toJSON } from "@dashfoo/core";
import { fromJSON, toJSON, warn } from "@dashfoo/core";
import { useCallback, useEffect, useMemo, useRef } from "react";

type StorageAdapter = {
Expand Down Expand Up @@ -31,8 +31,7 @@ const localStorageAdapter: StorageAdapter = {
try {
return window.localStorage.getItem(key);
} catch (error) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn("[dashfoo] failed to load persisted layout", error);
warn("failed to load persisted layout", error);
return null;
}
},
Expand All @@ -43,8 +42,7 @@ const localStorageAdapter: StorageAdapter = {
try {
window.localStorage.removeItem(key);
} catch (error) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn("[dashfoo] failed to clear persisted layout", error);
warn("failed to clear persisted layout", error);
}
},
setItem: (key, value) => {
Expand All @@ -54,8 +52,7 @@ const localStorageAdapter: StorageAdapter = {
try {
window.localStorage.setItem(key, value);
} catch (error) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn("[dashfoo] failed to persist layout", error);
warn("failed to persist layout", error);
}
},
};
Expand Down Expand Up @@ -89,8 +86,7 @@ const removeStoredLayout = ({ key, storage }: PersistTarget): void => {
try {
storage.removeItem(key);
} catch (error) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn("[dashfoo] failed to clear persisted layout", error);
warn("failed to clear persisted layout", error);
}
};

Expand All @@ -103,14 +99,12 @@ const loadStoredLayout = (target: PersistTarget): Dashfoo | undefined => {
try {
return fromJSON(raw);
} catch (error) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn("[dashfoo] discarding unreadable persisted layout", error);
warn("discarding unreadable persisted layout", error);
removeStoredLayout(target);
return undefined;
}
} catch (error) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn("[dashfoo] failed to load persisted layout", error);
warn("failed to load persisted layout", error);
return undefined;
}
};
Expand Down Expand Up @@ -152,9 +146,8 @@ const useLayoutStorage = (
const loadedKey = useRef(key);
useEffect(() => {
if (key !== undefined && key !== loadedKey.current) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn(
"[dashfoo] persist key changed for a mounted layout; the displayed layout still reflects the previous key; remount (key={persistKey}) to load the new key",
warn(
"persist key changed for a mounted layout; the displayed layout still reflects the previous key; remount (key={persistKey}) to load the new key",
);
loadedKey.current = key;
}
Expand All @@ -175,8 +168,7 @@ const useLayoutStorage = (
try {
write.storage.setItem(write.key, write.value);
} catch (error) {
// oxlint-disable-next-line no-console -- dashfoo reports degraded paths on the developer console instead of failing silently
console.warn("[dashfoo] failed to persist layout", error);
warn("failed to persist layout", error);
}
}
}, []);
Expand Down
Loading
Loading