Skip to content

feat: Have synced URLS with normal expressions#776

Open
Crunging wants to merge 12 commits intoViren070:mainfrom
Crunging:reorderSynced
Open

feat: Have synced URLS with normal expressions#776
Crunging wants to merge 12 commits intoViren070:mainfrom
Crunging:reorderSynced

Conversation

@Crunging
Copy link
Contributor

@Crunging Crunging commented Feb 28, 2026

This allows users to put their synced urls anywhere so they can have expressions come before and after them. I also got rid of pattern deduplication as it works fine without it and matches the frontend better.

To achieve this I had to use a new system of how synced urls are stored. There is a legacy fallback so that existing configs will still work, and will be migrated to the new version on next save.

Screenshot 2026-02-28 at 3 18 45 PM

Summary by CodeRabbit

  • New Features

    • Embedded "synced" URLs supported in pattern and expression inputs with visible placeholders, add-url flow and duplicate detection.
    • Synced entries show as non-editable where applicable and surface migrated synced data automatically.
  • Refactor

    • Syncing now processes sources progressively per URL with duplicate tracking and legacy fallback for seamless updates.
    • Centralised synced‑URL UI handling and expanded input options for clearer control.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

Walkthrough

Adds placeholder-driven synced-URL support: legacy synced lists are migrated into <SYNCED: URL> tags; config validation extracts and validates embedded URLs; core regex/sel access expands patterns/expressions per-URL at runtime; frontend UI gains primitives to display and add synced URLs.

Changes

Cohort / File(s) Summary
Core: config validation
packages/core/src/utils/config.ts
Filter out <SYNCED: ...> placeholders from expression/regex validation; add SYNCED_PREFIX/SYNCED_SUFFIX and helpers to extract and validate embedded synced URLs.
Core: per-URL regex expansion
packages/core/src/utils/regex-access.ts
Parse <SYNCED: URL> items, fetch/transform patterns per-URL via a per-URL processor, track used URLs to avoid duplicates, and append resolved patterns while preserving override/score logic.
Core: per-URL sel expansion
packages/core/src/utils/sel-access.ts
Switch to per-URL expression processing with <SYNCED: URL> detection, resolve/merge expressions per-URL, apply overrides, and avoid reprocessing using a usedUrls tracker; keep legacy fallback for urls array.
Frontend: migration & load
packages/frontend/src/context/userData.tsx, packages/frontend/src/components/config-modal.tsx
Add in-place migrations to convert legacy synced URL arrays into <SYNCED: URL> placeholders; apply migrations when loading user config via applyMigrations.
Frontend: synced-URL UI primitives
packages/frontend/src/components/menu/filters/_components/synced-patterns.tsx
Remove onUrlsChange from SyncConfig; add optional props to SyncedUrlInputs (hideHeader, hideList, hideAddForm, onUrlAdded, existingUrls, className); switch add handling to onUrlAdded, use existingUrls for duplicate detection, conditional UI rendering.
Frontend: inputs integration
packages/frontend/src/components/menu/filters/_components/filter-inputs.tsx
Add parseSyncedUrl, useSyncedUrls, PlaceholderSyncedUrls; render synced URL placeholders across multiple input components, disable editing for synced items, and wire add-url callbacks and duplicate checks.
Frontend: sync props consolidation
packages/frontend/src/components/menu/filters/index.tsx
Extend getSyncedProps to include syncedRankedRegexUrls and replace manual per-key handlers with centralized prop spreading for synced config props.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as Frontend UI
    participant Config as ConfigModal / Migration
    participant Core as Core (regex/sel)
    participant Remote as Remote URL

    User->>UI: Open config / Edit filters
    UI->>Config: Request user config
    Config->>Core: Fetch raw userData
    Core-->>Config: raw userData
    Config->>Config: applyMigrations(raw userData)
    Config-->>UI: migrated config (with <SYNCED: URL> placeholders)
    UI->>UI: parseSyncedUrl() / show placeholders
    User->>UI: Add synced URL
    UI->>Remote: Fetch patterns/expressions for URL
    Remote-->>Core: supply remote patterns
    Core->>Core: resolve transforms & overrides per-URL
    Core-->>UI: expanded patterns/expressions
    UI->>UI: replace placeholder with resolved items
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • Viren070

