E546 [WIKI-608] Change the header of the pages to be more generic by Palanikannan1437 · Pull Request #7787 · makeplane/plane · GitHub
[go: up one dir, main page]

Skip to content

Conversation

Palanikannan1437
Copy link
Member
@Palanikannan1437 Palanikannan1437 commented Sep 12, 2025

Description

Change the header of the pages to be more generic

Type of Change

  • Improvement (change that would cause existing functionality to not work as expected)

Summary by CodeRabbit

  • New Features

    • Introduced a two-tier pages header with built-in search, sort, and filters.
    • Displays applied filters in a secondary bar with per-filter removal and a clear-all option.
  • Refactor

    • Unified header logic into a shared component for consistent behavior across views.
    • Simplified header usage in pages list, reducing coupling without changing core list behavior.

Copy link
Contributor
coderabbitai bot commented Sep 12, 2025

Walkthrough

Introduces a new BasePagesListHeaderRoot component handling search, sort, and filters via shared page store. Refactors PagesListHeaderRoot into a thin wrapper delegating to the base component and removing the storeType prop. Updates PagesListView to use the new header signature without storeType.

Changes

Cohort / File(s) Summary
New shared base header component
apps/web/core/components/pages/header/base.tsx
Adds MobX-observed BasePagesListHeaderRoot rendering primary controls (tab nav via prop, search, sort, filters) and a secondary applied-filters bar. Integrates with page store (selected by storeType), supports updateFilters, clearAllFilters, and per-filter removal (including favorites special case).
Header wrapper refactor
apps/web/core/components/pages/header/root.tsx
Simplifies PagesListHeaderRoot to delegate to BasePagesListHeaderRoot. Removes internal filter/search/sort logic and the storeType prop from its public API; internally passes PROJECT as storeType to the base.
Callsite update
apps/web/core/components/pages/pages-list-view.tsx
Updates PagesListHeaderRoot invocation to drop the storeType prop; other behavior unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant V as PagesListView
  participant H as PagesListHeaderRoot
  participant B as BasePagesListHeaderRoot
  participant S as Page Store (PROJECT)
  participant M as useMember

  U->>V: Load Pages list view
  V->>H: Render header (pageType, projectId, workspaceSlug)
  H->>B: Render base header (storeType=PROJECT, tabNavigationComponent)
  B->>S: Read filters, order, search
  B->>M: Fetch workspace member IDs
  Note over B,S: Primary header renders search, sort, filters

  U->>B: Search / Sort / Open Filters
  B->>S: updateFilters(...) / change order / search query

  alt Any filters applied
    B->>S: calculateTotalFilters()
    Note over B,S: Secondary applied-filters bar shown
    U->>B: Remove a chip / Clear all
    opt Remove specific filter
      B->>S: updateFilters(remove key/value) [favorites handled specially]
    end
    opt Clear all
      B->>S: clearAllFilters()
    end
  else No filters
    Note over B: Secondary bar hidden
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

🌐frontend, 🌟improvement

Suggested reviewers

  • prateekshourya29
  • aaryan610
  • sriramveeraghanta

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The current PR description is minimal and does not follow the repository template: it only has a one-line summary and the Type of Change checkbox but lacks a detailed Description, Screenshots/Media, Test Scenarios, and References required by the template. Given this PR changes UI and behavior (new BasePagesListHeaderRoot, refactored PagesListHeaderRoot, and the storeType change), reviewers need implementation details and any breaking-impact notes to evaluate it. As written, the description is incomplete and insufficient for review. Please expand the Description with concrete details of what changed (list of modified files, the new BasePagesListHeaderRoot behavior, why storeType was removed from the header API and hard-coded to PROJECT, and any consumer impact), add Screenshots or a short recording showing before/after UI if applicable and list the Test Scenarios you ran (local build steps, unit/integration checks and expected results), and include References such as the issue/ticket number and related PRs; ensure these sections match the repository template before requesting review.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "[WIKI-608] Change the header of the pages to be more generic" accurately reflects the primary change in the changeset — making the pages header more generic via a refactor (addition of BasePagesListHeaderRoot and simplification of PagesListHeaderRoot) — and is concise and relevant for history. Including the ticket ID is acceptable and the phrasing is specific to the header change rather than being generic. Therefore the title meets the repository's title criteria.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

