diff --git a/typescript/README.md b/typescript/README.md index ce636dd..8990c24 100644 --- a/typescript/README.md +++ b/typescript/README.md @@ -274,11 +274,12 @@ b7b30c1 : Make more typesafe You can choose a different version from the list or create new one. But bumpp is smart enough to use appropriate next version. 3. Verify and confirm -4. Push commit and tag +4. Push commit and tag ``` git push --follow-tags ``` + 5. The new tag will trigger a release on github actions. 6. Go to github and create release using the new tag. Make sure you set the correct previous tag prefixed with `typescript-v` @@ -317,6 +318,7 @@ git push --follow-tags - [ ] validate page prop somehow. not clear how to do yet. we can't use the /schema because it's HUGE and also because it's outside of /typescript. Will need to think about this. maybe do something like this? + ``` import type { Page } from "../models/generated/page/page"; import { isPage } from "../models/generated/essential-types"; @@ -353,4 +355,4 @@ export function validatePageWithError(obj: unknown): { will require us to write a validator and we won't benefit from the defined schema jsons. -- [ ] make error boundary catch errors in JsonDocRenderer.tsx body \ No newline at end of file +- [ ] make error boundary catch errors in JsonDocRenderer.tsx body diff --git a/typescript/eslint.config.js b/typescript/eslint.config.js index a313898..a958d87 100644 --- a/typescript/eslint.config.js +++ b/typescript/eslint.config.js @@ -48,7 +48,11 @@ module.exports = [ // "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-unused-vars": [ "warn", - { argsIgnorePattern: "^_" }, + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, ], // React rules @@ -97,9 +101,7 @@ module.exports = [ "coverage/**", ".jest/**", "scripts/**", - "tests/**", "**/generated/**", - "src/serialization/**", ], }, ]; diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 9e6113a..784bab3 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -46,6 +46,7 @@ "json-schema-to-typescript": "^15.0.4", "json5": "^2.2.3", "lint-staged": "^15.5.2", + "nodemon": "^3.1.10", "prettier": "3.5.3", "puppeteer": "^24.9.0", "size-limit": "^11.2.0", @@ -2949,6 +2950,20 @@ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "dev": true }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -3295,6 +3310,19 @@ "node": ">=10.0.0" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5784,6 +5812,13 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -5924,6 +5959,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", @@ -7373,6 +7421,132 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/nodemon/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nwsapi": { "version": "2.2.20", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", @@ -7989,6 +8163,13 @@ "dev": true, "license": "MIT" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -8660,6 +8841,32 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sirv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", @@ -9362,6 +9569,16 @@ "node": ">=6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tough-cookie": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", @@ -9690,6 +9907,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", diff --git a/typescript/package.json b/typescript/package.json index 190db47..d4d014d 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -9,9 +9,9 @@ "clean": "rm -rf dist", "build": "tsup", "build:watch": "tsup --watch", - "dev": "concurrently \"npm run build:watch\" \"npm run lint:watch\" \"npm run type-check:watch\"", - "type-check:watch": "tsc --noEmit --watch --pretty", - "lint:watch": "eslint src --ext .ts,.tsx --cache", + "dev": "concurrently \"npm run build:watch\" \"npm run lint:watch\" \"npm run check:watch\"", + "check:watch": "tsc --noEmit --watch --pretty", + "lint:watch": "nodemon --watch src --ext ts,tsx --exec \"eslint src --ext .ts,.tsx\"", "generate-types": "ts-node scripts/generate-types.ts", "test": "vitest", "test:run": "vitest run", @@ -62,6 +62,7 @@ "json-schema-to-typescript": "^15.0.4", "json5": "^2.2.3", "lint-staged": "^15.5.2", + "nodemon": "^3.1.10", "prettier": "3.5.3", "puppeteer": "^24.9.0", "size-limit": "^11.2.0", diff --git a/typescript/src/models/generated/block/types/equation/equation.ts b/typescript/src/models/generated/block/types/equation/equation.ts index 1c232c0..b4c6942 100644 --- a/typescript/src/models/generated/block/types/equation/equation.ts +++ b/typescript/src/models/generated/block/types/equation/equation.ts @@ -1,6 +1,6 @@ -import type { Block } from "../../block"; +import type { BlockBase } from "../../base"; -export type EquationBlock = Block & { +export type EquationBlock = BlockBase & { type: "equation"; equation: { expression: string; diff --git a/typescript/src/renderer/JsonDocRenderer.tsx b/typescript/src/renderer/JsonDocRenderer.tsx index 338e830..c9da51b 100644 --- a/typescript/src/renderer/JsonDocRenderer.tsx +++ b/typescript/src/renderer/JsonDocRenderer.tsx @@ -1,8 +1,8 @@ import "./styles/index.css"; -import React from "react"; +import React, { useEffect } from "react"; import { Page } from "@/models/generated"; -// import { validateAgainstSchema } from "@/validation/validator"; +import { loadPage } from "@/serialization/loader"; import { BlockRenderer } from "./components/BlockRenderer"; import { PageDelimiter } from "./components/PageDelimiter"; @@ -46,6 +46,15 @@ export const JsonDocRenderer = ({ backrefs, }); + useEffect(() => { + try { + //TODO: this is not throwing for invalid page object (one that doesn't follow schema) + loadPage(page); + } catch (_) { + // console.log("error ", error); + } + }, [page]); + // return null; const renderedContent = (
@@ -100,10 +109,15 @@ export const JsonDocRenderer = ({ ); return ( -
+
-
+
{viewJson ? (
diff --git a/typescript/src/renderer/components/BlockRenderer.tsx b/typescript/src/renderer/components/BlockRenderer.tsx index 5e707fb..3371a6e 100644 --- a/typescript/src/renderer/components/BlockRenderer.tsx +++ b/typescript/src/renderer/components/BlockRenderer.tsx @@ -225,8 +225,12 @@ export const BlockRenderer: React.FC = ({ // Fallback for unsupported block types console.warn("Unsupported block type:", block?.type); return ( -
- Unsupported block type: {block?.type} +
+ Error Unsupported block type: {block?.type}
); }; diff --git a/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx b/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx index d798ba6..2a779a5 100644 --- a/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx @@ -23,11 +23,11 @@ export const EquationBlockRenderer: React.FC = ({ katex.render(equationData.expression, containerRef.current, { displayMode: true, throwOnError: false, - errorColor: '#cc0000', - strict: 'warn' + errorColor: "#cc0000", + strict: "warn", }); } catch (error) { - console.warn('KaTeX render error:', error); + console.warn("KaTeX render error:", error); if (containerRef.current) { containerRef.current.textContent = equationData.expression; } @@ -43,10 +43,7 @@ export const EquationBlockRenderer: React.FC = ({ >
-
+
diff --git a/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx index b26f70a..c34cbc5 100644 --- a/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx @@ -89,7 +89,11 @@ export const ImageBlockRenderer: React.FC = ({ }, [inView, imageUrl, resolveImageUrl]); useEffect(() => { - if (captionRef.current && imageData?.caption && imageData.caption.length > 0) { + if ( + captionRef.current && + imageData?.caption && + imageData.caption.length > 0 + ) { const element = captionRef.current; const isOverflowing = element.scrollHeight > element.clientHeight; setNeedsTruncation(isOverflowing); @@ -149,14 +153,21 @@ export const ImageBlockRenderer: React.FC = ({ {showFullCaption ? "Show less" : "Show more"} - - + )} diff --git a/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx index efffbd0..1ad3089 100644 --- a/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx @@ -30,7 +30,7 @@ export const ToDoBlockRenderer: React.FC = ({
-
+
diff --git a/typescript/src/renderer/components/dev/DevOverlay.tsx b/typescript/src/renderer/components/dev/DevOverlay.tsx index b9c0728..d6a1332 100644 --- a/typescript/src/renderer/components/dev/DevOverlay.tsx +++ b/typescript/src/renderer/components/dev/DevOverlay.tsx @@ -143,7 +143,7 @@ export const DevOverlay: React.FC = ({ try { await navigator.clipboard.writeText(JSON.stringify(block, null, 2)); } catch (err) { - console.error('Failed to copy to clipboard:', err); + console.error("Failed to copy to clipboard:", err); } }; @@ -233,27 +233,27 @@ export const DevOverlay: React.FC = ({ fill="currentColor" style={{ opacity: 0.8 }} > - + Copy + onClick={onClose} + onMouseDown={(e) => e.stopPropagation()} // Prevent drag when clicking close button + style={{ + background: "none", + border: "none", + color: "#ffffff", + cursor: "pointer", + fontSize: "16px", + padding: "4px 8px", + borderRadius: "4px", + }} + onMouseEnter={(e) => (e.currentTarget.style.background = "#333")} + onMouseLeave={(e) => (e.currentTarget.style.background = "none")} + > + × +
 ({
 describe("JsonDocRenderer - All Block Types", () => {
   it("renders page title correctly", () => {
     render();
+
     expect(
       screen.getByText("Test Page with All Block Types")
     ).toBeInTheDocument();
@@ -49,6 +50,8 @@ describe("JsonDocRenderer - All Block Types", () => {
     const page = createPageWithBlocks([mockBlocks.heading_1]);
     render();
 
+    screen.debug();
+
     expect(screen.getByText("Main Heading")).toBeInTheDocument();
   });
 
diff --git a/typescript/tests/ThemeSwitching.test.tsx b/typescript/tests/ThemeSwitching.test.tsx
new file mode 100644
index 0000000..f2f63da
--- /dev/null
+++ b/typescript/tests/ThemeSwitching.test.tsx
@@ -0,0 +1,87 @@
+import "@testing-library/jest-dom";
+
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import { describe, it, expect } from "vitest";
+import { JsonDocRenderer } from "../src/renderer/JsonDocRenderer";
+import { mockBlocks, mockPageWithAllBlocks } from "./fixtures/test-blocks";
+
+// Helper to create a simple page for theme testing
+const createSimplePage = () => ({
+  object: "page",
+  id: "theme-test-page",
+  properties: {
+    title: {
+      type: "title",
+      title: [
+        {
+          href: null,
+          type: "text",
+          text: { link: null, content: "Theme Test Page" },
+          annotations: {},
+          plain_text: "Theme Test Page",
+        },
+      ],
+    },
+  },
+  children: [mockBlocks.paragraph],
+});
+
+const JSON_DOC_ROOT_TEST_ID = "jsondoc-renderer-root";
+
+describe("JsonDocRenderer - Theme Switching", () => {
+  const simplePage = createSimplePage();
+
+  it("renders with light theme by default", () => {
+    const { container } = render();
+
+    const rendererDiv = screen.getByTestId(JSON_DOC_ROOT_TEST_ID);
+    expect(rendererDiv).toHaveClass("jsondoc-theme-light");
+    expect(rendererDiv).not.toHaveClass("jsondoc-theme-dark");
+  });
+
+  it("renders with light theme when explicitly set", () => {
+    const { container } = render(
+      
+    );
+
+    const rendererDiv = screen.getByTestId(JSON_DOC_ROOT_TEST_ID);
+    expect(rendererDiv).toHaveClass("jsondoc-theme-light");
+    expect(rendererDiv).not.toHaveClass("jsondoc-theme-dark");
+  });
+
+  it("renders with dark theme when theme prop is set to dark", () => {
+    const { container } = render(
+      
+    );
+
+    const rendererDiv = screen.getByTestId(JSON_DOC_ROOT_TEST_ID);
+    expect(rendererDiv).toHaveClass("jsondoc-theme-dark");
+    expect(rendererDiv).not.toHaveClass("jsondoc-theme-light");
+  });
+
+  it("handles undefined theme prop gracefully", () => {
+    const { container } = render(
+      
+    );
+
+    const rendererDiv = screen.getByTestId(JSON_DOC_ROOT_TEST_ID);
+    expect(rendererDiv).toHaveClass("jsondoc-theme-light");
+  });
+
+  it("theme persists across different block types", () => {
+    const { container } = render(
+      
+    );
+
+    const rendererDiv = screen.getByTestId(JSON_DOC_ROOT_TEST_ID);
+    expect(rendererDiv).toHaveClass("jsondoc-theme-dark");
+
+    // Verify various block types are still rendered
+    expect(screen.getByText("This is a paragraph with")).toBeInTheDocument();
+    expect(screen.getByText("First bullet point")).toBeInTheDocument();
+    expect(
+      screen.getByText('console.log("Hello, World!");')
+    ).toBeInTheDocument();
+  });
+});
diff --git a/typescript/tests/UnsupportedBlockHandling.test.tsx b/typescript/tests/UnsupportedBlockHandling.test.tsx
new file mode 100644
index 0000000..160a78d
--- /dev/null
+++ b/typescript/tests/UnsupportedBlockHandling.test.tsx
@@ -0,0 +1,105 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
+import { JsonDocRenderer } from "../src/renderer/JsonDocRenderer";
+import { mockBlocks } from "./fixtures/test-blocks";
+
+// Helper to create a page with specific blocks
+const createPageWithBlocks = (blocks: any[]) => ({
+  object: "page",
+  id: "test-page",
+  properties: {
+    title: {
+      type: "title",
+      title: [
+        {
+          href: null,
+          type: "text",
+          text: { link: null, content: "Test Page" },
+          annotations: {},
+          plain_text: "Test Page",
+        },
+      ],
+    },
+  },
+  children: blocks,
+});
+
+describe("Unsupported Block Handling", () => {
+  // Mock console.warn to test logging behavior
+  const originalConsoleWarn = console.warn;
+  beforeEach(() => {
+    console.warn = vi.fn();
+  });
+  afterEach(() => {
+    console.warn = originalConsoleWarn;
+  });
+
+  const warningMsg = expect.stringMatching(/Unsupported block type:/);
+
+  it("renders fallback UI for unsupported block types", () => {
+    const page = createPageWithBlocks([mockBlocks.unsupported_with_content]);
+    const { container } = render();
+
+    // Should still render fallback UI regardless of block content
+    expect(
+      screen.getByText(/Unsupported block type: ai_block/)
+    ).toBeInTheDocument();
+
+    expect(screen.getByRole("alert")).toBeInTheDocument();
+  });
+
+  it("renders unsupported blocks with children gracefully", () => {
+    const page = createPageWithBlocks([mockBlocks.unsupported_with_children]);
+    const { container } = render();
+
+    // Should show fallback for the unsupported parent block
+    expect(
+      screen.getByText(/Unsupported block type: template_block/)
+    ).toBeInTheDocument();
+
+    expect(
+      container.querySelector('[data-block-type="template_block"]')
+    ).toBeInTheDocument();
+
+    // Children should not be rendered within unsupported block fallback
+    expect(
+      screen.queryByText(/Content inside unsupported block/)
+    ).not.toBeInTheDocument();
+  });
+
+  it("handles multiple unsupported blocks without breaking renderer", () => {
+    const page = createPageWithBlocks([
+      mockBlocks.unsupported_single,
+      mockBlocks.paragraph, // Mix with supported block
+      mockBlocks.unsupported_with_content,
+      mockBlocks.heading_1, // Another supported block
+      mockBlocks.unsupported_with_children,
+    ]);
+    const { container } = render();
+
+    // All unsupported blocks should have fallback UI
+    expect(
+      screen.getByText(/Unsupported block type: custom_widget/)
+    ).toBeInTheDocument();
+    expect(
+      screen.getByText(/Unsupported block type: ai_block/)
+    ).toBeInTheDocument();
+    expect(
+      screen.getByText(/Unsupported block type: template_block/)
+    ).toBeInTheDocument();
+
+    // Supported blocks should still render normally
+    expect(screen.getByText(/This is a paragraph with/)).toBeInTheDocument();
+    expect(screen.getByText(/Main Heading/)).toBeInTheDocument();
+
+    // Check all unsupported blocks have correct CSS class
+    expect(screen.getAllByRole("alert")).toHaveLength(3);
+
+    // Verify all warnings were logged
+    expect(console.warn).toHaveBeenCalledTimes(3);
+    expect(console.warn).toHaveBeenCalledWith(warningMsg, "custom_widget");
+    expect(console.warn).toHaveBeenCalledWith(warningMsg, "ai_block");
+    expect(console.warn).toHaveBeenCalledWith(warningMsg, "template_block");
+  });
+});
diff --git a/typescript/tests/fixtures/test-blocks.ts b/typescript/tests/fixtures/test-blocks.ts
index 4c3b97b..8293a72 100644
--- a/typescript/tests/fixtures/test-blocks.ts
+++ b/typescript/tests/fixtures/test-blocks.ts
@@ -454,6 +454,59 @@ export const mockBlocks: Record<
       expression: "E = mc^2",
     },
   },
+
+  // Mock unsupported block types for testing fallback behavior
+  unsupported_single: {
+    object: "block",
+    id: "unsupported-1",
+    type: "custom_widget" as any,
+    created_time: "2025-06-24T09:44:12.014249Z",
+    has_children: false,
+  },
+
+  unsupported_with_content: {
+    object: "block",
+    id: "unsupported-2",
+    type: "ai_block" as any,
+    created_time: "2025-06-24T09:44:12.014249Z",
+    has_children: false,
+    // @ts-expect-error
+    ai_block: {
+      prompt: "Generate something amazing",
+    },
+  },
+
+  unsupported_with_children: {
+    object: "block",
+    id: "unsupported-3",
+    type: "template_block" as any,
+    created_time: "2025-06-24T09:44:12.014249Z",
+    has_children: true,
+    template_block: {
+      template_id: "template-123",
+    },
+    children: [
+      {
+        object: "block",
+        id: "h1-1",
+        // @ts-ignore
+        type: "paragraph",
+        created_time: "2025-06-24T09:44:12.014249Z",
+        has_children: false,
+        // @ts-ignore
+        heading_1: {
+          rich_text: [
+            {
+              type: "text",
+              text: { content: "Main Heading" },
+              annotations: {},
+              plain_text: "Main Heading",
+            },
+          ],
+        },
+      },
+    ],
+  },
 };
 
 // Complete page with all block types for comprehensive testing
diff --git a/typescript/tests/test-cases.md b/typescript/tests/test-cases.md
index 8b87f91..b980a0c 100644
--- a/typescript/tests/test-cases.md
+++ b/typescript/tests/test-cases.md
@@ -5,4 +5,4 @@
 - make sure all blocks are rendered
 - make sure numbered lists are correctly labeled
 - make sure bad page is gracefully handled
-- invalid block type should show error block UI
\ No newline at end of file
+- invalid block type should show error block UI
diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json
index c62ecad..287d36d 100644
--- a/typescript/tsconfig.json
+++ b/typescript/tsconfig.json
@@ -1,4 +1,6 @@
 {
+  "$schema": "https://json.schemastore.org/tsconfig",
+
   "compilerOptions": {
     "target": "es2022",
     "module": "NodeNext",
@@ -16,6 +18,8 @@
     "resolveJsonModule": true,
     "sourceMap": true,
     "allowJs": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
     // "noUncheckedIndexedAccess": true,
     "baseUrl": ".",
     "paths": {