Poem

🐰
I nibble legacy crumbs away,
I stitch <SYNCED: tags into the hay,
Per-URL hops fetch patterned treats,
Placeholders bloom into synced beats,
Hooray — the configs hop and play! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% 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 accurately captures the main feature: allowing synced URLs to be placed anywhere among normal expressions, enabling flexible ordering.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
packages/frontend/src/components/menu/filters/_components/synced-patterns.tsx (1)

787-803: ⚠️ Potential issue | 🟠 Major

Fix duplicate detection fallback for URL adds.

Line 787 initialises knownUrls to [] when existingUrls is not provided, so Line 801 can allow duplicates already present in syncConfig.urls. This reintroduces duplicate URL entries and repeated fetches.

Suggested patch
-  const knownUrls = existingUrls || [];
+  const knownUrls = Array.from(
+    new Set([...(existingUrls ?? []), ...urls])
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/frontend/src/components/menu/filters/_components/synced-patterns.tsx`
around lines 787 - 803, The duplicate-detection uses knownUrls = existingUrls ||
[] which misses URLs already in syncConfig.urls and allows duplicates; update
the logic that builds knownUrls (used by validateAndAdd) to include both
existingUrls and syncConfig.urls (or merge them into a Set) so the includes
check in validateAndAdd detects duplicates against the full current source of
truth (existingUrls and syncConfig.urls); modify the code around the knownUrls
initialization and ensure validateAndAdd still references that combined
collection to prevent re-adding URLs.
🧹 Nitpick comments (1)
packages/core/src/utils/regex-access.ts (1)

270-297: Cache per-URL sync results within this method call.

If the same <SYNCED: ...> marker appears multiple times, the same URL is resolved repeatedly. A small per-call cache keeps ordering behaviour intact while avoiding repeated fetch/transform work.

♻️ Proposed refactor
+    const processedByUrl = new Map<string, Promise<U[]>>();
+
     // Helper to process and transform patterns from a URL
     const processPatterns = async (url: string): Promise<U[]> => {
-      const patterns = await this.resolvePatterns([url], userData);
-      const syncedItems: U[] = [];
-      for (const regex of patterns) {
-        const override = overrides.find(
-          (o) =>
-            o.pattern === regex.pattern ||
-            (regex.name && o.originalName === regex.name)
-        );
-
-        if (override?.disabled) continue;
-
-        const item = transform(
-          override
-            ? {
-                ...regex,
-                name: override.name ?? regex.name,
-                score:
-                  override.score !== undefined ? override.score : regex.score,
-              }
-            : regex
-        );
-
-        syncedItems.push(item);
-      }
-      return syncedItems;
+      const cached = processedByUrl.get(url);
+      if (cached) return [...(await cached)];
+
+      const run = (async (): Promise<U[]> => {
+        const patterns = await this.resolvePatterns([url], userData);
+        const syncedItems: U[] = [];
+        for (const regex of patterns) {
+          const override = overrides.find(
+            (o) =>
+              o.pattern === regex.pattern ||
+              (regex.name && o.originalName === regex.name)
+          );
+
+          if (override?.disabled) continue;
+
+          const item = transform(
+            override
+              ? {
+                  ...regex,
+                  name: override.name ?? regex.name,
+                  score:
+                    override.score !== undefined ? override.score : regex.score,
+                }
+              : regex
+          );
+          syncedItems.push(item);
+        }
+        return syncedItems;
+      })();
+
+      processedByUrl.set(url, run);
+      return [...(await run)];
     };

Also applies to: 304-329

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

In `@packages/core/src/utils/regex-access.ts` around lines 270 - 297,
processPatterns currently calls this.resolvePatterns([url], ...) for each URL
occurrence which can re-resolve the same "<SYNCED:...>" marker multiple times;
add a small per-call cache (e.g., a Map keyed by the raw URL string) inside
processPatterns to store and return the resolved patterns for a URL on
subsequent requests within the same invocation, use the cached patterns instead
of calling resolvePatterns again, and then proceed with the existing override
lookup/transform/push logic (affecting the processPatterns function and
similarly the analogous block around lines 304-329).
🤖 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/frontend/src/components/menu/filters/_components/filter-inputs.tsx`:
- Around line 317-318: The current guard uses newValue.includes('<SYNCED') which
blocks any input containing that substring; replace it with a strict validation
that only short-circuits when the entire token is a valid synced tag (e.g.,
match with a regex like /^\s*<SYNCED:\s*[^>]+>\s*$/) before returning, then call
handleValueChange(newValue, index) as before; apply this same change to every
occurrence of the includes check (the conditional guarding newValue before
handleValueChange at the shown locations).

In
`@packages/frontend/src/components/menu/filters/_components/synced-patterns.tsx`:
- Around line 677-687: The add-UI currently becomes a silent no-op when
onUrlAdded is not provided; change the logic so the add form is only shown when
a real callback exists by deriving a flag like showAddForm = !hideAddForm &&
!!onUrlAdded and use that flag to conditionally render the add UI instead of
relying on optional chaining; also update the add handlers (the places calling
onUrlAdded?.(...) in this component) to assume onUrlAdded exists when the form
is shown (or guard with an assertion) so you no longer present a clickable UI
that does nothing.

In `@packages/frontend/src/context/userData.tsx`:
- Around line 261-262: The migration currently deduplicates by name (using the
accessor v => v.name); change it to deduplicate by pattern instead by using v =>
v.pattern when calling migrateSyncedUrls for 'syncedPreferredRegexUrls' ->
'preferredRegexPatterns', keeping the creation mapping (url => ({ name:
syncedTag(url), pattern: syncedTag(url) })) unchanged so migrated entries retain
the same pattern and display name but are de-duplicated by pattern to make the
migration idempotent.

---

Outside diff comments:
In
`@packages/frontend/src/components/menu/filters/_components/synced-patterns.tsx`:
- Around line 787-803: The duplicate-detection uses knownUrls = existingUrls ||
[] which misses URLs already in syncConfig.urls and allows duplicates; update
the logic that builds knownUrls (used by validateAndAdd) to include both
existingUrls and syncConfig.urls (or merge them into a Set) so the includes
check in validateAndAdd detects duplicates against the full current source of
truth (existingUrls and syncConfig.urls); modify the code around the knownUrls
initialization and ensure validateAndAdd still references that combined
collection to prevent re-adding URLs.

---

Nitpick comments:
In `@packages/core/src/utils/regex-access.ts`:
- Around line 270-297: processPatterns currently calls
this.resolvePatterns([url], ...) for each URL occurrence which can re-resolve
the same "<SYNCED:...>" marker multiple times; add a small per-call cache (e.g.,
a Map keyed by the raw URL string) inside processPatterns to store and return
the resolved patterns for a URL on subsequent requests within the same
invocation, use the cached patterns instead of calling resolvePatterns again,
and then proceed with the existing override lookup/transform/push logic
(affecting the processPatterns function and similarly the analogous block around
lines 304-329).

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e540b29 and 6dcf74b.

📒 Files selected for processing (8)
  • packages/core/src/utils/config.ts
  • packages/core/src/utils/regex-access.ts
  • packages/core/src/utils/sel-access.ts
  • packages/frontend/src/components/config-modal.tsx
  • packages/frontend/src/components/menu/filters/_components/filter-inputs.tsx
  • packages/frontend/src/components/menu/filters/_components/synced-patterns.tsx
  • packages/frontend/src/components/menu/filters/index.tsx
  • packages/frontend/src/context/userData.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/frontend/src/context/userData.tsx`:
- Around line 244-249: The migration assumes (config as any)[legacyKey] is an
array of strings and (config as any)[valuesKey] is an array — add defensive
guards before using them: check Array.isArray(urls) and that each element is a
string (skip non-strings) and ensure values is an array (if not, initialize to
[]), then iterate only the validated urls and use getKey(safeValue) /
syncedTag(url) as before; update the variables used by createItem, getKey,
syncedTag and ensure this logic lives inside the same migration block
(applyMigrations) so malformed saved configs are skipped rather than causing
.some or .push to throw.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6dcf74b and f4eba5e.

📒 Files selected for processing (1)
  • packages/frontend/src/context/userData.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/frontend/src/context/userData.tsx`:
- Around line 244-249: The migration loop may call getKey(v) on null/undefined
entries and throw, so update the dedupe check in the loop (the values array
handling around variables values, getKey, syncedTag, createItem) to defensively
handle nulls: ensure values is filtered or the some() predicate guards v (e.g.,
values.some((v: any) => v != null && getKey(v) === syncedTag(url))) and
similarly skip/clean null/undefined entries before pushing createItem(url); this
prevents TypeErrors during migration and avoids discarding the rest of the
config.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4eba5e and 64bce90.

📒 Files selected for processing (1)
  • packages/frontend/src/context/userData.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/frontend/src/context/userData.tsx (1)

244-251: ⚠️ Potential issue | 🟡 Minor

Normalise legacy URL entries before creating placeholder tags.

At Line 247, raw urls entries are iterated directly; malformed persisted values can be coerced into invalid placeholders (for example, <SYNCED: [object Object]>). Please filter to non-empty strings (and trim) before dedupe/push.

Suggested patch
-    const urls = (config as any)[legacyKey];
-    if (!Array.isArray(urls) || urls.length === 0) return;
+    const rawUrls = (config as any)[legacyKey];
+    if (!Array.isArray(rawUrls) || rawUrls.length === 0) return;
+    const urls = rawUrls
+      .filter((u: unknown): u is string => typeof u === 'string')
+      .map((u) => u.trim())
+      .filter((u) => u.length > 0);
+    if (urls.length === 0) {
+      delete (config as any)[legacyKey];
+      return;
+    }
     const values: any[] = Array.isArray((config as any)[valuesKey]) ? (config as any)[valuesKey] : [];
     for (const url of urls) {
-      if (!values.some((v: any) => v != null && getKey(v) === syncedTag(url))) {
+      const tag = syncedTag(url);
+      if (!values.some((v: any) => v != null && getKey(v) === tag)) {
         values.push(createItem(url));
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/context/userData.tsx` around lines 244 - 251, The loop
over legacy `urls` may include non-string or empty entries producing invalid
placeholders; before iterating/filtering use a normalized array: map each entry
to a trimmed string, filter out non-strings and empty strings (e.g., const
normalizedUrls = urls.map(u => typeof u === 'string' ? u.trim() :
'').filter(Boolean)), then iterate `normalizedUrls` instead of `urls` when
checking `values.some((v) => v != null && getKey(v) === syncedTag(url))` and
when calling `createItem(url)` so dedupe and placeholder creation use validated,
trimmed URL strings; keep references to `legacyKey`, `valuesKey`, `getKey`,
`syncedTag`, and `createItem` unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/frontend/src/context/userData.tsx`:
- Around line 244-251: The loop over legacy `urls` may include non-string or
empty entries producing invalid placeholders; before iterating/filtering use a
normalized array: map each entry to a trimmed string, filter out non-strings and
empty strings (e.g., const normalizedUrls = urls.map(u => typeof u === 'string'
? u.trim() : '').filter(Boolean)), then iterate `normalizedUrls` instead of
`urls` when checking `values.some((v) => v != null && getKey(v) ===
syncedTag(url))` and when calling `createItem(url)` so dedupe and placeholder
creation use validated, trimmed URL strings; keep references to `legacyKey`,
`valuesKey`, `getKey`, `syncedTag`, and `createItem` unchanged.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64bce90 and b477cca.

📒 Files selected for processing (1)
  • packages/frontend/src/context/userData.tsx

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