Skip to content

feat: theme aware map styling - #419

Merged
bdjulbic merged 8 commits into
mainfrom
feat/app-theme-aware-map-style
Mar 6, 2026
Merged

bdjulbic merged 8 commits into
mainfrom
feat/app-theme-aware-map-style

Conversation

@bdjulbic

@bdjulbic bdjulbic commented Mar 5, 2026 •

Copy link
Copy Markdown
Collaborator
  • New Features

    • Check app theme when map is created and assign an appropriate map theme detection utilities to support light and dark mode integration.
    • Map styling now automatically adapts to the current system theme setting.
  • Breaking Changes

    • Removed previously exposed public API members from the state interface.

Summary by CodeRabbit

  • New Features

    • Maps now use theme-aware initial state at creation/initialization so basemaps and styles match light/dark/system themes.
    • Theme utilities and related types exported so apps can query and resolve the current theme.
  • Documentation

    • Upgrade guide updated: map initialization is now callback-based (theme-aware). UI toast API switched to Sonner-style methods; a legacy compatibility export is provided.

@coderabbitai

coderabbitai Bot commented Mar 5, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces static map initial state with a theme-aware, context-driven initialization flow: adds CreateInitialMapKeplerStateContext, createDefaultMapKeplerState, createKeplerReducer(resolveInitialMapKeplerState), and routes map lifecycle operations through the new reducer factory with explicit reasons. Also expands and re-exports theme utilities and public Kepler types.

Changes

Cohort / File(s) Summary
Kepler slice — reducer factory & init API
packages/kepler/src/KeplerSlice.ts
Replaced static initialKeplerState with createInitialMapKeplerState(context) callback and added CreateInitialMapKeplerStateContext. Added createDefaultMapKeplerState(resolvedTheme), resolveInitialMapKeplerState, and createKeplerReducer(context). Updated map lifecycle paths to call createKeplerReducer({ reason, mapId, name })(...). Restored public imports from room-shell and theme utilities.
Kepler package exports
packages/kepler/src/index.ts
Exported new types: CreateInitialMapKeplerStateContext, CreateKeplerSliceOptions, KeplerSliceState; reordered exports (KeplerInjector moved).
UI package exports
packages/ui/src/index.ts
Expanded theme-provider exports: DEFAULT_THEME, DEFAULT_THEME_STORAGE_KEY, getResolvedTheme, getThemePreference, getTheme, and exported Theme/ResolvedTheme types in addition to ThemeProvider/useTheme.
Theme provider implementation
packages/ui/src/theme/theme-provider.tsx
Introduced Theme and ResolvedTheme types, DEFAULT_THEME and DEFAULT_THEME_STORAGE_KEY constants, and utilities getThemePreference, getResolvedTheme, getTheme. ThemeProvider now initializes via getThemePreference and applies resolved theme consistently.
Docs / Upgrade guide
docs/upgrade-guide.md
Documented breaking API change: initialKeplerState → createInitialMapKeplerState callback (provides { defaultInitialMapKeplerState } and access to theme). Noted UI toast API change to Sonner with legacyToast compatibility export.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant KeplerSlice
  participant ThemeUtils
  participant KeplerReducer
  participant MapStore

  Client->>KeplerSlice: createMap(request)
  KeplerSlice->>ThemeUtils: getTheme() / getResolvedTheme(...)
  ThemeUtils-->>KeplerSlice: ResolvedTheme
  KeplerSlice->>KeplerSlice: createDefaultMapKeplerState(resolvedTheme)
  KeplerSlice->>KeplerReducer: createKeplerReducer({reason: 'create-map', mapId, name})
  KeplerReducer-->>KeplerSlice: reducer(instance)
  KeplerSlice->>KeplerReducer: reducer(undefined, initAction)
  KeplerReducer-->>MapStore: initial map state
  MapStore-->>Client: map created with theme-aware initial state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: Command system implementation #382 — Adds room-shell command APIs (registerCommandsForOwner, unregisterCommandsForOwner, RoomCommand) that are reintroduced/used by the Kepler changes in this PR.

Poem