I nudge my whiskers, hop through state,
A header split—now lean, now great.
Filters chip and searches sing,
Sorting swoops on feathered wing.
One base to rule the UI sky—
I thump approve, then bound on by. 🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/dependencies-pages-header

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
makeplane bot commented Sep 12, 2025

Pull Request Linked with Plane Work Items

Comment Automatically Generated by Plane

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

🧹 Nitpick comments (1)
apps/web/core/components/pages/header/base.tsx (1)

50-50: Reuse computed flag instead of recomputing filter count

Use isFiltersApplied in both places to avoid duplicate work and ensure consistent logic.

-  const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0;
+  const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0;
...
-      {calculateTotalFilters(filters?.filters ?? {}) !== 0 && (
+      {isFiltersApplied && (

Also applies to: 83-93

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3e7cfd and 17027bc.

📒 Files selected for processing (3)
  • apps/web/core/components/pages/header/base.tsx (1 hunks)
  • apps/web/core/components/pages/header/root.tsx (1 hunks)
  • apps/web/core/components/pages/pages-list-view.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
apps/web/core/components/pages/pages-list-view.tsx (1)
apps/web/core/components/pages/header/root.tsx (1)
  • PagesListHeaderRoot (16-27)
apps/web/core/components/pages/header/base.tsx (5)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
apps/space/core/hooks/store/use-member.ts (1)
  • useMember (7-11)
packages/types/src/page/core.ts (1)
  • TPageFilterProps (34-39)
apps/web/core/components/issues/issue-layouts/filters/header/helpers/dropdown.tsx (1)
  • FiltersDropdown (25-124)
apps/web/core/store/member/workspace-member.store.ts (1)
  • workspaceMemberIds (97-102)
apps/web/core/components/pages/header/root.tsx (2)
packages/types/src/page/core.ts (1)
  • TPageNavigationTabs (28-28)
apps/web/core/components/pages/header/base.tsx (1)
  • BasePagesListHeaderRoot (26-95)
🔇 Additional comments (2)
apps/web/core/components/pages/header/base.tsx (1)

52-82: Nice separation of concerns; base header composition looks solid

Clear split between navigation (left) and controls (right), and all mutations flow through the store. Good use of observer and small subcomponents.

apps/web/core/components/pages/header/root.tsx (1)

16-26: Wrapper refactor is clean and minimal

Thin wrapper delegating to Base keeps responsibilities clear and reduces duplication.

Comment on lines +35 to +48
const handleRemoveFilter = useCallback(
(key: keyof TPageFilterProps, value: string | null) => {
let newValues = filters.filters?.[key];

if (key === "favorites") newValues = !!value;
if (Array.isArray(newValues)) {
if (!value) newValues = [];
else newValues = newValues.filter((val) => val !== value);
}

updateFilters("filters", { [key]: newValues });
},
[filters.filters, updateFilters]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix favorites removal and improve type safety in handleRemoveFilter

Setting favorites to false on removal may leave a redundant key and relies on downstream logic to ignore it. Prefer clearing the key (undefined) and narrow types per-key to avoid boolean/array confusion.

-  const handleRemoveFilter = useCallback(
-    (key: keyof TPageFilterProps, value: string | null) => {
-      let newValues = filters.filters?.[key];
-
-      if (key === "favorites") newValues = !!value;
-      if (Array.isArray(newValues)) {
-        if (!value) newValues = [];
-        else newValues = newValues.filter((val) => val !== value);
-      }
-
-      updateFilters("filters", { [key]: newValues });
-    },
-    [filters.filters, updateFilters]
-  );
+  const handleRemoveFilter = useCallback(
+    (key: keyof TPageFilterProps, value: string | null) => {
+      const current = filters.filters ?? {};
+      // Special-case boolean filters
+      if (key === "favorites") {
+        // remove when value is null, otherwise set true
+        updateFilters("filters", { favorites: value === null ? undefined : true });
+        return;
+      }
+      const existing = current[key as Exclude<keyof TPageFilterProps, "favorites">];
+      let next: unknown = existing;
+      if (Array.isArray(existing)) {
+        next = value ? existing.filter((v) => v !== value) : [];
+      } else {
+        // Non-array keys: clear by default
+        next = undefined;
+      }
+      updateFilters("filters", { [key]: next } as Partial<TPageFilterProps>);
+    },
+    [filters.filters, updateFilters]
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleRemoveFilter = useCallback(
(key: keyof TPageFilterProps, value: string | null) => {
let newValues = filters.filters?.[key];
if (key === "favorites") newValues = !!value;
if (Array.isArray(newValues)) {
if (!value) newValues = [];
else newValues = newValues.filter((val) => val !== value);
}
updateFilters("filters", { [key]: newValues });
},
[filters.filters, updateFilters]
);
const handleRemoveFilter = useCallback(
(key: keyof TPageFilterProps, value: string | null) => {
const current = filters.filters ?? {};
// Special-case boolean filters
if (key === "favorites") {
// remove when value is null, otherwise set true
updateFilters("filters", { favorites: value === null ? undefined : true });
return;
}
const existing = current[key as Exclude<keyof TPageFilterProps, "favorites">];
let next: unknown = existing;
if (Array.isArray(existing)) {
next = value ? existing.filter((v) => v !== value) : [];
} else {
// Non-array keys: clear by default
next = undefined;
}
updateFilters("filters", { [key]: next } as Partial<TPageFilterProps>);
},
[filters.filters, updateFilters]
);
🤖 Prompt for AI Agents
In apps/web/core/components/pages/header/base.tsx around lines 35–48,
handleRemoveFilter currently sets the "favorites" filter to a boolean (false)
and uses a union-typed newValues which can leave a redundant key and cause
downstream confusion; change the removal behavior for "favorites" to clear the
key (undefined) instead of setting false, and narrow the per-key types before
manipulating values (e.g., if key === "favorites" treat current value as boolean
| undefined and set newValues = undefined on removal; otherwise treat as
string[] and filter or set to []), then call updateFilters with the
properly-typed partial update so the favorites key is removed rather than left
false.

Comment on lines +21 to +25
storeType={EPageStoreType.PROJECT}
tabNavigationComponent={
<PageTabNavigation workspaceSlug={workspaceSlug} projectId={projectId} pageType={pageType} />
}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Don’t hard-code PROJECT; thread storeType through to Base

Hard-coding ties the “generic” header to project scope and can desync from views using other stores. Accept an optional storeType (default to PROJECT) and pass it through.

 type Props = {
   pageType: TPageNavigationTabs;
   projectId: string;
   workspaceSlug: string;
+  storeType?: EPageStoreType;
 };
 
 export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
-  const { pageType, projectId, workspaceSlug } = props;
+  const { pageType, projectId, workspaceSlug, storeType = EPageStoreType.PROJECT } = props;
 
   return (
     <BasePagesListHeaderRoot
-      storeType={EPageStoreType.PROJECT}
+      storeType={storeType}
       tabNavigationComponent={
         <PageTabNavigation workspaceSlug={workspaceSlug} projectId={projectId} pageType={pageType} />
       }
     />
   );
 });

After this change, pass storeType from PagesListView (see companion comment).


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== repo root ==="
pwd

echo
echo "=== show file if exists: apps/web/core/components/pages/header/root.tsx ==="
if [ -f "apps/web/core/components/pages/header/root.tsx" ]; then
  sed -n '1,240p' apps/web/core/components/pages/header/root.tsx
else
  echo "MISSING: apps/web/core/components/pages/header/root.tsx"
fi

echo
echo "=== search for related symbols ==="
rg -n --hidden -S 'PagesListHeaderRoot' -g '!*node_modules/*' -C3 || true
rg -n --hidden -S 'BasePagesListHeaderRoot' -g '!*node_modules/*' -C3 || true
rg -n --hidden -S 'EPageStoreType' -g '!*node_modules/*' -C3 || true
rg -n --hidden -S 'PagesListView' -g '!*node_modules/*' -C3 || true
rg -n --hidden -S 'PageTabNavigation' -g '!*node_modules/*' -C3 || true

Length of output: 1674


Don't hard-code PROJECT — thread storeType through to Base

Hard-coding ties the generic header to project scope and can desync from views using other stores. Accept an optional storeType (default to EPageStoreType.PROJECT) and pass it to BasePagesListHeaderRoot.

File: apps/web/core/components/pages/header/root.tsx (around lines 21–25)

 type Props = {
   pageType: TPageNavigationTabs;
   projectId: string;
   workspaceSlug: string;
+  storeType?: EPageStoreType;
 };
 
 export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
-  const { pageType, projectId, workspaceSlug } = props;
+  const { pageType, projectId, workspaceSlug, storeType = EPageStoreType.PROJECT } = props;
 
   return (
     <BasePagesListHeaderRoot
-      storeType={EPageStoreType.PROJECT}
+      storeType={storeType}
       tabNavigationComponent={
         <PageTabNavigation workspaceSlug={workspaceSlug} projectId={projectId} pageType={pageType} />
       }
     />
   );
 });

After this change, pass storeType from PagesListView (see companion comment).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
storeType={EPageStoreType.PROJECT}
tabNavigationComponent={
<PageTabNavigation workspaceSlug={workspaceSlug} projectId={projectId} pageType={pageType} />
}
/>
type Props = {
pageType: TPageNavigationTabs;
projectId: string;
workspaceSlug: string;
storeType?: EPageStoreType;
};
export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
const { pageType, projectId, workspaceSlug, storeType = EPageStoreType.PROJECT } = props;
return (
<BasePagesListHeaderRoot
storeType={storeType}
tabNavigationComponent={
<PageTabNavigation workspaceSlug={workspaceSlug} projectId={projectId} pageType={pageType} />
}
/>
);
});
🤖 Prompt for AI Agents
In apps/web/core/components/pages/header/root.tsx around lines 21 to 25, the
component currently hard-codes storeType=EPageStoreType.PROJECT; change the
component signature to accept an optional prop storeType with a default of
EPageStoreType.PROJECT, and forward that prop to the BasePagesListHeaderRoot
(replace the hard-coded value with the new prop). After this change, update the
PagesListView to pass its storeType through into this header component so the
header reflects the actual view's store type.

