From 74c303ca6ef6e8d237ba52c5574e5eb8b7f3ef3b Mon Sep 17 00:00:00 2001 From: amitdhiman5086 Date: Thu, 19 Feb 2026 12:49:23 +0530 Subject: [PATCH 1/9] feat: viewing changes unpublished partial blocks via a diff viewer --- .../unpublished-partials-modal.tsx | 25 +++++++++++++++---- src/pages/client/components/topbar-right.tsx | 24 ++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx b/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx index 0f32c35b..bbd353fa 100644 --- a/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx +++ b/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx @@ -8,12 +8,14 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { PartialBlockInfo } from "@/pages/hooks/pages/use-get-unpublished-partial-blocks"; +import { Eye } from "lucide-react"; import { useTranslation } from "react-i18next"; interface UnpublishedPartialsModalProps { isOpen: boolean; onClose: () => void; onContinue: () => void; + onViewChanges?: (partialId: string, partialName: string) => void; isPending?: boolean; partialBlocksInfo?: PartialBlockInfo[]; } @@ -22,6 +24,7 @@ const UnpublishedPartialsModal = ({ isOpen, onClose, onContinue, + onViewChanges, isPending = false, partialBlocksInfo = [], }: UnpublishedPartialsModalProps) => { @@ -44,11 +47,23 @@ const UnpublishedPartialsModal = ({ {partialBlocksInfo.map((info) => (
  • • {info?.name} - - {info?.status === "unpublished" ? t("Unpublished page") : t("Unpublished changes")} + + {info?.status === "unpublished_changes" && onViewChanges && ( + + )} + + {info?.status === "unpublished" ? t("Unpublished page") : t("Unpublished changes")} +
  • ))} diff --git a/src/pages/client/components/topbar-right.tsx b/src/pages/client/components/topbar-right.tsx index 7a15de97..3cbbed45 100644 --- a/src/pages/client/components/topbar-right.tsx +++ b/src/pages/client/components/topbar-right.tsx @@ -195,6 +195,7 @@ const PublishButton = () => { const [showUnpublishedPartialsWarning, setShowUnpublishedPartialsWarning] = useState(false); const [unpublishedPartialBlockIds, setUnpublishedPartialBlockIds] = useState([]); const [unpublishedPartialBlocksInfo, setUnpublishedPartialBlocksInfo] = useState([]); + const [comparePartial, setComparePartial] = useState<{ id: string; name: string } | null>(null); const { data: currentPage } = usePrimaryPage(); const { mutate: publishPage, isPending } = usePublishPages(); @@ -261,6 +262,10 @@ const PublishButton = () => { setUnpublishedPartialBlocksInfo([]); }; + const handleViewPartialChanges = useCallback((partialId: string, partialName: string) => { + setComparePartial({ id: partialId, name: partialName }); + }, []); + const handleContinueAnyway = () => { setShowTranslationWarning(false); checkAndPublish([activePage?.id, activePage?.primaryPage]); @@ -413,11 +418,30 @@ const PublishButton = () => { isOpen={showUnpublishedPartialsWarning} onClose={handleCancelPartials} onContinue={handleContinueWithPartials} + onViewChanges={handleViewPartialChanges} isPending={isPending} partialBlocksInfo={unpublishedPartialBlocksInfo} /> )} + + {comparePartial && ( + + { + if (!open) { + setComparePartial(null); + setShowUnpublishedPartialsWarning(true); + } + }} + compare={[ + { label: "live", uid: `live:${comparePartial.id}`, item: {} }, + { label: "draft", uid: `draft:${comparePartial.id}`, item: {} }, + ]} + /> + + )} ); }; From 251120f45457085ab41c561bb549b99c144b1f0c Mon Sep 17 00:00:00 2001 From: amitdhiman5086 Date: Thu, 19 Feb 2026 13:43:28 +0530 Subject: [PATCH 2/9] fix: removed "Unpublished changes" --- .../components/save-ui-blocks/unpublished-partials-modal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx b/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx index bbd353fa..ccf8f1bf 100644 --- a/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx +++ b/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx @@ -60,9 +60,9 @@ const UnpublishedPartialsModal = ({ )} - {info?.status === "unpublished" ? t("Unpublished page") : t("Unpublished changes")} + {info?.status === "unpublished" && t("Unpublished page")} From 02dda9fb90ff95d636be6379364a680bd7fbd4bf Mon Sep 17 00:00:00 2001 From: amitdhiman5086 Date: Thu, 19 Feb 2026 14:31:57 +0530 Subject: [PATCH 3/9] feat: added clear text --- .../unpublished-partials-modal.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx b/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx index ccf8f1bf..d55489f9 100644 --- a/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx +++ b/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx @@ -34,10 +34,10 @@ const UnpublishedPartialsModal = ({ {isOpen && ( - {t("Publish Page with Unpublished Blocks?")} + {t("You have some unpublished changes")} {t( - "You have unpublished changes in the following blocks. They will be published together with the page.", + "The following partials are either unpublished or have unpublished changes.", )} @@ -46,23 +46,22 @@ const UnpublishedPartialsModal = ({
      {partialBlocksInfo.map((info) => (
    • - • {info?.name} - +

      {info?.name}
      + {info?.status === "unpublished_changes" && onViewChanges && ( )} - {info?.status === "unpublished" && t("Unpublished page")} + {info?.status === "unpublished" && t("Unpublished")}
    • @@ -75,7 +74,7 @@ const UnpublishedPartialsModal = ({ {t("Cancel")} From db09a1f470b6919113faa2b356e4c1232b97b0cf Mon Sep 17 00:00:00 2001 From: amitdhiman5086 Date: Thu, 19 Feb 2026 14:35:14 +0530 Subject: [PATCH 4/9] fix: removed eye --- .../components/save-ui-blocks/unpublished-partials-modal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx b/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx index d55489f9..8d2bb365 100644 --- a/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx +++ b/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx @@ -8,7 +8,6 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { PartialBlockInfo } from "@/pages/hooks/pages/use-get-unpublished-partial-blocks"; -import { Eye } from "lucide-react"; import { useTranslation } from "react-i18next"; interface UnpublishedPartialsModalProps { From ba8e9d8b3661c5c7b61b583deb687340437386fe Mon Sep 17 00:00:00 2001 From: amitdhiman5086 Date: Thu, 19 Feb 2026 15:00:45 +0530 Subject: [PATCH 5/9] fix: replace status text with Badge components in the unpublished partials modal --- .../unpublished-partials-modal.tsx | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx b/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx index 8d2bb365..9a0c312c 100644 --- a/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx +++ b/src/pages/client/components/save-ui-blocks/unpublished-partials-modal.tsx @@ -1,3 +1,4 @@ +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -46,22 +47,28 @@ const UnpublishedPartialsModal = ({ {partialBlocksInfo.map((info) => (
    • {info?.name}
      - - {info?.status === "unpublished_changes" && onViewChanges && ( - + + {info?.status === "unpublished_changes" && ( + <> + + {t("Published")} + + {onViewChanges && ( + + )} + + )} + {info?.status === "unpublished" && ( + + {t("Unpublished")} + )} - - {info?.status === "unpublished" && t("Unpublished")} -
    • ))} From a20cfbf706e4c67ea61ebe524885aff207400526 Mon Sep 17 00:00:00 2001 From: amitdhiman5086 Date: Thu, 19 Feb 2026 15:21:54 +0530 Subject: [PATCH 6/9] feat: dedicated live page link button to the topbar right and remove the old address bar link --- src/pages/client/components/topbar-right.tsx | 23 +++++++- src/pages/extensions/topbar.tsx | 57 ++------------------ 2 files changed, 26 insertions(+), 54 deletions(-) diff --git a/src/pages/client/components/topbar-right.tsx b/src/pages/client/components/topbar-right.tsx index 3cbbed45..5e8a32ce 100644 --- a/src/pages/client/components/topbar-right.tsx +++ b/src/pages/client/components/topbar-right.tsx @@ -15,7 +15,7 @@ import PermissionChecker from "@/pages/client/components/permission-checker"; import PublishPages from "@/pages/client/components/publish-pages/publish-pages"; import { PAGES_PERMISSIONS } from "@/pages/constants/PERMISSIONS"; import { usePublishPages } from "@/pages/hooks/pages/mutations"; -import { useCurrentActivePage, usePrimaryPage } from "@/pages/hooks/pages/use-current-page"; +import { useCurrentActivePage, useGetPageFullSlug, usePrimaryPage } from "@/pages/hooks/pages/use-current-page"; import { useGetUnpublishedPartialBlocks } from "@/pages/hooks/pages/use-get-unpublished-partial-blocks"; import { useIsLanguagePageCreated } from "@/pages/hooks/pages/use-is-languagep-page-created"; import { useLanguagePages } from "@/pages/hooks/pages/use-language-pages"; @@ -30,6 +30,7 @@ import { compact, find, isEmpty, upperCase } from "lodash-es"; import { CheckCircle, ChevronDown, + ExternalLink, Eye, Loader, Palette, @@ -446,6 +447,25 @@ const PublishButton = () => { ); }; +const LiveLinkButton = () => { + const { t } = useTranslation(); + const { data: currentPage } = usePrimaryPage(); + const fullUrl = useGetPageFullSlug(); + const isOnline = currentPage?.online; + + if (!isOnline) return null; + + return ( + + + + + + ); +}; + export default function TopbarRight() { const { isLocked } = usePageLockStatus(); const [searchParams] = useSearchParams(); @@ -467,6 +487,7 @@ export default function TopbarRight() { + ); } diff --git a/src/pages/extensions/topbar.tsx b/src/pages/extensions/topbar.tsx index 20cdb7c4..ff026af2 100644 --- a/src/pages/extensions/topbar.tsx +++ b/src/pages/extensions/topbar.tsx @@ -4,15 +4,13 @@ import { PageDropdownInHeader } from "@/pages/client/components/page-dropdown-in import { ScreenOverlay } from "@/pages/client/components/screen-overlay"; import TopbarLeft, { LanguageSwitcher } from "@/pages/client/components/topbar-left"; import TopbarRight from "@/pages/client/components/topbar-right"; -import { useCurrentActivePage, useGetPageFullSlug, usePrimaryPage } from "@/pages/hooks/pages/use-current-page"; -import { useDynamicPageSelector, useDynamicPageSlug } from "@/pages/hooks/pages/use-dynamic-page-selector"; +import { useCurrentActivePage, usePrimaryPage } from "@/pages/hooks/pages/use-current-page"; +import { useDynamicPageSelector } from "@/pages/hooks/pages/use-dynamic-page-selector"; import { useChaiFeatureFlag } from "@/runtime/client"; import { get } from "lodash-es"; -import { ChevronRight, ExternalLink } from "lucide-react"; +import { ChevronRight } from "lucide-react"; import { lazy, Suspense } from "react"; -import { useTranslation } from "react-i18next"; import PagesManagerTrigger from "../client/components/page-manager/page-manager-trigger"; -import Tooltip from "../utils/tooltip"; const DynamicPageSelector = lazy(() => import("../client/components/dynamic-page-selector")); const DynamicPageSelectorSuspense = () => { @@ -31,21 +29,12 @@ const DynamicPageSelectorSuspense = () => { }; const AddressBar = () => { - const { data: activePage, isFetching: isFetchingActivePage } = useCurrentActivePage(); + const { isFetching: isFetchingActivePage } = useCurrentActivePage(); const { data: page, isFetching: isFetchingCurrentPage } = usePrimaryPage(); const dynamic = get(page, "dynamic", false); - const dynamicPageSlug = useDynamicPageSlug(); const isDynamicPageSelectorEnabled = useChaiFeatureFlag("dynamic-page-selector"); - const { t } = useTranslation(); - const slug = activePage?.slug; - const isPartialPage = !slug; - const fullUrl = useGetPageFullSlug(); const isFetching = isFetchingActivePage || isFetchingCurrentPage; - // Ensure the slug is always visible, truncate domain if needed - const visible = isPartialPage ? `Partial: ${activePage?.name} ` : `${slug}${dynamicPageSlug}`; - const visibleSlug = visible.replace(window.location.host, ""); - return (
      @@ -61,44 +50,6 @@ const AddressBar = () => {
      - - {/* ChevronRight */} - - - {/* LanguageSwitcher */} - {/*
      - -
      */} - - {/* ChevronRight */} - {/* */} - - {/* Current page path */} -
      -
      - {visibleSlug === "/" ? ( - - /(Homepage) - - ) : ( - visibleSlug - )} -
      - {!isPartialPage && ( - - -
      - -
      -
      -
      - )} -
      {dynamic && isDynamicPageSelectorEnabled && } From 55c228caf7675025d6bbbb7ddc3bdeefe858f177 Mon Sep 17 00:00:00 2001 From: Suraj Air Date: Sun, 22 Feb 2026 14:53:34 +0530 Subject: [PATCH 7/9] refactor: move `lsThemeAtom` from demo atoms to ui atoms and update import paths --- src/atoms/ui.ts | 3 +++ src/core/rjsf-widgets/rte-widget/rte-color-picker.tsx | 2 +- src/routes/builder.tsx | 3 ++- src/routes/demo/atoms-dev.ts | 3 --- src/routes/page-preview.tsx | 3 ++- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/atoms/ui.ts b/src/atoms/ui.ts index 810085c0..3afd7a4c 100644 --- a/src/atoms/ui.ts +++ b/src/atoms/ui.ts @@ -1,3 +1,5 @@ +import { defaultThemeValues } from "@/hooks/default-theme-options"; +import { ChaiTheme } from "@/types"; import { atom } from "jotai"; import { atomWithStorage } from "jotai/utils"; import { TreeApi } from "react-arborist"; @@ -58,3 +60,4 @@ selectedLibraryAtom.debugLabel = "selectedLibraryAtom"; export const dataBindingActiveAtom = atom(true); dataBindingActiveAtom.debugLabel = "dataBindingActiveAtom"; +export const lsThemeAtom = atomWithStorage("chai-builder-theme", defaultThemeValues); diff --git a/src/core/rjsf-widgets/rte-widget/rte-color-picker.tsx b/src/core/rjsf-widgets/rte-widget/rte-color-picker.tsx index 8bbb25f1..9436e6fc 100644 --- a/src/core/rjsf-widgets/rte-widget/rte-color-picker.tsx +++ b/src/core/rjsf-widgets/rte-widget/rte-color-picker.tsx @@ -1,7 +1,7 @@ +import { lsThemeAtom } from "@/atoms/ui"; import { Input } from "@/components/ui/input"; import { cn } from "@/core/utils/cn"; import { useDarkMode } from "@/hooks/use-dark-mode"; -import { lsThemeAtom } from "@/routes/demo/atoms-dev"; import { CaretDownIcon, Cross1Icon } from "@radix-ui/react-icons"; import { useDebouncedState } from "@react-hookz/web"; import { useAtom } from "jotai"; diff --git a/src/routes/builder.tsx b/src/routes/builder.tsx index addd8cef..ea3f56b8 100644 --- a/src/routes/builder.tsx +++ b/src/routes/builder.tsx @@ -1,6 +1,7 @@ +import { lsThemeAtom } from "@/atoms/ui"; import { ChaiBuilderEditor, defaultChaiLibrary } from "@/core/main"; import "@/index.css"; -import { lsBlocksAtom, lsDesignTokensAtom, lsThemeAtom } from "@/routes/demo/atoms-dev"; +import { lsBlocksAtom, lsDesignTokensAtom } from "@/routes/demo/atoms-dev"; import { EXTERNAL_DATA } from "@/routes/demo/EXTERNAL_DATA"; import { PARTIALS } from "@/routes/demo/PARTIALS"; import { defaultShadcnPreset } from "@/routes/demo/THEME_PRESETS"; diff --git a/src/routes/demo/atoms-dev.ts b/src/routes/demo/atoms-dev.ts index b60b210d..b9707384 100644 --- a/src/routes/demo/atoms-dev.ts +++ b/src/routes/demo/atoms-dev.ts @@ -1,9 +1,6 @@ -import { defaultThemeValues } from "@/hooks/default-theme-options"; -import { ChaiTheme } from "@/types"; import { ChaiDesignTokens } from "@/types/types"; import { atomWithStorage } from "jotai/utils"; export const lsBlocksAtom = atomWithStorage("chai-builder-blocks", []); -export const lsThemeAtom = atomWithStorage("chai-builder-theme", defaultThemeValues); export const lsDesignTokensAtom = atomWithStorage("chai-builder-design-tokens", {}); export const lsAiContextAtom = atomWithStorage("chai-builder-ai-context", ""); diff --git a/src/routes/page-preview.tsx b/src/routes/page-preview.tsx index 5da1f03f..21448105 100644 --- a/src/routes/page-preview.tsx +++ b/src/routes/page-preview.tsx @@ -1,8 +1,9 @@ +import { lsThemeAtom } from "@/atoms/ui"; import "@/core/index.css"; import { getChaiThemeCssVariables, getStylesForBlocks, RenderChaiBlocks } from "@/render"; import { applyDesignTokens } from "@/render/apply-design-tokens"; import { getMergedPartialBlocks } from "@/render/functions"; -import { lsBlocksAtom, lsDesignTokensAtom, lsThemeAtom } from "@/routes/demo/atoms-dev"; +import { lsBlocksAtom, lsDesignTokensAtom } from "@/routes/demo/atoms-dev"; import registerCustomBlocks from "@/routes/demo/blocks"; import { EXTERNAL_DATA } from "@/routes/demo/EXTERNAL_DATA"; import { PARTIALS } from "@/routes/demo/PARTIALS"; From 146b83cc1d71afb30d46ac3cca2dbcdd7426cdfe Mon Sep 17 00:00:00 2001 From: Suraj Air Date: Sun, 22 Feb 2026 14:56:36 +0530 Subject: [PATCH 8/9] fix: change docs router mode from history to hash --- docs/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.html b/docs/index.html index 6911667e..f24c5833 100644 --- a/docs/index.html +++ b/docs/index.html @@ -50,7 +50,7 @@ loadSidebar: true, subMaxLevel: 3, auto2top: true, - routerMode: "history", + routerMode: "hash", search: { placeholder: "Search docs...", noData: "No results found", From 2021318c374eb090404334412aa985196f4b2e8c Mon Sep 17 00:00:00 2001 From: Suraj Air Date: Sun, 22 Feb 2026 15:01:07 +0530 Subject: [PATCH 9/9] feat: add GitHub workflow to auto-generate sitemap for docs --- .github/workflows/sitemap.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/sitemap.yml diff --git a/.github/workflows/sitemap.yml b/.github/workflows/sitemap.yml new file mode 100644 index 00000000..4b17de85 --- /dev/null +++ b/.github/workflows/sitemap.yml @@ -0,0 +1,33 @@ +name: Generate Sitemap + +on: + push: + branches: + - main + paths: + - "docs/**" + workflow_dispatch: + +permissions: + contents: write + +jobs: + sitemap: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate Sitemap + uses: tenelabs/docsify-sitemap-action@v1 + with: + baseUrl: https://docs.chaibuilder.com + docsPath: ./docs + sitemapPath: ./docs/sitemap.xml + + - name: Commit and Push + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add docs/sitemap.xml + git diff --quiet && git diff --staged --quiet || (git commit -m "chore: update sitemap" && git push)