@morphika/andami 0.1.10 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/app/admin/pages/[slug]/page.tsx +3 -7
  2. package/app/api/admin/pages/[slug]/route.ts +2 -28
  3. package/app/api/admin/settings/route.ts +30 -0
  4. package/components/blocks/EnterAnimationWrapper.tsx +19 -4
  5. package/components/blocks/PageRenderer.tsx +2 -15
  6. package/components/blocks/ProjectGridBlockRenderer.tsx +34 -36
  7. package/components/blocks/TextBlockRenderer.tsx +1 -1
  8. package/components/builder/DndWrapper.tsx +2 -24
  9. package/components/builder/InsertionLines.tsx +5 -5
  10. package/components/builder/ReadOnlyFrame.tsx +5 -49
  11. package/components/builder/SectionV2Canvas.tsx +2 -2
  12. package/components/builder/SectionV2Column.tsx +5 -5
  13. package/components/builder/SettingsPanel.tsx +0 -12
  14. package/components/builder/SortableBlock.tsx +3 -3
  15. package/components/builder/SortableRow.tsx +6 -27
  16. package/components/builder/live-preview/ProjectCardWrapper.tsx +3 -3
  17. package/components/builder/live-preview/drag-utils.tsx +2 -2
  18. package/components/builder/settings-panel/AnimationTab.tsx +2 -16
  19. package/components/builder/settings-panel/index.ts +0 -1
  20. package/components/builder/settings-panel/responsive-helpers.ts +2 -50
  21. package/components/builder/settings-panel/useSettingsPanelSelection.ts +1 -16
  22. package/lib/builder/constants.ts +5 -4
  23. package/lib/builder/serializer/normalizers.ts +2 -40
  24. package/lib/builder/serializer/serializers.ts +3 -74
  25. package/lib/builder/store-blocks.ts +3 -19
  26. package/lib/builder/store-helpers.ts +2 -2
  27. package/lib/builder/store-sections.ts +26 -64
  28. package/lib/builder/store.ts +3 -6
  29. package/lib/builder/templates.ts +9 -45
  30. package/lib/builder/types.ts +4 -11
  31. package/lib/sanity/queries.ts +6 -29
  32. package/lib/sanity/types.ts +2 -70
  33. package/package.json +2 -2
  34. package/sanity/schemas/index.ts +0 -5
  35. package/sanity/schemas/objects/parallaxGroup.ts +2 -2
  36. package/sanity/schemas/page.ts +1 -1
  37. package/sanity/schemas/pageSectionV2.ts +1 -0
  38. package/sanity/schemas/siteSettings.ts +42 -0
  39. package/styles/base.css +7 -5
  40. package/components/blocks/SectionRenderer.tsx +0 -171
  41. package/components/builder/settings-panel/LayoutTab.tsx +0 -346
  42. package/sanity/schemas/pageSection.ts +0 -157