storeType={storeType}
workspaceSlug={workspaceSlug}
/>
<PagesListHeaderRoot pageType={pageType} projectId={projectId} workspaceSlug={workspaceSlug} />
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Header store now diverges from view store (PROJECT vs storeType)

Header is hard-coded to the PROJECT store via the wrapper, while this view gates and fetches via storeType and renders content with storeType. This can desync filters/search/sort and even hide the header when storeType !== PROJECT. Pass storeType through to keep header and content in the same store.

Apply:

-        <PagesListHeaderRoot pageType={pageType} projectId={projectId} workspaceSlug={workspaceSlug} />
+        <PagesListHeaderRoot
+          pageType={pageType}
+          projectId={projectId}
+          workspaceSlug={workspaceSlug}
+          storeType={storeType}
+        />

To verify current usages and whether non-PROJECT store types exist:


🏁 Script executed:

#!/bin/bash
# Find PagesListView call sites and inspect storeType usage.
rg -nP --type tsx -C3 '<PagesListView\b' | sed -n '1,200p'
rg -nP --type tsx -C3 'storeType\s*=' | sed -n '1,200p'

Length of output: 55


🏁 Script executed:

#!/bin/bash
set -o pipefail

echo "=== search for PagesListView / PagesListHeaderRoot usages ==="
rg -n --hidden -S '<PagesListView\b' || true
rg -n --hidden -S '<PagesListHeaderRoot\b' || true

