Skip to content

ENG-1454: Base getter() task#828

Open
sid597 wants to merge 51 commits intomainfrom
migration-block-init-staging-branch
Open

ENG-1454: Base getter() task#828
sid597 wants to merge 51 commits intomainfrom
migration-block-init-staging-branch

Conversation

@sid597
Copy link
Collaborator

@sid597 sid597 commented Feb 25, 2026

#DO NOT MERGE - until all the other prs are


Open with Devin

@linear
Copy link

linear bot commented Feb 25, 2026

@supabase
Copy link

supabase bot commented Feb 25, 2026

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines 241 to +244
export const QuerySettingsSchema = z.object({
"Hide query metadata": z.boolean().default(false),
"Hide query metadata": z.boolean().default(true),
"Default page size": z.number().default(10),
"Query pages": z.array(z.string()).default([]),
"Query pages": z.array(z.string()).default(["discourse-graph/queries/*"]),
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot Mar 1, 2026

Choose a reason for hiding this comment

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

🟡 Changed QuerySettingsSchema defaults corrupt legacy-path fallbacks

The QuerySettingsSchema defaults were changed ("Hide query metadata" from falsetrue, "Query pages" from []["discourse-graph/queries/*"]). These new defaults propagate into DEFAULT_PERSONAL_SETTINGS (computed at module load via PersonalSettingsSchema.parse({}) at accessors.ts:165), which is then used as the fallback value in getLegacyPersonalSetting().

Root cause and impact

In getLegacyPersonalSetting (accessors.ts:212-251), when a mapped key is found, the fallback default is read from DEFAULT_PERSONAL_SETTINGS:

return getSetting<unknown>(
  mappedOldKey,
  readPathValue(DEFAULT_PERSONAL_SETTINGS, keys),
);

For keys = ["Query", "Hide query metadata"], this resolves to getSetting("hide-metadata", true). If the user has never explicitly set this value, getSetting returns true (the new default) instead of false (the old default). The intent of the legacy path is backward compatibility with the old system, but the fallback now returns different values than the old system provided.

Concretely:

  • Users who never explicitly set "Hide query metadata" will see query metadata hidden when it was previously shown.
  • Users who never explicitly set "Query pages" will get ["discourse-graph/queries/*"] instead of [].

The getters aren't called yet (being built in this PR), but when they are wired up, the legacy path will silently change behavior for users who haven't explicitly configured these settings.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 24 additional findings in Devin Review.

Open in Devin Review

Comment on lines +419 to +427
getGlobalSetting<boolean>([
"Suggestive mode",
"Include current page relations",
]) ?? false;
const shouldGrabParentChildContext =
(extensionAPI.settings.get(
"context-grab-parent-child-context",
) as boolean) ?? true;
getGlobalSetting<boolean>([
"Suggestive mode",
"Include parent and child blocks",
]) ?? false;
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot Mar 3, 2026

Choose a reason for hiding this comment

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

🔴 Hyde search defaults changed from true to false, degrading suggestive mode search quality

In hyde.ts, the old code read shouldGrabFromReferencedPages and shouldGrabParentChildContext from phantom extensionAPI keys (context-grab-from-referenced-pages, context-grab-parent-child-context) that were never written anywhere in the codebase. Since the keys never existed, extensionAPI.settings.get(...) always returned undefined, and the ?? true fallback ensured both flags were always true. The new code reads from the global accessor with a ?? false fallback. The schema defaults for "Include current page relations" and "Include parent and child blocks" are also false. This means for users who never explicitly configured these settings (which is everyone, since the UI writes to different keys), the behavior silently changes from always including referenced pages and parent/child blocks in the search to never including them. This degrades suggestive mode hyde search results.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

sid597 added 11 commits March 3, 2026 18:26
* ENG-1454: Enable dual read feature flag

* add caller

* resolve merge conflicts

* review

* example fix

* review
* ENG-1455: Dual-read from old-system settings and from blockprops

* remove console.logs

* address review

* prettier

* add comment describing why we do it like this

* reviewed

* rebase, review

* address review

* explicit throw, now zodschema default reads

* replace throw with warn, throw was too strong, with warn we test if legacy and block props are same
…ult reads (#813)

* ENG-1472: Refactor BlockPropSettingPanels to add accessor-backed default reads (with initialValue fallback)

* review, fix
* ENG-1473: Rebase onto updated eng-1472, resolve conflicts and fix missing import

* ENG-1473: Review fixes — remove dead extensionAPI params, fix type casts

* ENG-1473: Fix restrict-template-expressions warnings in query utils

* rename persistQueryPages → setQueryPages, setQueryPages → setInitialQueryPages

* restore legacy type coercion for query-pages
* ENG-1467: Port global setting consumer reads (→ getGlobalSetting)

* prettier

* rebase onto updated eng-1472, resolve conflicts and fix tsc
* ENG-1479: Port suggestive mode settings to dual-read

Add dual-read routing to getFeatureFlag for flags with legacy counterparts.
FEATURE_FLAG_LEGACY_MAP maps "Suggestive mode enabled" and "Enable left
sidebar" to their config tree reads, gated by isNewSettingsStoreEnabled().
Flags without legacy entries go straight to block props (no behavior change).

Migrate value reads from getFormattedConfigTree() / extensionAPI.settings
to accessors:

- index.ts: getUidAndBooleanSetting → getFeatureFlag (accessor handles
  legacy fallback now)
- AdminPanel: suggestiveModeEnabled.value → getFeatureFlag in both
  FeatureFlagsTab and main component. Removed unused useMemo.
- SuggestiveModeSettings: includePageRelations state init →
  getGlobalSetting. Dropped initialValue from both GlobalFlagPanels.
- hyde.ts: orphan extensionAPI.settings.get() reads →
  getGlobalSetting. Fixes bug where sync config toggles had no
  runtime effect (keys were never written by any code).

Structural UIDs remain with getFormattedConfigTree().
pageGroups.groups NOT migrated — type mismatch (legacy PageGroup has
UIDs, Zod PageGroup does not). Deferred to ENG-1470.

* Align hyde.ts fallback defaults with Zod schema

* Fix eslint naming-convention warnings in FEATURE_FLAG_LEGACY_MAP

* Add TODO(ENG-1484) for suggestive mode reactivity
* Rebase ENG-1468 onto eng-1472 (fresh redo)

* Remove unnecessary lazy initializer from complement useState

* Break circular dep: inline DISCOURSE_CONFIG_PAGE_TITLE

* Address review: case-insensitive attribute lookup, empty-string fallback, move DISCOURSE_CONFIG_PAGE_TITLE to data/constants
…er-based reactivity

- Extract mergeGlobalSectionWithAccessor/mergePersonalSectionsWithAccessor to
  getLeftSidebarSettings.ts (used by LeftSidebarView, GlobalSettings, PersonalSettings)
- Settings panels now actually use accessor values (not fire-and-forget)
- Replace setLeftSidebarFlagHandler with settingsEmitter pub/sub
- useConfig subscribes to emitter instead of old subscribe()
- Wire up global/personal/flag handlers in pullWatchers to emit
- Remove dead newValue===oldValue guard (hasPropChanged already checks)
- Remove dead ?./?? in personal merge (Zod defaults guarantee values)
toggleFoldedState mutates folded.uid in-place then dual-writes to
block props, triggering pull watch → emitter → buildConfig(). Without
refreshConfigTree(), buildConfig() reads stale UIDs from the cached
tree, overwriting the in-place mutation and orphaning Roam blocks.
@sid597 sid597 force-pushed the migration-block-init-staging-branch branch from 21b6000 to 3b4c80a Compare March 3, 2026 13:03
sid597 added 2 commits March 9, 2026 12:13
…l-and-personal-left-sidebar-to-dual-readers-v2

ENG-1478: Port left sidebar to dual-readers + reactivity
…courserelations-and-getdiscoursenodes-to-read

ENG-1469: Refactor getDiscourseRelations and getDiscourseNodes to read from block props
devin-ai-integration[bot]

This comment was marked as resolved.

sid597 added 21 commits March 9, 2026 16:33
…m settingKeys.ts

Replaces all raw string literals used as block-prop accessor paths in consumer files
with typed constants from settingKeys.ts. Adds TEMPLATE_SETTING_KEYS constant and
tightens KeyboardShortcutInput blockPropKey prop from string to keyof PersonalSettings.
…blockprop-key-pairs-to-shared-constant-or

ENG-1499: Convert legacy-blockprop key pairs to shared constant or typed key
…-of-getformattedconfigtree

ENG-1503: Replace getFormattedConfigTree consumers with direct helper calls
…props-migration

ENG-1519: Add legacy-to-blockprops migration, remove backedBy from schemea
…ettings-pages

ENG-735: Remove inline settings UI from config pages
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 new potential issues.

View 43 additional findings in Devin Review.

Open in Devin Review

Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Inverted fold/unfold logic: isOpen state and block CRUD are backwards

In toggleFoldedState, when isOpen is true (section is currently open/expanded), the code calls setIsOpen(false) and deletes the "Folded" block — meaning it unfolds by removing the folded marker. But when isOpen is false, it calls setIsOpen(true) and creates a "Folded" block. This means the isOpen React state tracks whether the section is collapsed (folded), not whether it is open. The newFolded value at LeftSidebarView.tsx:130 is computed as !isOpen, so when isOpen=true (collapsed/folded), newFolded=false — and this is the value written to the new settings store. However, the old-system block CRUD on lines 132-148 does the opposite: when isOpen=true it deletes the Folded block (setting folded=false), and when isOpen=false it creates the Folded block (setting folded=true). So both systems agree on the final folded value.

The actual bug is that the newFolded value written to the new settings store at lines 150-166 is !isOpen, but the old-system block operations establish that isOpen=true means "currently folded" (since the Folded block exists). When the section is currently folded (isOpen=true), newFolded = !true = false → correct (we're unfolding). When the section is currently expanded (isOpen=false), newFolded = !false = true → correct (we're folding). So the value is actually correct.

However, looking at PersonalSectionItem at line 223, isOpen is initialized from !!section.settings?.folded.value, meaning isOpen=true when folded=true. This means isOpen actually represents "is folded", not "is open". The Collapse isOpen={isOpen} at line 262 will then show content when folded=true and hide content when folded=false — which is the opposite of expected behavior. The section is shown expanded when the folded flag is true, and collapsed when it's false.

(Refers to lines 223-224)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

sid597 added 2 commits March 13, 2026 11:07
…nch' into eng-1484-reactive-settings-some-settings-require-reloading-the-graph

# Conflicts:
#	apps/roam/src/index.ts
#	apps/roam/src/utils/initializeObserversAndListeners.ts
…gs-some-settings-require-reloading-the-graph

ENG-1484: reactive settings for triggers and suggestive mode overlay
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 45 additional findings in Devin Review.

Open in Devin Review

Comment on lines +283 to +296
const getLegacyRelationsSetting = (): Record<string, unknown> => {
const settingsUid = getPageUidByPageTitle(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE);
if (!settingsUid) return DEFAULT_GLOBAL_SETTINGS.Relations;

const configTree = getBasicTreeByParentUid(settingsUid);
const grammarChildren = getSubTree({
tree: configTree,
key: "grammar",
}).children;
const relationNodes = getSubTree({
tree: grammarChildren,
key: "relations",
}).children;
if (relationNodes.length === 0) return DEFAULT_GLOBAL_SETTINGS.Relations;
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 getLegacyRelationsSetting reads relations from the block-props settings page instead of the legacy config page

getLegacyRelationsSetting at apps/roam/src/components/settings/utils/accessors.ts:284 reads a grammar > relations subtree from DG_BLOCK_PROP_SETTINGS_PAGE_TITLE (the new block-props settings page). However, the legacy grammar/relations tree actually lives on DISCOURSE_CONFIG_PAGE_TITLE ("roam/js/discourse-graph"), as shown by getDiscourseRelations.ts reading from discourseConfigRef.tree. Since the block-props settings page has no grammar subtree, getSubTree returns empty children, relationNodes is empty, and the function falls back to DEFAULT_GLOBAL_SETTINGS.Relations.

This causes two downstream problems:

  1. Migration data loss: migrateGraphLevelreadAllLegacyGlobalSettings()getLegacyGlobalSetting(["Relations"]) reads defaults instead of actual legacy relations, then overwrites correctly-initialized block props (init.ts + reconcileRelationKeys) with those defaults.
  2. Spurious dual-read warnings: When isNewSettingsStoreEnabled() is true, getGlobalSetting(["Relations", ...]) compares block-props values against defaults (not actual legacy data), generating incorrect mismatch warnings.
Expected fix Read from `discourseConfigRef.tree` (or `DISCOURSE_CONFIG_PAGE_TITLE`) instead of `DG_BLOCK_PROP_SETTINGS_PAGE_TITLE`:
const getLegacyRelationsSetting = (): Record<string, unknown> => {
  const tree = discourseConfigRef.tree;
  const grammarChildren = getSubTree({ tree, key: "grammar" }).children;
  const relationNodes = getSubTree({ tree: grammarChildren, key: "relations" }).children;
  ...
};
Prompt for agents
In apps/roam/src/components/settings/utils/accessors.ts, the function getLegacyRelationsSetting (starting around line 283) reads from DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, which is the new block-props settings page. It should instead read from the legacy discourse config tree where the grammar/relations data actually lives.

Change:
  const settingsUid = getPageUidByPageTitle(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE);
  if (!settingsUid) return DEFAULT_GLOBAL_SETTINGS.Relations;
  const configTree = getBasicTreeByParentUid(settingsUid);

To:
  const configTree = discourseConfigRef.tree;
  if (!configTree || configTree.length === 0) return DEFAULT_GLOBAL_SETTINGS.Relations;

This ensures the legacy relations are read from 'roam/js/discourse-graph' (via discourseConfigRef.tree) where they are actually stored, matching how getDiscourseRelations.ts reads them.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

sid597 added 2 commits March 16, 2026 10:43
Resolved 11 conflicts:
- Replaced getFeatureFlag("Reified relation triples") with getStoredRelationsEnabled()
  (main moved reified relations from FeatureFlags to PersonalSettings)
- Removed "Reified relation triples" from FeatureFlagsSchema, kept "Use new settings store"
- Kept toDiscourseNode + added migrateNodeBlockProps in accessors.ts (both needed)
- Dropped MigrationTab from AdminPanel (moved to HomePersonalSettings on main)
- Migrated DiscourseContextOverlay score validation to use accessors instead of
  legacy tree reads (getSubTree/getSettingValueFromTree/getBasicTreeByParentUid)
- Removed dead legacy imports (getSetting, AUTO_CANVAS_RELATIONS_KEY from Tldraw.tsx;
  USE_REIFIED_RELATIONS from accessors.ts)
- Zero net increase in legacy reader call sites
…ing-branch

Resolved 2 import conflicts in canvas files — keep both sides (accessor
imports + new canvas sync/posthog imports).
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