🐰 I sniff the theme, a whisper in the air,
I stitch a state that's cozy, bold, and fair,
A reducer hops to life with reason true,
Maps wake in sun or moon — a rabbit's woohoo!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: theme aware map styling' directly summarizes the main change: introducing theme-aware map styling that adapts to the current system theme, which is the primary objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/app-theme-aware-map-style

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/kepler/src/KeplerSlice.ts (1)

261-347: ⚠️ Potential issue | 🟠 Major

Reintroduce a kepler.destroy() lifecycle hook to preserve Room teardown behavior.

With this slice no longer exposing destroy, packages/room-store/src/BaseRoomStore.ts:48-82 and packages/room-store/src/BaseRoomStore.ts:167-177 won’t invoke Kepler cleanup during room destroy/HMR. That can leave stale Kepler state/resources across hot reload cycles.

♻️ Suggested fix
 export type KeplerSliceState = {
   kepler: {
@@
     initialize: () => Promise<void>;
+    destroy: () => Promise<void>;
@@
       kepler: {
@@
         async initialize() {
@@
         },
+        async destroy() {
+          set((state) =>
+            produce(state, (draft) => {
+              for (const mapId of Object.keys(draft.kepler.map)) {
+                draft.kepler.map = keplerReducer(
+                  draft.kepler.map,
+                  deleteEntry(mapId),
+                );
+                delete draft.kepler.forwardDispatch[mapId];
+              }
+              draft.kepler.__reduxProviderStore = undefined;
+            }),
+          );
+        },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/kepler/src/KeplerSlice.ts` around lines 261 - 347, Add a
kepler.destroy() lifecycle method to the KeplerSlice that performs the same
cleanup BaseRoomStore expects on room destroy/HMR: implement a destroy method on
the returned kepler object that (1) cancels or stops any ongoing sync/requests
(eg. calls/flags used by syncKeplerDatasets/requestMapStyle), (2) clears
forwardDispatch, (3) sets __reduxProviderStore to undefined, (4) resets
kepler.map to {} (or keplerGlReducer.initialState if preferred) and (5) removes
any subscriptions/listeners created in initialize; reference the KeplerSlice
functions/fields (initialize, syncKeplerDatasets, requestMapStyle,
forwardDispatch, __reduxProviderStore, kepler.map) and invoke those cleanup
steps inside kepler.destroy so BaseRoomStore’s destroy/HMR paths will correctly
teardown Kepler.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/kepler/src/KeplerSlice.ts`:
- Around line 60-66: In getBasemapForTheme, guard the call to window.matchMedia
before invoking it by checking that typeof window !== 'undefined' && typeof
window.matchMedia === 'function' (or window.matchMedia != null) so environments
without matchMedia won't throw; if matchMedia is unavailable, treat 'system' as
a default (e.g., 'light') or use a sensible fallback, then map the resolvedTheme
to the basemap string as before.

---

Outside diff comments:
In `@packages/kepler/src/KeplerSlice.ts`:
- Around line 261-347: Add a kepler.destroy() lifecycle method to the
KeplerSlice that performs the same cleanup BaseRoomStore expects on room
destroy/HMR: implement a destroy method on the returned kepler object that (1)
cancels or stops any ongoing sync/requests (eg. calls/flags used by
syncKeplerDatasets/requestMapStyle), (2) clears forwardDispatch, (3) sets
__reduxProviderStore to undefined, (4) resets kepler.map to {} (or
keplerGlReducer.initialState if preferred) and (5) removes any
subscriptions/listeners created in initialize; reference the KeplerSlice
functions/fields (initialize, syncKeplerDatasets, requestMapStyle,
forwardDispatch, __reduxProviderStore, kepler.map) and invoke those cleanup
steps inside kepler.destroy so BaseRoomStore’s destroy/HMR paths will correctly
teardown Kepler.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e83a4c1-d9cc-40df-889f-ed8868476771

📥 Commits

Reviewing files that changed from the base of the PR and between bcd4f79 and 1c327f1.

📒 Files selected for processing (2)
  • packages/kepler/src/KeplerSlice.ts
  • packages/kepler/src/index.ts

Comment thread packages/kepler/src/KeplerSlice.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/kepler/src/KeplerSlice.ts (1)

65-71: ⚠️ Potential issue | 🟡 Minor

Guard window.matchMedia before calling it.

Line 70 still assumes matchMedia exists whenever window does. In tests, jsdom, and some embedded browser contexts that can throw during createKeplerSlice() initialization instead of falling back to a default theme.

Suggested fix
   const resolvedTheme =
     theme === 'system'
       ? typeof window !== 'undefined' &&
+        typeof window.matchMedia === 'function' &&
         window.matchMedia('(prefers-color-scheme: dark)').matches
         ? 'dark'
         : 'light'
       : theme;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/kepler/src/KeplerSlice.ts` around lines 65 - 71, The
getBasemapForTheme function calls window.matchMedia unguarded; update it to
check that typeof window !== 'undefined' AND typeof window.matchMedia ===
'function' (or window.matchMedia !== undefined) before invoking it, so when
window exists but matchMedia is missing (e.g., jsdom/tests) it falls back to a
safe default theme (e.g., 'light') and continues to return the correct basemap
string; adjust the resolvedTheme logic inside getBasemapForTheme (and any
callers like createKeplerSlice initialization) to use this guarded check and
fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/kepler/src/KeplerSlice.ts`:
- Around line 302-304: The code resolves the basemap once (using
getBasemapForTheme(getCurrentThemeFromDOM())) so existing maps'
kepler.map[*].mapStyle.styleType never update on theme changes; to fix,
subscribe to theme changes (or expose a theme-change handler) and, when theme
updates, iterate existing maps (kepler.map.forEach or equivalent) and set each
map.mapStyle.styleType = getBasemapForTheme(newTheme) and trigger the map
update/refresh path you already use in createMap so the new style is applied;
update KeplerSlice where mapStyle is initialized and/or augment createMap to
register that theme-change listener and clean it up on teardown.

---

Duplicate comments:
In `@packages/kepler/src/KeplerSlice.ts`:
- Around line 65-71: The getBasemapForTheme function calls window.matchMedia
unguarded; update it to check that typeof window !== 'undefined' AND typeof
window.matchMedia === 'function' (or window.matchMedia !== undefined) before
invoking it, so when window exists but matchMedia is missing (e.g., jsdom/tests)
it falls back to a safe default theme (e.g., 'light') and continues to return
the correct basemap string; adjust the resolvedTheme logic inside
getBasemapForTheme (and any callers like createKeplerSlice initialization) to
use this guarded check and fallback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b509e0a9-c3d1-47e2-822d-1995ee4a2a84

📥 Commits

Reviewing files that changed from the base of the PR and between 1c327f1 and bd0327f.

📒 Files selected for processing (1)
  • packages/kepler/src/KeplerSlice.ts

Comment thread packages/kepler/src/KeplerSlice.ts Outdated
Comment on lines +302 to +304
mapStyle: {
styleType: getBasemapForTheme(getCurrentThemeFromDOM()),
} as MapStyle,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This only themes newly created state, not existing maps.

The basemap is resolved once for the initial state and once inside createMap(). After that, theme changes only update the HTML class in packages/ui/src/theme/theme-provider.tsx:91-108; nothing here propagates the new resolved theme to kepler.map[*].mapStyle.styleType, so already-open maps stay on the old basemap after a theme toggle or a system dark/light change.

Also applies to: 499-518

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/kepler/src/KeplerSlice.ts` around lines 302 - 304, The code resolves
the basemap once (using getBasemapForTheme(getCurrentThemeFromDOM())) so
existing maps' kepler.map[*].mapStyle.styleType never update on theme changes;
to fix, subscribe to theme changes (or expose a theme-change handler) and, when
theme updates, iterate existing maps (kepler.map.forEach or equivalent) and set
each map.mapStyle.styleType = getBasemapForTheme(newTheme) and trigger the map
update/refresh path you already use in createMap so the new style is applied;
update KeplerSlice where mapStyle is initialized and/or augment createMap to
register that theme-change listener and clean it up on teardown.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is ok, we don't want to change theme of other maps, just the newly created ones

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/ui/src/theme/theme-provider.tsx (1)

80-87: Consider guarding window.matchMedia before invoking it.

While window is checked for SSR, matchMedia may not exist in certain test environments (e.g., Jest/jsdom without mocking). If tests don't mock matchMedia, this will throw.

🛡️ Optional defensive fix
 export function getResolvedTheme(theme: Theme): ResolvedTheme {
   if (theme !== 'system') return theme;
   if (typeof window === 'undefined') return 'light';
 
-  return window.matchMedia('(prefers-color-scheme: dark)').matches
+  return typeof window.matchMedia === 'function' &&
+    window.matchMedia('(prefers-color-scheme: dark)').matches
     ? 'dark'
     : 'light';
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/theme/theme-provider.tsx` around lines 80 - 87,
getResolvedTheme currently calls window.matchMedia without ensuring matchMedia
exists, which can throw in some test/envs; update the getResolvedTheme function
to guard access by checking typeof window.matchMedia === 'function' (or
Boolean(window.matchMedia)) before invoking it and return a safe fallback
('light' or the non-dark branch) when matchMedia is unavailable so SSR/tests
won't throw.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ui/src/theme/theme-provider.tsx`:
- Around line 80-87: getResolvedTheme currently calls window.matchMedia without
ensuring matchMedia exists, which can throw in some test/envs; update the
getResolvedTheme function to guard access by checking typeof window.matchMedia
=== 'function' (or Boolean(window.matchMedia)) before invoking it and return a
safe fallback ('light' or the non-dark branch) when matchMedia is unavailable so
SSR/tests won't throw.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e0f19456-b9e6-4f5f-ad84-4f0a1eabf90e

📥 Commits

Reviewing files that changed from the base of the PR and between 4575b21 and 6f56df6.

📒 Files selected for processing (5)
  • docs/upgrade-guide.md
  • packages/kepler/src/KeplerSlice.ts
  • packages/kepler/src/index.ts
  • packages/ui/src/index.ts
  • packages/ui/src/theme/theme-provider.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/kepler/src/index.ts

Signed-off-by: Ilya Boyandin <[email protected]>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/ui/src/theme/theme-provider.tsx (1)

19-22: Consider exporting GetThemeOptions for consumers.

The GetThemeOptions type is used in the public API (getThemePreference and getTheme function signatures) but is not exported. Consumers who want to type their options when calling these functions would benefit from having this type available.

♻️ Suggested change
-type GetThemeOptions = {
+export type GetThemeOptions = {
   defaultTheme?: Theme;
   storageKey?: string;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/theme/theme-provider.tsx` around lines 19 - 22, The
GetThemeOptions type is used in the public API (getThemePreference and getTheme)
but isn't exported; make it part of the public types by exporting it (e.g., add
`export` to GetThemeOptions) so consumers can import and use this type when
calling getThemePreference/getTheme; ensure the exported name matches the
existing references (GetThemeOptions) and update any barrel/index files if
needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ui/src/theme/theme-provider.tsx`:
- Around line 19-22: The GetThemeOptions type is used in the public API
(getThemePreference and getTheme) but isn't exported; make it part of the public
types by exporting it (e.g., add `export` to GetThemeOptions) so consumers can
import and use this type when calling getThemePreference/getTheme; ensure the
exported name matches the existing references (GetThemeOptions) and update any
barrel/index files if needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6161d5e-20ce-4246-8d82-88250094c2d5

📥 Commits

Reviewing files that changed from the base of the PR and between 6f56df6 and cd3a9ca.

📒 Files selected for processing (1)
  • packages/ui/src/theme/theme-provider.tsx

@bdjulbic
bdjulbic merged commit e34f3b4 into main Mar 6, 2026
4 checks passed
@bdjulbic
bdjulbic deleted the feat/app-theme-aware-map-style branch March 6, 2026 12:43
dmitriy-kostianetskiy pushed a commit that referenced this pull request Mar 9, 2026
Signed-off-by: Ilya Boyandin <[email protected]>
Co-authored-by: Ilya Boyandin <[email protected]>
bdjulbic added a commit that referenced this pull request Mar 16, 2026
Signed-off-by: Ilya Boyandin <[email protected]>
Co-authored-by: Ilya Boyandin <[email protected]>
Signed-off-by: bdjulbic <[email protected]>
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.

2 participants