echo
echo "=== search for filenames / symbols ==="
rg -n --hidden -S 'pages-list-view|pages-list-header-root|PagesListHeaderRoot|PagesListView' || true

echo
echo "=== show specific file if it exists ==="
if [ -f "apps/web/core/components/pages/pages-list-view.tsx" ]; then
  echo "---- apps/web/core/components/pages/pages-list-view.tsx ----"
  sed -n '1,240p' apps/web/core/components/pages/pages-list-view.tsx || true
else
  echo "file apps/web/core/components/pages/pages-list-view.tsx not found"
fi

echo
echo "=== find files that define PagesListHeaderRoot and print head ==="
files=$(rg -l --hidden -S 'PagesListHeaderRoot' || true)
if [ -n "$files" ]; then
  for f in $files; do
    echo "---- $f ----"
    sed -n '1,240p' "$f" || true
    echo
  done
else
  echo "No files named PagesListHeaderRoot found by rg"
fi

echo
echo "=== search for storeType / StoreType.PROJECT mentions ==="
rg -n --hidden -S 'storeType|StoreType\.PROJECT' || true

echo
echo "=== list files in apps/web/core/components/pages if present ==="
if [ -d "apps/web/core/components/pages" ]; then
  ls -la apps/web/core/components/pages || true
else
  echo "dir apps/web/core/components/pages not found"
