Skip to content

Typescript port - #85

Merged
meetzaveri merged 16 commits into
devfrom
typescript-port
May 16, 2026
Merged

Typescript port#85
meetzaveri merged 16 commits into
devfrom
typescript-port

Conversation

@meetzaveri

Copy link
Copy Markdown
Owner

Migration from JS-> TS codebase alongside implementing types and interfaces for applicable code carried with help of claude.

Note - Not all of the elements,files or components have implemented full type checking /compliant spec as few of still contain escape hatches using any (loose migration) . All hooks, components which are needed for board context gets explicit types.

meetzaveri and others added 16 commits May 12, 2026 18:54
Set up the foundation for the JS → TS migration. No source files changed.

- tsconfig: strict, react-jsx, bundler resolution, allowJs:true so existing
  .js keeps compiling during incremental conversion
- graphql-codegen: typescript + typescript-operations + typed-document-node
  plugins, points at live Hasura via VITE_GRAPHQL_ENDPOINT + admin secret
- src/types/two.d.ts: module augmentation for Two.js internals we touch
  (subtractions, _flagSubtractions, renderer.elem, ZUI extras)
- src/types/global.d.ts: ImportMetaEnv shape, svgr client types, idx shim
- package.json: yarn typecheck (tsc --noEmit) and yarn codegen scripts
- @types/react pinned to ^18.3 to match runtime

Known: codegen surfaces pre-existing duplicate operation names across
queries/subscriptions (MyQuery, getComponentsForBoard, getBoardComponents).
Will be resolved when schema files convert in stage 3.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Convert src/lib.js → src/lib.ts and add .d.ts companions for the JS files
it re-exports. Consumers (craftmaps + future ones) now get real types for
the Board component, BoardContext, useBoardContext, and ComponentRecord.

New files:
- src/types/board.ts: canonical types — BoardProps, BoardContextValue,
  ComponentRecord (mirrors the Hasura schema), ComponentStore, hook value
  shapes. Internal-handler signatures stay loose; tightened in stages 4–10
  as their source files convert.
- src/views/Board/index.d.ts: <Board /> typed as ComponentType<BoardProps>.
- src/views/Board/board.d.ts: BoardContext + useBoardContext declarations.
  Stage 10 will fold this into a typed source and decide on a strict
  null-throwing useBoardContext.
- src/schema/mutations/index.d.ts: INSERT_USER_ONE as DocumentNode shim.
- src/utils/misc.d.ts: generateRandomUsernames typed via RandomUsername.

package.json: main / module / types / exports flipped to src/lib.ts.
Vite consumers resolve .ts via esbuild natively; the link: symlink setup
is unaffected.

yarn typecheck passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Convert all pure-logic + light-Two.js files in src/utils, src/constants, and
the root serviceWorker. No behavior changes — just types.

Fully typed (pure logic):
- src/constants/misc.ts, elementSchema.ts, exportHooks.ts
- src/utils/constants.ts (svgr icons typed as FunctionComponent<SVGProps>)
- src/utils/drawModeUtils.ts, misc.ts, groupInspect.ts, pencilHelper.ts
- src/serviceWorker.ts

Loose-typed (heavy Two.js interop, narrows in Stages 7–9):
- src/utils/updateVertices.ts — Anchor / Commands constructor namespace
- src/utils/canvasUtils.ts — scene bookkeeping (.elementData, etc.)
- src/utils/applyProperty.ts, applyGroupProperty.ts — selectedComponent
  scaffolding from canvas

Also removed:
- src/utils/misc.d.ts shim — superseded by misc.ts as source of truth.

yarn typecheck passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GraphQL schema (typed via graphql-codegen):
- Generated src/schema/generated.ts (3109 lines) from the live Hasura
  introspection. Mirrors typescript + typescript-operations + typed-document-
  node plugins; pinned scalars (uuid→string, bigint→number, jsonb→unknown).
- queries / mutations / subscriptions index files converted to .ts. Each
  exported gql is typed as TypedDocumentNode<Result, Variables> from
  '@apollo/client', so useQuery / useMutation / useSubscription will infer
  the right data and variables shapes once their callers convert.

Pre-existing fixes surfaced by codegen validation:
- Renamed 3 duplicate subscription operations whose names clashed with the
  query side:
    MyQuery → userDetailsSubscription
    getBoardComponents → getBoardComponentsSubscription
    getComponentsForBoard → getComponentsForBoardSubscription
- Aligned 5 variable types with the actual schema (no runtime behavior
  change — Hasura was already coercing):
    $id: uuid → $id: String         (users_user.id is String)
    $boardId: String(!) → $boardId: uuid(!) (components.boardId is uuid)
- Renamed mutation operation `UPDATE_COMPONENT_INFO` → `updateComponentInfo`
  for codegen name parity (camelCase) with the other mutations.

Also removed:
- src/schema/mutations/index.d.ts shim — superseded by the .ts file.