@@ -109,7 +109,7 @@ export default function SortableBlock({
109
109
  style={style}
110
110
  className={`relative transition-[opacity,box-shadow] ${
111
111
  isDragging
112
- ? "ring-2 ring-[#e28b00] ring-offset-1 ring-offset-transparent rounded"
112
+ ? "ring-2 ring-[#0d9668] ring-offset-1 ring-offset-transparent rounded"
113
113
  : ""
114
114
  }`}
115
115
  onClick={(e) => {
@@ -129,7 +129,7 @@ export default function SortableBlock({
129
129
  isSelected
130
130
  ? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px ${BUILDER_ORANGE}` }
131
131
  : isHovered
132
- ? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(226, 139, 0, 0.4)` }
132
+ ? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(13, 150, 104, 0.4)` }
133
133
  : undefined
134
134
  }
135
135
  />
@@ -149,7 +149,7 @@ export default function SortableBlock({
149
149
  className="flex items-center gap-1.5"
150
150
  style={{ transform: `scale(${Math.min(2, 1 / canvasZoom)})`, transformOrigin: "top center" }}
151
151
  >
152
- <div className="flex items-center bg-[#e28b00] rounded shadow-lg overflow-hidden">
152
+ <div className="flex items-center bg-[#0d9668] rounded shadow-lg overflow-hidden">
153
153
  {/* Move up arrow */}
154
154
  <button
155
155
  onClick={() => canMoveUp && reorderBlocks(rowKey, colKey, blockIndex, blockIndex - 1)}
@@ -8,11 +8,11 @@ import { useBuilderStore } from "../../lib/builder/store";
8
8
  import { DEFAULT_GRID_WIDTH } from "../../lib/builder/constants";
9
9
  import { DEVICE_HEIGHTS } from "../../lib/builder/types";
10
10
  import type { ReactNode } from "react";
11
- import type { ContentItem, PageSection, PageSectionV2, CustomSectionInstance, ParallaxGroup } from "../../lib/sanity/types";
12
- import { isPageSection, isPageSectionV2, isCustomSectionInstance, isParallaxGroup } from "../../lib/sanity/types";
11
+ import type { ContentItem, PageSectionV2, CustomSectionInstance, ParallaxGroup } from "../../lib/sanity/types";
12
+ import { isPageSectionV2, isCustomSectionInstance, isParallaxGroup } from "../../lib/sanity/types";
13
13
  import { getRowLayoutStyles } from "../../lib/builder/layout-styles";
14
14
  import { normalizeMinHeight } from "../../lib/builder/utils";
15
- import { getSectionV2SettingValue, getRowSettingValue } from "./settings-panel/responsive-helpers";
15
+ import { getSectionV2SettingValue } from "./settings-panel/responsive-helpers";
16
16
 
17
17
  /**
18
18
  * Convert vh-based CSS values to pixels using the simulated device viewport height.
@@ -29,14 +29,8 @@ function vhToBuilderPx(value: string | undefined, deviceHeight: number): string
29
29
  return value;
30
30
  }
31
31
 
32
- /** Section type labels */
33
- const SECTION_TYPE_LABELS: Record<string, string> = {
34
- projectGrid: "Project Grid",
35
- };
36
-
37
32
  /**
38
33
  * Get the section label for a content item.
39
- * For PageSections: uses section_type directly (no structural inference).
40
34
  * For PageSectionV2: returns "Section".
41
35
  */
42
36
  function getSectionLabel(item: ContentItem): string | null {
@@ -49,9 +43,6 @@ function getSectionLabel(item: ContentItem): string | null {
49
43
  if (isPageSectionV2(item)) {
50
44
  return "Section";
51
45
  }
52
- if (isPageSection(item)) {
53
- return SECTION_TYPE_LABELS[item.section_type] || "Section";
54
- }
55
46
  return null;
56
47
  }
57
48
 
@@ -108,9 +99,8 @@ export default function SortableRow({
108
99
  zIndex: isDragging ? 50 : undefined,
109
100
  };
110
101
 
111
- // Determine if this is a PageSection, PageSectionV2, or a Row
102
+ // Determine if this is a PageSectionV2
112
103
  const isV2Section = isPageSectionV2(row);
113
- const isSection = isPageSection(row);
114
104
  const sectionLabel = getSectionLabel(row);
115
105
 
116
106
  // For sections: use section settings — viewport-aware for both V1 and V2 sections
@@ -133,17 +123,6 @@ export default function SortableRow({
133
123
  return merged;
134
124
  }
135
125
 
136
- if (isSection) {
137
- const ps = row as PageSection;
138
- const base = ps.settings || {};
139
- // BUG-013 continuation: Merge responsive overrides for V1 PageSections too
140
- const merged: Record<string, unknown> = { ...base };
141
- for (const key of overridableKeys) {
142
- merged[key] = getRowSettingValue(ps, activeViewport, key, (base as Record<string, unknown>)[key]);
143
- }
144
- return merged;
145
- }
146
-
147
126
  // CustomSectionInstance: merge base section settings from cache + per-instance overrides
148
127
  // Then apply viewport-aware responsive resolution (same pattern as V2 sections)
149
128
  if (isCustomSectionInstance(row)) {
@@ -169,7 +148,7 @@ export default function SortableRow({
169
148
  }
170
149
 
171
150
  return {};
172
- }, [isV2Section, isSection, row, activeViewport, customSectionCache]);
151
+ }, [isV2Section, row, activeViewport, customSectionCache]);
173
152
 
174
153
  const bgColor = (resolvedSettings as Record<string, unknown>).background_color as string || "transparent";
175
154
 
@@ -187,7 +166,7 @@ export default function SortableRow({
187
166
  const layoutStyles = getRowLayoutStyles(resolvedSettings as Record<string, unknown> || {});
188
167
 
189
168
  const showToolbar = isSelected || isHovered;
190
- const coverRow = isSection ? true : false;
169
+ const coverRow = false;
191
170
 
192
171
  // ---- Preview Mode: clean rendering with row styles applied ----
193
172
  if (previewMode) {
@@ -11,7 +11,7 @@
11
11
  import { useState, useCallback, useRef } from "react";
12
12
  import { ProjectGridCard } from "./shared";
13
13
  import { useBuilderStore } from "../../../lib/builder/store";
14
- import { ADMIN_BLUE, DROP_GREEN, CrossArrowIcon } from "./drag-utils";
14
+ import { ADMIN_BLUE, DROP_BLUE, CrossArrowIcon } from "./drag-utils";
15
15
  import type { ProjectGridItem } from "../../../lib/sanity/types";
16
16
 
17
17
  // ─── Props ───────────────────────────────────────────────────────────
@@ -278,11 +278,11 @@ export default function ProjectCardWrapper({
278
278
  style={{
279
279
  position: "absolute",
280
280
  inset: 0,
281
- backgroundColor: "rgba(34,197,94,0.30)",
281
+ backgroundColor: "rgba(7,107,255,0.30)",
282
282
  borderRadius: br,
283
283
  pointerEvents: "none",
284
284
  zIndex: 5,
285
- border: `2px solid ${DROP_GREEN}`,
285
+ border: `2px solid ${DROP_BLUE}`,
286
286
  }}
287
287
  />
288
288
  )}
@@ -7,7 +7,7 @@
7
7
  * Session 162: Extracted from LiveProjectGridPreview.tsx (Phase B1).
8
8
  */
9
9
 
10
- import { BUILDER_BLUE, BUILDER_GREEN } from "../../../lib/builder/constants";
10
+ import { BUILDER_BLUE } from "../../../lib/builder/constants";
11
11
  import type { MasonryOutput } from "../../../lib/builder/masonry";
12
12
 
13
13
  // ─── Constants ───────────────────────────────────────────────────────
@@ -16,7 +16,7 @@ export const HOLD_DELAY = 150; // ms before card-body drag activates
16
16
  export const MOVE_THRESHOLD_SQ = 9; // 3px² — grabbed → dragging
17
17
  export const CANCEL_DURATION = 200; // ms — cancel fly-back animation
18
18
  export const ADMIN_BLUE = BUILDER_BLUE;
19
- export const DROP_GREEN = BUILDER_GREEN;
19
+ export const DROP_BLUE = BUILDER_BLUE;
20
20
 
21
21
  // ─── Types ───────────────────────────────────────────────────────────
22
22
 
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { useBuilderStore } from "../../../lib/builder/store";
10
- import type { ContentBlock, PageSection, ProjectGridBlock } from "../../../lib/sanity/types";
10
+ import type { ContentBlock, ProjectGridBlock } from "../../../lib/sanity/types";
11
11
  import type { HoverEffectConfig } from "../../../lib/animation/hover-effect-types";
12
12
  import EnterAnimationPicker from "../editors/EnterAnimationPicker";
13
13
  import HoverEffectPicker from "../editors/HoverEffectPicker";
@@ -32,25 +32,11 @@ export function getBlockHoverEffect(block: ContentBlock): HoverEffectConfig | un
32
32
 
33
33
  interface AnimationTabProps {
34
34
  selectedBlock: { block: ContentBlock; rowKey: string; colKey: string; isSection: boolean } | null;
35
- selectedSection: PageSection | null;
36
35
  }
37
36
 
38
- export function AnimationTab({ selectedBlock, selectedSection }: AnimationTabProps) {
37
+ export function AnimationTab({ selectedBlock }: AnimationTabProps) {
39
38
  const store = useBuilderStore();
40
39
 
41
- // PageSection (V1): enter animation on the section settings level
42
- if (selectedSection) {
43
- return (
44
- <EnterAnimationPicker
45
- mode={{ level: "section", parentConfig: store.pageSettings.enter_animation }}
46
- config={selectedSection.settings?.enter_animation}
47
- onChange={(cfg) => {
48
- store.updateSectionSettings(selectedSection._key, { enter_animation: cfg });
49
- }}
50
- />
51
- );
52
- }
53
-
54
40
  // Block level: type-specific enter picker + card entrance for projectGrid
55
41
  if (selectedBlock) {
56
42
  const isProjectGrid = selectedBlock.block._type === "projectGridBlock";
@@ -6,7 +6,6 @@
6
6
  * RowLayoutPresetPicker, TRBLInputs.
7
7
  */
8
8
 
9
- export { LayoutTab } from "./LayoutTab";
10
9
  export { BlockLayoutTab } from "./BlockLayoutTab";
11
10
  export { TRBLInputs } from "./TRBLInputs";
12
11
  export { default as PageSettings, PageSeoSettings } from "./PageSettings";
@@ -1,60 +1,12 @@
1
1
  /**
2
- * Responsive helpers for section setting resolution.
2
+ * Responsive helpers for setting resolution.
3
3
  *
4
4
  * Session 64: Extracted from SettingsPanel.tsx.
5
- * Session 86: Removed V1 Row/Column types now uses generic interface
6
- * compatible with PageSection (settings + responsive shape).
5
+ * Session 166: Removed V1 PageSection helpers (getRowSettingValue, hasRowSettingOverride, setRowResponsiveOverride).
7
6
  */
8
7
 
9
- import type { PageSection } from "../../../lib/sanity/types";
10
8
  import type { DeviceViewport } from "../../../lib/builder/types";
11
9
 
12
- /**
13
- * Generic shape for items with settings + responsive overrides.
14
- * Compatible with PageSection (and formerly Row).
15
- * Uses `unknown` to accept any settings interface without index signature issues.
16
- */
17
- type SettingsItem = {
18
- settings?: unknown;
19
- responsive?: Record<string, Record<string, unknown>>;
20
- };
21
-
22
- /** Get effective value for a section setting, checking viewport overrides */
23
- export function getRowSettingValue<T>(item: SettingsItem, viewport: DeviceViewport, property: string, fallback: T): T {
24
- if (viewport === "desktop") {
25
- const val = (item.settings as Record<string, unknown> || {})[property];
26
- return (val !== undefined ? val : fallback) as T;
27
- }
28
- const override = item.responsive?.[viewport]?.[property];
29
- if (override !== undefined) return override as T;
30
- const base = (item.settings as Record<string, unknown> || {})[property];
31
- return (base !== undefined ? base : fallback) as T;
32
- }
33
-
34
- /** Check if a section setting has a responsive override */
35
- export function hasRowSettingOverride(item: SettingsItem, viewport: DeviceViewport, property: string): boolean {
36
- if (viewport === "desktop") return false;
37
- return item.responsive?.[viewport]?.[property] !== undefined;
38
- }
39
-
40
- /** Set a responsive override on a section setting, returning the updated fields */
41
- export function setRowResponsiveOverride(item: SettingsItem, viewport: DeviceViewport, property: string, value: unknown): Partial<PageSection> {
42
- if (viewport === "desktop") {
43
- return { settings: { ...(item.settings as Record<string, unknown> || {}), [property]: value } } as Partial<PageSection>;
44
- }
45
- const existing = item.responsive || {};
46
- const vp = viewport as "tablet" | "phone";
47
- const vpOverrides = { ...(existing[vp] || {}), [property]: value };
48
- if (value === undefined) {
49
- delete (vpOverrides as Record<string, unknown>)[property];
50
- }
51
- const responsive = { ...existing, [vp]: vpOverrides };
52
- if (Object.keys(vpOverrides).length === 0) {
53
- delete responsive[vp];
54
- }
55
- return { responsive: Object.keys(responsive).length ? responsive : undefined } as Partial<PageSection>;
56
- }
57
-
58
10
  // ============================================
59
11
  // Block Layout responsive helpers
60
12
  // ============================================
@@ -11,7 +11,6 @@ import { BLOCK_GRADIENTS, BLOCK_ICON_COMPONENTS } from "../blockStyles";
11
11
  import type {
12
12
  ContentBlock,
13
13
  ContentItem,
14
- PageSection,
15
14
  PageSectionV2,
16
15
  CustomSectionInstance,
17
16
  ParallaxGroup,
@@ -19,7 +18,6 @@ import type {
19
18
  SectionColumn,
20
19
  } from "../../../lib/sanity/types";
21
20
  import {
22
- isPageSection,
23
21
  isPageSectionV2,
24
22
  isCustomSectionInstance,
25
23
  isParallaxGroup,
@@ -43,7 +41,6 @@ export function useSettingsPanelSelection() {
43
41
 
44
42
  // Find selected elements — handle page sections, V2 sections, and parallax groups/slides
45
43
  const selectedItem: ContentItem | undefined = store.rows.find((r) => r._key === store.selectedRowKey);
46
- const selectedSection: PageSection | null = selectedItem && isPageSection(selectedItem) ? selectedItem : null;
47
44
  const selectedSectionV2: PageSectionV2 | null = selectedItem && isPageSectionV2(selectedItem) ? selectedItem : null;
48
45
  const selectedCustomSectionInstance: CustomSectionInstance | null = selectedItem && isCustomSectionInstance(selectedItem) ? selectedItem as CustomSectionInstance : null;
49
46
 
@@ -76,15 +73,8 @@ export function useSettingsPanelSelection() {
76
73
  ? effectiveSectionV2.columns.find((c) => c._key === store.selectedColumnKey) || null
77
74
  : null;
78
75
 
79
- // For PageSections, the "block" is section.block[0] — selected automatically
80
76
  const selectedBlock: SelectedBlockInfo | null = (() => {
81
- // If a PageSection is selected, its block is the section block
82
- if (selectedSection) {
83
- const block = selectedSection.block[0];
84
- if (block) return { block, rowKey: selectedSection._key, colKey: "", isSection: true };
85
- return null;
86
- }
87
- // Regular block search inside rows, V2 sections, and parallax slides
77
+ // Block search inside V2 sections and parallax slides
88
78
  if (!store.selectedBlockKey) return null;
89
79
  for (const item of store.rows) {
90
80
  // V2 sections: search inside columns
@@ -130,8 +120,6 @@ export function useSettingsPanelSelection() {
130
120
  ? (selectedCustomSectionInstance.custom_section_title || "Saved Section")
131
121
  : selectedSectionV2
132
122
  ? "Section"
133
- : selectedSection
134
- ? (selectedSection.section_type === "projectGrid" ? "Project Grid" : "Parallax Section")
135
123
  : "Page";
136
124
 
137
125
  // Resolve gradient + icon component for the header
@@ -145,8 +133,6 @@ export function useSettingsPanelSelection() {
145
133
  ? "customSectionInstance"
146
134
  : selectedSectionV2
147
135
  ? "row"
148
- : selectedSection
149
- ? (selectedSection.block[0]?._type || "row")
150
136
  : "page";
151
137
  const headerGradient = BLOCK_GRADIENTS[headerStyleKey] || BLOCK_GRADIENTS.page;
152
138
  const HeaderIconComponent = BLOCK_ICON_COMPONENTS[headerStyleKey];
@@ -163,7 +149,6 @@ export function useSettingsPanelSelection() {
163
149
 
164
150
  return {
165
151
  selectedItem,
166
- selectedSection,
167
152
  selectedSectionV2,
168
153
  selectedCustomSectionInstance,
169
154
  selectedParallaxGroup,
@@ -57,11 +57,12 @@ export const ADMIN_ERROR_DARK = "#d42f1a";
57
57
  //
58
58
  // BLUE (#076bff) — Columns: outlines, resize handles, drag grip,
59
59
  // span badge, column selection/hover chrome.
60
- // ORANGE (#e28b00) — Blocks: "+ Add Block" buttons, block toolbar
60
+ // GREEN-B (#0d9668) — Blocks: "+ Add Block" buttons, block toolbar
61
61
  // pill, block selection ring, block-level actions.
62
- // GREEN (#22c55e) — Drop zones: gap drop targets during drag,
62
+ // BLUE (#076bff) — Drop zones: gap drop targets during drag,
63
63
  // insertion lines, "Drop Here" labels, swap target
64
- // highlight (green border + tinted background).
64
+ // highlight (blue border + tinted background).
65
+ // (Merged with column color for coherence.)
65
66
  // VIOLET (#8b5cf6) — Custom sections: saved section cards, custom
66
67
  // section instance badges, section editor chrome,
67
68
  // "Create New" custom section button.
@@ -72,7 +73,7 @@ export const ADMIN_ERROR_DARK = "#d42f1a";
72
73
  // while a delete button on a block toolbar is ORANGE.
73
74
 
74
75
  export const BUILDER_BLUE = "#076bff"; // Columns
75
- export const BUILDER_ORANGE = "#e28b00"; // Blocks
76
+ export const BUILDER_ORANGE = "#0d9668"; // Blocks (emerald — was orange #e28b00)
76
77
  export const BUILDER_GREEN = "#22c55e"; // Drop zones
77
78
  export const BUILDER_VIOLET = "#8b5cf6"; // Custom sections
78
79
 
@@ -5,7 +5,7 @@
5
5
  * Extracted from serializer.ts in Session 162.
6
6
  */
7
7
 
8
- import type { Page, ContentBlock, ContentItem, PageSection, PageSectionV2, SectionBlock, SectionColumn, SectionV2Settings, CustomSectionInstance, ParallaxGroup } from "../../../lib/sanity/types";
8
+ import type { Page, ContentBlock, ContentItem, PageSectionV2, SectionColumn, SectionV2Settings, CustomSectionInstance, ParallaxGroup } from "../../../lib/sanity/types";
9
9
  import type { BuilderState, PageSettings } from "../types";
10
10
  import { generateKey } from "../utils";
11
11
  import { DEFAULT_BG_COLOR, DEFAULT_TEXT_COLOR, DEFAULT_GRID_WIDTH } from "../constants";
@@ -18,39 +18,6 @@ import { migrateProjectGridV1ToV2, normalizeBlockAnimationFields } from "./migra
18
18
  // Section Normalizers
19
19
  // ============================================
20
20
 
21
- export function normalizePageSection(section: Partial<PageSection> & { _key: string }): PageSection {
22
- const ensuredBlock = ensureKeys((section.block || []) as SectionBlock[]) as (SectionBlock & { _key: string })[];
23
- const ensuredBlockType = ensuredBlock[0]?._type || "projectGridBlock";
24
- // BUG-022 fix: Preserve the original section_type even if unknown.
25
- // Only default when the field is missing entirely (new sections).
26
- const sectionType = section.section_type || (SECTION_TYPE_MAP[ensuredBlockType] || "projectGrid");
27
-
28
- // Warn on unknown types but don't override — future types should survive round-trips
29
- if (!Object.values(SECTION_TYPE_MAP).includes(sectionType)) {
30
- console.warn(`[Serializer] Unknown section_type: ${sectionType} — preserving as-is`);
31
- }
32
-
33
- // Auto-migrate projectGridBlock v1 → v2
34
- if (ensuredBlock[0]) {
35
- migrateProjectGridV1ToV2(ensuredBlock[0] as unknown as Record<string, unknown>);
36
- }
37
-
38
- // Session 117: Migrate animation fields on section blocks
39
- if (ensuredBlock[0]) {
40
- normalizeBlockAnimationFields(ensuredBlock[0] as unknown as Record<string, unknown>);
41
- }
42
-
43
- return {
44
- _type: "pageSection",
45
- _key: section._key,
46
- section_type: sectionType as import("../../../lib/sanity/types").PageSectionType,
47
- block: [ensuredBlock[0] || { _type: ensuredBlockType, _key: generateKey() }] as [SectionBlock],
48
- settings: section.settings || {},
49
- // BUG-013 fix: preserve responsive overrides for sections
50
- ...(section.responsive ? { responsive: section.responsive } : {}),
51
- };
52
- }
53
-
54
21
  /**
55
22
  * Normalize a PageSectionV2 from Sanity data into the builder's shape.
56
23
  * Ensures all columns have _keys, validates grid positions, and fills defaults.
@@ -177,14 +144,9 @@ export function normalizeParallaxGroup(group: Partial<ParallaxGroup> & { _key: s
177
144
  }
178
145
 
179
146
  /**
180
- * Normalize a content item (PageSection, PageSectionV2, ParallaxGroup, etc.).
147
+ * Normalize a content item (PageSectionV2, ParallaxGroup, CustomSectionInstance, etc.).
181
148
  */
182
149
  export function migrateContentItem(item: Record<string, unknown>): ContentItem {
183
- // PageSection — normalize and return
184
- if (item._type === "pageSection") {
185
- return normalizePageSection(item as unknown as Partial<PageSection> & { _key: string });
186
- }
187
-
188
150
  // PageSectionV2 — normalize and return
189
151
  if (item._type === "pageSectionV2") {
190
152
  return normalizePageSectionV2(item as unknown as Partial<PageSectionV2> & { _key: string });
@@ -5,8 +5,8 @@
5
5
  * Extracted from serializer.ts in Session 162.
6
6
  */
7
7
 
8
- import type { ContentBlock, ContentItem, PageSection, PageSectionV2, ParallaxGroup } from "../../../lib/sanity/types";
9
- import { isPageSection, isPageSectionV2, isCustomSectionInstance, isParallaxGroup } from "../../../lib/sanity/types";
8
+ import type { ContentBlock, ContentItem, PageSectionV2, ParallaxGroup } from "../../../lib/sanity/types";
9
+ import { isPageSectionV2, isCustomSectionInstance, isParallaxGroup } from "../../../lib/sanity/types";
10
10
  import type { BuilderState } from "../types";
11
11
  import { DEFAULT_BG_COLOR, DEFAULT_TEXT_COLOR } from "../constants";
12
12
 
@@ -67,74 +67,6 @@ function serializeBlock(block: ContentBlock): ContentBlock {
67
67
  return serialized;
68
68
  }
69
69
 
70
- // ============================================
71
- // Section Serialization
72
- // ============================================
73
-
74
- /**
75
- * Normalize section responsive overrides for serialization (save path).
76
- * Strips undefined/null/empty string values and removes empty viewport objects.
77
- */
78
- function normalizeSectionResponsiveForSerialize(
79
- responsive: PageSection["responsive"] | undefined
80
- ): PageSection["responsive"] | undefined {
81
- if (!responsive) return undefined;
82
-
83
- const result: NonNullable<PageSection["responsive"]> = {};
84
-
85
- for (const vp of ["tablet", "phone"] as const) {
86
- const overrides = responsive[vp];
87
- if (!overrides || typeof overrides !== "object") continue;
88
-
89
- const cleaned: Record<string, unknown> = {};
90
- for (const [key, val] of Object.entries(overrides)) {
91
- if (val === undefined || val === null) continue;
92
- if (typeof val === "string" && val.trim() === "") continue;
93
- cleaned[key] = val;
94
- }
95
-
96
- if (Object.keys(cleaned).length > 0) {
97
- result[vp] = cleaned as typeof result[typeof vp];
98
- }
99
- }
100
-
101
- return Object.keys(result).length > 0 ? result : undefined;
102
- }
103
-
104
- function serializePageSection(section: PageSection): Record<string, unknown> {
105
- const s = section.settings;
106
- return {
107
- _key: section._key,
108
- _type: "pageSection",
109
- section_type: section.section_type,
110
- block: (section.block || []).map((b) => serializeBlock(b as ContentBlock)),
111
- // BUG-013 fix: persist responsive overrides for sections (normalized: strips empty values)
112
- responsive: normalizeSectionResponsiveForSerialize(section.responsive),
113
- settings: s ? stripUndefined({
114
- background_color: s.background_color,
115
- background_opacity: s.background_opacity,
116
- background_image: s.background_image,
117
- background_size: s.background_size,
118
- background_position: s.background_position,
119
- background_repeat: s.background_repeat,
120
- spacing_top: s.spacing_top,
121
- spacing_right: s.spacing_right,
122
- spacing_bottom: s.spacing_bottom,
123
- spacing_left: s.spacing_left,
124
- offset_top: s.offset_top,
125
- offset_right: s.offset_right,
126
- offset_bottom: s.offset_bottom,
127
- offset_left: s.offset_left,
128
- border_color: s.border_color,
129
- border_width: s.border_width,
130
- border_style: s.border_style,
131
- border_sides: s.border_sides,
132
- border_radius: s.border_radius,
133
- enter_animation: s.enter_animation,
134
- }) : undefined,
135
- };
136
- }
137
-
138
70
  /**
139
71
  * Normalize V2 section responsive overrides for serialization.
140
72
  */
@@ -283,12 +215,9 @@ function serializeParallaxGroup(group: ParallaxGroup): Record<string, unknown> {
283
215
  // ============================================
284
216
 
285
217
  /**
286
- * Serialize a content item (PageSection or PageSectionV2) for Sanity.
218
+ * Serialize a content item for Sanity.
287
219
  */
288
220
  function serializeContentItem(item: ContentItem): Record<string, unknown> {
289
- if (isPageSection(item)) {
290
- return serializePageSection(item);
291
- }
292
221
  if (isPageSectionV2(item)) {
293
222
  return serializePageSectionV2(item);
294
223
  }
@@ -1,6 +1,6 @@
1
1
  import type { BuilderStore, BuilderState } from "./types";
2
- import type { ContentBlock, ContentItem, PageSection, PageSectionV2, ParallaxGroup, SectionBlock } from "../../lib/sanity/types";
3
- import { isPageSection, isPageSectionV2, isParallaxGroup } from "../../lib/sanity/types";
2
+ import type { ContentBlock, ContentItem, PageSectionV2, ParallaxGroup } from "../../lib/sanity/types";
3
+ import { isPageSectionV2, isParallaxGroup } from "../../lib/sanity/types";
4
4
  import { createDefaultBlock } from "./defaults";
5
5
  import { generateKey } from "./utils";
6
6
  import { findSectionPath, updateSectionAtPath, moveBlockInState } from "./store-helpers";
@@ -27,13 +27,6 @@ function applyBlockUpdate(
27
27
  }));
28
28
 
29
29
  return rows.map((item) => {
30
- if (isPageSection(item)) {
31
- const block = item.block[0];
32
- if (block && block._key === blockKey) {
33
- return { ...item, block: [{ ...block, ...updates } as SectionBlock] };
34
- }
35
- return item;
36
- }
37
30
  if (isPageSectionV2(item)) {
38
31
  return { ...item, columns: updateColumns(item.columns) };
39
32
  }
@@ -68,17 +61,8 @@ export function createBlockActions(set: StoreSet, get: StoreGet) {
68
61
  cols.map((c) => ({ ...c, blocks: c.blocks.filter((b) => b._key !== blockKey) }));
69
62
 
70
63
  set((state) => {
71
- // Handle PageSection: delete the entire section if its block is being deleted
72
- const updatedRows = state.rows.filter((item) => {
73
- if (isPageSection(item)) {
74
- const block = item.block[0];
75
- return !(block && block._key === blockKey);
76
- }
77
- return true;
78
- });
79
-
80
64
  // Handle V2 Sections + ParallaxGroup slides
81
- const finalRows = updatedRows.map((item) => {
65
+ const finalRows = state.rows.map((item) => {
82
66
  if (isPageSectionV2(item)) {
83
67
  return { ...item, columns: filterBlocks(item.columns) } as ContentItem;
84
68
  }
@@ -16,7 +16,7 @@ import type {
16
16
  SectionColumn,
17
17
  SectionV2Preset,
18
18
  } from "../../lib/sanity/types";
19
- import { isPageSection, isPageSectionV2, isParallaxGroup } from "../../lib/sanity/types";
19
+ import { isPageSectionV2, isParallaxGroup } from "../../lib/sanity/types";
20
20
  import { columnsFromPreset, detectPreset } from "./cascade";
21
21
  import { resizeColumnLeft as cascadeResizeLeft, moveColumn as cascadeMoveColumn, type ResizeLeftResult } from "./cascade";
22
22
  import { applyBlocksToColumns, toCascadeColumns, type CascadeColumn } from "./cascade-helpers";
@@ -62,7 +62,7 @@ export function moveBlockInState(
62
62
 
63
63
  // Pass 1: find and remove block from its current section (V2 + parallax slides)
64
64
  // RC-006 fix: Early-exit once block is found, consistent with selectBlock()
65
- // search pattern. Also searches PageSection V1 for completeness.
65
+ // search pattern.
66
66
  let rowsAfterRemove = rows;
67
67
  for (let i = 0; i < rows.length; i++) {
68
68
  const item = rows[i];