fi

Length of output: 40506


Pass storeType through to the header and make the header respect it

Header is hard-coded to EPageStoreType.PROJECT while PagesListView fetches/renders by the incoming storeType — this can desync filters/search/sort or hide the header when storeType !== PROJECT. Change both files so header receives and uses the same storeType.

  • Apply to apps/web/core/components/pages/pages-list-view.tsx:
-        <PagesListHeaderRoot pageType={pageType} projectId={projectId} workspaceSlug={workspaceSlug} />
+        <PagesListHeaderRoot
+          pageType={pageType}
+          projectId={projectId}
+          workspaceSlug={workspaceSlug}
+          storeType={storeType}
+        />
  • Update apps/web/core/components/pages/header/root.tsx to accept and forward storeType (keep PROJECT as default for backwards compatibility):
-type Props = {
-  pageType: TPageNavigationTabs;
-  projectId: string;
-  workspaceSlug: string;
-};
+type Props = {
+  pageType: TPageNavigationTabs;
+  projectId: string;
+  workspaceSlug: string;
+  storeType?: EPageStoreType;
+};

 export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
-  const { pageType, projectId, workspaceSlug } = props;
+  const { pageType, projectId, workspaceSlug, storeType = EPageStoreType.PROJECT } = props;

   return (
     <BasePagesListHeaderRoot
-      storeType={EPageStoreType.PROJECT}
+      storeType={storeType}
       tabNavigationComponent={
         <PageTabNavigation workspaceSlug={workspaceSlug} projectId={projectId} pageType={pageType} />
       }
     />
   );
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<PagesListHeaderRoot pageType={pageType} projectId={projectId} workspaceSlug={workspaceSlug} />
// apps/web/core/components/pages/pages-list-view.tsx
<PagesListHeaderRoot
pageType={pageType}
projectId={projectId}
workspaceSlug={workspaceSlug}
storeType={storeType}
/>
Suggested change
<PagesListHeaderRoot pageType={pageType} projectId={projectId} workspaceSlug={workspaceSlug} />
// apps/web/core/components/pages/header/root.tsx
type Props = {
pageType: TPageNavigationTabs;
projectId: string;
workspaceSlug: string;
storeType?: EPageStoreType;
};
export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
const { pageType, projectId, workspaceSlug, storeType = EPageStoreType.PROJECT } = props;
return (
<BasePagesListHeaderRoot
storeType={storeType}
tabNavigationComponent={
<PageTabNavigation workspaceSlug={workspaceSlug} projectId={projectId} pageType={pageType} />
}
/>
);
});
🤖 Prompt for AI Agents
In apps/web/core/components/pages/pages-list-view.tsx around line 33 and in
apps/web/core/components/pages/header/root.tsx, the PagesListHeaderRoot is
hard-coded to EPageStoreType.PROJECT causing desync when pages-list-view uses a
different storeType; update pages-list-view to pass the current storeType prop
into PagesListHeaderRoot, and update header/root.tsx to accept an optional
storeType prop (defaulting to EPageStoreType.PROJECT for backwards
compatibility) and forward that storeType to any child/header internals that
currently assume PROJECT so filters/search/sort reflect the incoming storeType.

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
0