Factories (src/factory/*.ts):
- Main.ts is generic over its properties shape; each factory binds a
  concrete shape (RectangleProperties, CircleProperties, DiamondProperties,
  DividerProperties, ArrowLineProperties, NewArrowLineProperties,
  NewTextProperties, PencilProperties).
- Two.js instance + shape returns are typed loosely (`any`) where the
  published Two.js types don't cover the methods/fields we use. These
  sharpen in stages 7–9 when scene types converge.
- `parseInt(prevX)` calls wrapped in String() casts to satisfy strict
  typing without changing runtime behavior.

yarn typecheck + yarn codegen both pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All six custom hooks now have explicit option/return interfaces. Internal
Two.js scene access stays loose; the canvas-side typing converges in
Stages 7–9 where these refs originate.

Hooks:
- useMobileToolbarPanels: MobileToolbarPanelsOptions + Api
- useDrawingModes: DrawingModeToggleOptions + DrawingModesApi; toggle
  helpers are explicit Dispatch<SetStateAction<...>> at the boundary
- useElementDefaults: ElementDefaultsState + ElementDefaultsApi;
  TextSizeLabel union for the size enum
- useLocalDraftPersistence: LocalDraftPersistenceOptions/Api; ComponentStore
  + Partial<ComponentStore> at boundaries; PersistedDraft for the on-disk
  shape
- useCanvasClipboard: CanvasClipboardOptions/Api with a
  SingleClipboardPayload | GroupClipboardPayload union for the in-memory
  clipboard; per-call casts only where elementData carries arrow/pencil
  fields not on the shared ComponentRecord
- useComponentHistory: HistoryEntry discriminated union over
  ADD | DELETE | UPDATE_VERTICES | UPDATE_BULK | BATCH. undo/redo
  dispatchers narrow on .action, removing the need for casts in
  applyRemove/applyInsert/applyVertices/applyBulkProps/applyBatch.

yarn typecheck passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
React components with no Two.js coupling — easy wins. All file extensions
flip to .tsx where there's JSX, and Icon class drops its prop-types
declaration in favor of a TS interface.

Converted:
- src/components/common/portal.tsx — ReactPortal return; parent ?? document.body
  fix (TS flagged the always-truthy method-reference check on appendChild)
- src/components/common/spinnerWithSize.tsx — LoaderSize union ('xs'|'sm'|
  'md'|'lg'), typed customStyles as CSSProperties
- src/components/common/spinner.tsx
- src/components/common/button.tsx — ButtonIntent / ButtonSize unions
- src/components/common/modal.tsx — typed refs (HTMLDivElement), narrowed
  event handlers (KeyboardEvent, MouseEvent), fixed the className={active &&
  open && 'active'} expression to return a string
- src/components/common/modalContainer.tsx
- src/components/modals/PermissionErrorModal.tsx
- src/components/modals/StorageLimitModal.tsx
- src/icons/icons.ts — added IconData interface; Record<string, IconData>
- src/icons/icon.tsx — dropped prop-types, used IconProps interface,
  optional-chained Icons[icon] lookup so noUncheckedIndexedAccess is safe

yarn typecheck passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sixteen React + class files converted. Internal Two.js scene access stays
loose (`any`); canvas-side typing converges in Stages 7–9.

Converted (alphabetical):
- src/components/ZoomControls.tsx
- src/components/sidebar/menuDrawer.tsx
- src/components/sidebar/primary.tsx — typed shapeData via ComponentRecord;
  filled in the DB-required keys (x1/y1/y2, radius, iconStroke, isDummy,
  createdAt, boardName) that the JS version was relying on Hasura defaults
  for. No runtime behavior change.
- src/components/sidebar/shapesToolbar.tsx — DrawerAnchor interface; uses
  PrimaryElement type from utils/constants.
- src/components/sidebar/shareLinkPopup.tsx
- src/components/sidebar/userDetailsPopup.tsx
- src/components/sidebar/elementProperties.tsx (577 lines, biggest one) —
  ResolveSetKeyOptions + ReadEffectiveValuesOptions interfaces; per-row
  prop shapes (StrokeWidthRow, StrokeTypeRow, TextSizeRow, FontFamilyRow,
  SectionLabel) typed inline; SETS / SET_LABELS indexed with `as keyof`.
- src/components/utils/borderStyleBox.tsx — StrokeTypeValue derived union
  from STROKE_TYPES `as const`.
- src/components/utils/colorPicker.tsx — dropped prop-types
- src/components/utils/dragger.ts (heavy Two.js, params kept loose)
- src/components/utils/editWrapper.ts
- src/components/utils/loader.tsx
- src/components/utils/objectSelector.ts (Two.js Selector class)
- src/components/utils/opacitySlider.tsx — dropped prop-types
- src/components/utils/toolbarConnector.ts
- src/components/utils/zoomer.tsx

BoardContextValue audit + fixes (`src/types/board.ts`):
- persistBoard typed as `() => Promise<string>` (was `() => void`).
- addToLocalComponentStore signature corrected to match board.js:
  `(id: string, type: string, info: ComponentRecord, skipHistory?: boolean) => void`.
- updateComponentVerticesInLocalStore signature corrected:
  `(id, x, y) => void` instead of `(id, Partial<ComponentRecord>)`.
- updateComponentBulkPropertiesInLocalStore gains optional skipDbWrite.
- CurrentElement narrowed from `unknown` to `string` (active tool name).

yarn typecheck passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Nine Two.js-bound React components. Loose ElementProps + ShapeLike `any`
types throughout — these props come from ElementRenderWrapper / canvas /
factories whose internals stay loose during the migration. Stage 12 cleanup
will sweep what we can after newCanvas converges in Stage 9.

Converted:
- src/components/elements/circle.tsx
- src/components/elements/rectangle.tsx
- src/components/elements/diamond.tsx
- src/components/elements/pencil.tsx
- src/components/elements/arrowLine.tsx
- src/components/elements/divider.tsx
- src/components/elements/newText.tsx (largest — text input overlay,
  proportional resize via corner handles)
- src/components/elements/groupobject.tsx — focused-group orchestration;
  Two.js scene.subtractions safety pattern from CLAUDE.md preserved
- src/components/utils/elementRenderWrappers.tsx — typed as
  ComponentType<ElementProps> factories

Common patterns introduced:
- `useImmer<Record<string, any>>({})` for the internalState bags
- DOM getElementById results captured into `const el = ...; if (el) ...`
  blocks rather than chained `.style` access; satisfies strict null checks
- Event handlers receive concrete DOM types (FocusEvent, KeyboardEvent,
  MouseEvent) at the boundary; everything past `event.target` falls back to
  `any` via cast.
- Two.js constructor-namespace access (Two.Anchor, Two.Commands) goes
  through `(Two as any)` since @types/two.js doesn't expose those.
- factoryModules glob updated to '.ts' to match Stage 3's converted
  factories.

yarn typecheck passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
src/canvas/selectionController.ts: 676-line class converted with explicit
field declarations, typed listener slots, and a discriminated Interaction
union for the scale/rotate handle drag. ShapeAdapter formalized as an
interface; SHAPE_ADAPTERS is now Record<string, ShapeAdapter>. Two.js
constructor access (Group/Rectangle/Points/Vector) goes through `(Two as
any)` since @types/two.js doesn't expose those.

yarn typecheck passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Type addZUI's 15 params, all event handlers (MouseEvent/TouchEvent/
WheelEvent/KeyboardEvent), the Canvas component (React.FC<CanvasProps>),
and Canvas-local callbacks. Two.js scene shapes stay `any` with eslint
disables per the Stage 2/7 convention; refs get explicit nullable
types. resolveShapeFromPath in canvasUtils accepts EventTarget[] since
DOM event paths are EventTarget[], not Element[].

Drive-by: elementModules lookup in handleSetComponentsToRender was
using `\${componentType}.js` after the glob was updated to *.tsx,
which would have silently skipped every component on a fresh render.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rename board.js → board.tsx. Type BoardContext as
<BoardContextValue | undefined>; useBoardContext now throws when called
outside <Board />. All useState/useRef nulls get explicit types,
callbacks get param types, and Apollo error handlers get any-typed
error params per Stage 2/7 convention.

Delete board.d.ts shim; types are now sourced from the .tsx file
itself.

Tighten BoardContextValue: togglePencilMode/Pointer/PanMode and
createTextAtSurface get real signatures (were (...args: unknown[])).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Convert App.js, index.js, routes.js, setupTests.js, and all view
container/errorBoundary files. Error boundaries get explicit
Props/State generics + override modifiers (noImplicitOverride). DOM
roots and Sentry context values are null-safe. Delete the
views/Board/index.d.ts shim (no longer needed).

setupTests imports '@testing-library/jest-dom' directly — the
deprecated /extend-expect entrypoint was removed in v6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- tsconfig: allowJs:false (no straggler .js files in src/; only
  src/types/*.d.ts module augmentation remain).
- vite.config.mjs: remove the treat-js-files-as-jsx plugin and the
  `'.js': 'jsx'` optimizeDeps loader override — both were JSX-in-.js
  workarounds for the migration.
- package.json: drop prop-types and @types/prop-types (no propTypes
  blocks remain in src/).
- CLAUDE.md: update file paths to .ts/.tsx, add a TypeScript line to
  the Technology Stack, and note the bundler-glob heads-up for
  consumers (craftmaps) — `node_modules/craftbase/src/**/*.{ts,tsx}`
  replaces the previous .{js,jsx} glob in vite + tailwind content.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@netlify

netlify Bot commented May 16, 2026

Copy link
Copy Markdown

Deploy Preview for craftbase ready!

Name Link
🔨 Latest commit 50ee929
🔍 Latest deploy log https://app.netlify.com/projects/craftbase/deploys/6a082fb1c98201000867f865
😎 Deploy Preview https://deploy-preview-85--craftbase.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@meetzaveri

Copy link
Copy Markdown
Owner Author

Tests failing for either of the reasons

  • Connection timeout
  • Timeout while performing test

I've validated these all tests by running locally as well as failed test case by checking them on preview app. LGTM. This issue of timeout sometime happens which we can resolve in future. For now, by manual testing i've verified it works as expected. Closing this.

@meetzaveri
meetzaveri merged commit 1747117 into dev May 16, 2026
4 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant