Skip to content

feat(shortcuts): global New create shortcut (LFXV2-2721)#1121

Merged
manishdixitlfx merged 15 commits into
mainfrom
feat/LFXV2-2721-create-shortcuts
Jul 18, 2026
Merged

feat(shortcuts): global New create shortcut (LFXV2-2721)#1121
manishdixitlfx merged 15 commits into
mainfrom
feat/LFXV2-2721-create-shortcuts

Conversation

@manishdixitlfx

@manishdixitlfx manishdixitlfx commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a global "New" (+) shortcut to the left nav rail → popover of creatable types → a dialog to pick the target Foundation/Project → routes into the existing create flow with the context pre-selected.

  • Types & order: Meeting · Newsletter │ Vote · Survey │ Group · Mailing List — a semantic grouping (Engage / Decide / Organize), with thin separators between the pairs (order is intentional, not frequency).
  • Eligibility: intersection of the project writer grant (GET /api/projectswriter: true) and a lens the user's persona holds. Fail-closed — the button stays hidden until eligibility is proven, and resolves to empty on load/error.
  • Authorization is never granted by the button: each create route's writerGuard (and newsletterAccessGuard for newsletters, which is itself writer-or-ED) remains authoritative. The shortcut only advertises access.
  • Project picker: reuses the sidebar's lfx-project-selector (search + All/Foundations/Projects tabs + logos + role badges) via a new curated items input — fed the writer-scoped list, never the view-scoped nav catalog. Sidebar behavior is unchanged when items is absent.
  • Single-project convenience: when the user can create on exactly one project/foundation, the dialog auto-selects it so Continue is enabled on open (no redundant open-and-pick).

Accepted trade-off (persona-differential)

The shortcut is deliberately writer-scoped, so an Executive Director with no per-project writer grant won't see it (unlike writerGuard, this has no ED fast-path). EDs still create through the in-page flows, which carry their own ED fast-path. Documented in CreatePermissionService.

Test plan

  • E2E create-quick-link.spec.ts (S1–S5): rail button visibility, six-type grouped popover + render order, dialog + selector (search/tabs), writer-scoped list, and single-project auto-select. Real-API, name-agnostic; skips when the test user holds no writer grant.
  • yarn build / lint / check-types green. (apps/lfx-one is e2e-only — no unit runner.)
  • ⚠️ Runtime visual pass pending — verify the selector renders search + tabs + logos + badges and lists only writer-held projects on a real authenticated session.

Follow-ups (separate tickets)

  • GET /api/projects/creatable — a dedicated endpoint returning just the writer-creatable set, replacing the current client-triggered full-catalog scan + per-project access-check. Motivation: the rail icon lags on load because the gating call access-checks every visible project.
  • Optimistic / loading-state show of the "New" icon — show while eligibility loads instead of fail-closed-hidden. Deferred to a separate PR by design (keeps this PR's vetted fail-closed behavior intact).
  • The LF overview page's "Quick links" element should mirror this six-type set/order.
  • Emit a log/telemetry event when the canShowCreateButton() gating call errors — today a transport failure is indistinguishable from "no permission".
  • Fix pre-existing packages/shared/src/utils/meeting-privacy.utils.spec.ts (describe is not defined, missing vitest import) — unrelated, already failing on main.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UYB2jLJ3sacvXSvQZ6vwCk

Copilot AI review requested due to automatic review settings July 17, 2026 05:23
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c61c40ab-24ad-455b-aea6-60ae65fc2244

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds a permission-scoped Create quick-link to the lens rail, a grouped artifact menu, a project-selection dialog with navigation setup, curated selector behavior, impersonation-aware positioning, and Playwright coverage for the complete flow.

Changes

Create quick-link flow

Layer / File(s) Summary
Artifact contracts and configuration
packages/shared/src/interfaces/create-artifact.interface.ts, packages/shared/src/constants/create-artifact.constants.ts, packages/shared/src/{interfaces,constants}/index.ts
Defines creatable artifact types, groups, configuration, project metadata, and six ordered artifact entries.
Permission-scoped create targets
apps/lfx-one/src/app/shared/services/create-permission.service.ts
Loads writer-enabled projects, intersects them with available lenses, and exposes creatable projects, types, and button visibility.
Curated project selector
apps/lfx-one/src/app/shared/components/project-selector/*
Supports locally filtered curated items without navigation-backed search, loading, or pagination, with dialog-specific styling.
Create menu and artifact dialog
apps/lfx-one/src/app/shared/components/lens-switcher/*, apps/lfx-one/src/app/shared/components/create-artifact-dialog/*, apps/lfx-one/src/app/layouts/main-layout/main-layout.component.html
Connects the rail menu to artifact dialogs, project selection, context setup, create-route navigation, and banner-offset positioning.
Quick-link end-to-end validation
apps/lfx-one/e2e/create-quick-link.spec.ts
Tests permission gating, grouped artifact options, selector structure, project selection, single-project auto-selection, and navigation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant LensRail as Lens rail
  participant PermissionService as Permission service
  participant CreateDialog as Create dialog
  participant ProjectSelector as Project selector
  participant Router
  User->>LensRail: Click Create
  LensRail->>PermissionService: Read creatable artifact types
  LensRail->>CreateDialog: Open selected artifact
  CreateDialog->>ProjectSelector: Render eligible projects
  User->>ProjectSelector: Select project
  ProjectSelector->>CreateDialog: Return selected item
  CreateDialog->>Router: Navigate to artifact create route
Loading

Suggested labels: deploy-preview

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a global create shortcut in the left navigation rail.
Description check ✅ Passed The description directly describes the create shortcut flow, eligibility rules, project picker, and tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/LFXV2-2721-create-shortcuts

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

Copilot AI 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.

Pull request overview

Adds a permission-gated desktop rail shortcut for creating meetings, groups, and mailing lists within a selected project or foundation.

Changes:

  • Adds shared artifact configuration and project-selection types.
  • Adds writer/lens eligibility, popover, dialog, and context-aware navigation.
  • Adds Playwright smoke coverage for the shortcut flow.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/shared/src/interfaces/index.ts Exports quick-link interfaces.
packages/shared/src/interfaces/create-artifact.interface.ts Defines artifact and target-project types.
packages/shared/src/constants/index.ts Exports artifact configuration.
packages/shared/src/constants/create-artifact.constants.ts Configures supported artifact types and routes.
apps/lfx-one/src/app/shared/services/create-permission.service.ts Resolves writer- and lens-eligible projects.
apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.ts Controls the menu and creation dialog.
apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.scss Positions and styles the popover.
apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.html Adds the desktop rail button and menu.
apps/lfx-one/src/app/shared/components/create-artifact-dialog/create-artifact-dialog.component.ts Aligns context and navigates to creation flows.
apps/lfx-one/src/app/shared/components/create-artifact-dialog/create-artifact-dialog.component.html Renders project selection and actions.
apps/lfx-one/src/app/layouts/main-layout/main-layout.component.html Accounts for the impersonation banner offset.
apps/lfx-one/e2e/create-quick-link.spec.ts Adds shortcut smoke tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.html Outdated
Comment on lines +51 to +55
async function skipWhenNoCreatePermission(page: Page): Promise<void> {
const trigger = page.getByTestId('create-rail-button');
const visible = await trigger.isVisible().catch(() => false);
if (!visible) {
test.skip(true, 'Test user holds `writer` on no project — button hidden by design.');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged — because the suite skips when the test account holds no writer grant, a render/permission/eligibility regression could pass silently. Making the positive path non-skippable needs a page.route stub of /api/projects (a deliberate shift from this suite's real-API, name-agnostic design) plus a separate fail-closed case. Tracking as a follow-up rather than expanding this PR — leaving this thread open.

Comment thread apps/lfx-one/e2e/create-quick-link.spec.ts Outdated
manishdixitlfx and others added 11 commits July 17, 2026 11:26
…LFXV2-2721)

Adds a "New" (+) button to the left nav rail that opens a popover to create a
Meeting, Group, or Mailing List. A dialog picks the target Foundation/Project,
then routes into the existing create flows with that context pre-selected.

Eligibility is the intersection of the project writer grant, the same signal
writerGuard enforces, and a lens the user's persona holds. GET /api/projects
batch access-checks every visible project and returns writer per project.
activeContext is lens-gated, so a project outside the user's lenses could not be
resolved by the create page; writer and lens are independent grants, so both
must hold. Everything resolves empty while loading and on error, keeping the
button hidden until eligibility is proven.

Applied on top of the attached patch:

- Gate on the writer grant rather than a persona proxy. The patch inferred
  eligibility, and isFoundation, from which persona bucket a project arrived in,
  which mislabelled every ED-bucket project as a foundation and left parent_uid
  and logoUrl unset. isFoundation now comes from computeIsFoundation over the
  project's own attributes.
- Align the lens before seeding context, and abort if setLens refuses, so the
  create page cannot resolve a different project than the one picked.
- Pass syncUrl false, so seeding no longer rewrites the history entry of the page
  the dialog was opened from.
- Navigate with ?project= so writerGuard is authoritative on the selection.
- Load the project list in afterNextRender. The request is already issued during
  SSR by profile-affiliations, so the transfer cache would resolve it
  synchronously at construction and render the button on the client's first pass
  while the server rendered it absent.
- Offset the popover under the impersonation banner, which shifts the rail down.
  The bottom-anchored apps and user menus are immune to this; a top-anchored one
  is not.
- Report the dialog's dead ends with a toast instead of an inert CTA.
- Target data-test for the project select in e2e; lfx-select does not emit
  data-testid, so the assertion could never run.
- Use fa-light for the rail icon to match the other rail affordances.

Co-authored-by: Nuno Eufrasio <neufrasio@contractor.linuxfoundation.org>
Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
Adds Newsletter as a fourth type in the rail Create quick-link. Newsletter
creation is writer-gated by newsletterAccessGuard (ED or project.writer === true),
the same signal CreatePermissionService already intersects on, so the entry
never dead-ends an offered project. Uses the newsletters feature's own
fa-paper-plane icon and the existing /newsletters/create route.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
Expands the rail Create quick-link from four types to six, in a deliberate
sequence: Meeting, Newsletter, Vote, Survey, Group, Mailing List.

The order is a semantic grouping, not frequency — Engage (Meeting, Newsletter),
Decide (Vote, Survey), Organize (Group, Mailing List). A thin separator is drawn
wherever the group changes, driven by a new group field on CREATABLE_ARTIFACTS
so sequence and grouping share one source of truth. No group labels yet.

Vote and Survey create routes carry the same authGuard + writerGuard
(writeFeature: votes/surveys) as the existing four, and CreatePermissionService
already intersects on project.writer, so neither entry over-promises a route the
persona cannot use.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
Full-branch sweep flagged (general + conventions reviewers) that creatableTypes
is all-or-nothing, so the per-type filter it feeds in lens-switcher never removes
an entry — presenting the illusion of per-type control. Add a doc comment warning
maintainers that a new CREATABLE_ARTIFACTS entry inherits visibility for free and
does not get independent gating, and note the filter is forward-looking scaffolding
for a future GET /api/projects/creatable endpoint. No behavior change.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
…oped list

Replaces the plain PrimeNG select in the create-artifact dialog with the existing
lfx-project-selector, so the dialog presents the familiar sidebar pattern: search,
All/Foundations/Projects tabs, logos, and role badges.

The selector defaults to NavigationService, which is view-scoped (every project the
user can see, gated only by the bearer token) — adopting it as-is would have listed
the global catalog. To keep the create list scoped to projects the user holds writer
on, extend lfx-project-selector with an optional curated `items` input: when provided
it renders that list with local search + tab filtering and no pagination; when absent
(every sidebar caller) nav-backed behavior is unchanged. The dialog feeds it the
writer-scoped creatableProjects mapped to LensItem, so scope is unchanged from before
— only the UI changed.

Also aligns the dialog label color to the gray brand scale (slate is not an lfx scale)
and updates the e2e to drive the selector: asserts search + tabs render and that
sentinel non-member catalog names are absent.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
…ames

Post-commit trio flagged two e2e issues (production code was clean):

- General reviewer: getByTestId('project-selector') was unscoped, but that testid
  is emitted by the lfx-project-selector template itself, so the sidebar's instance
  (present in project/foundation lens — the writer-scoped test user's normal state)
  also matches it, tripping Playwright strict mode. Scope the trigger to the dialog.
- Conventions reviewer: S4 hardcoded catalog names ('AAA'/'QHTTPX') to assert a
  negative, which is vacuous and violates this suite's real-API, name-agnostic
  convention. Replace with a contract assertion (search + tabs + a selectable
  lens-item render); writer-scoping is guaranteed by the dialog feeding the curated
  writer-scoped list, verified in the production-code review.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
Post-commit trio (all three) flagged the S4 coverage-map line still claiming it
lists 'only writer-scoped projects, not the global catalog' — the exact catalog-
exclusion assertion the prior commit removed. Update the docstring to describe what
S4 now verifies (search + tabs + a selectable list); writer-scoping is guaranteed by
the dialog feeding the curated creatableProjects, verified in production-code review,
not asserted in-suite. Comment-only.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
Full-branch sweep findings:

- Conventions reviewer: selectedContext (WritableSignal) was declared after the
  derived projectOptions signal; component-organization.md wants writables before
  derived. Move selectedContext above the derived signals. No behavior change.
- General reviewer: the Create button gates purely on the FGA writer relation with
  no ED fast-path, so an ED with no per-project writer grant sees no shortcut.
  Per product decision (LFXV2-2721) this stays writer-scoped; document the ED
  persona-differential explicitly in the service docstring as an accepted trade-off
  (EDs create via the in-page flows, which carry their own ED fast-path). Doc-only.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
When the user holds writer on exactly one project/foundation, the create dialog
pre-selects it on open so Continue is enabled immediately and the picker shows the
locked-in choice — no redundant open-and-pick for a list of one. Extracts the
selection logic into applySelection(), called from the constructor when
creatableProjects has a single entry and from onItemSelected otherwise. If the
list later widens (persona data resolving), the user can still change the pick.

Adds e2e S5, which adapts to the real-API test account: asserts Continue is enabled
on open when exactly one project is eligible, and gated until a pick when several are.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
…elect

General reviewer flagged that S3 asserted Continue disabled immediately on dialog
open, but the auto-select commit enables Continue on open for a single-eligible-
project account — so S3 would fail where S5 passes on that account shape. Remove
the on-open assertion (S5 now owns the enabled/disabled-on-open distinction); S3
keeps its real job: picking a project via the selector enables Continue.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
General reviewer (full-branch sweep) flagged the ProjectContext object being
rebuilt field-by-field in both onContinue and applySelection. Extract a single
toContext(project) helper so the field list lives in one place and can't drift if
ProjectContext gains a field. No behavior change.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 17, 2026 18:44
@manishdixitlfx
manishdixitlfx force-pushed the feat/LFXV2-2721-create-shortcuts branch from 2430104 to 18f0aeb Compare July 17, 2026 18:44
@manishdixitlfx manishdixitlfx changed the title feat(shortcuts): add global new shortcut for meeting, group, mailing list feat(shortcuts): global New create shortcut (LFXV2-2721) Jul 17, 2026
@manishdixitlfx
manishdixitlfx marked this pull request as ready for review July 17, 2026 18:47
@manishdixitlfx
manishdixitlfx requested a review from a team as a code owner July 17, 2026 18:47

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.

Comment thread packages/shared/src/constants/create-artifact.constants.ts
Comment thread apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.html Outdated
Comment on lines +86 to +90
await page
.getByTestId('create-rail-button')
.waitFor({ state: 'visible', timeout: RAIL_TIMEOUT })
.catch(() => undefined);
await skipWhenNoCreatePermission(page);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged — because the suite skips when the test account holds no writer grant, a render/permission/eligibility regression could pass silently. Making the positive path non-skippable needs a page.route stub of /api/projects (a deliberate shift from this suite's real-API, name-agnostic design) plus a separate fail-closed case. Tracking as a follow-up rather than expanding this PR — leaving this thread open.

@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
New navigation path aligns lens and project context before create routes; eligibility is client-derived from GET /api/projects and can under-show some roles, but it does not grant access beyond existing guards.

Overview
Adds a desktop rail “New” (+) shortcut that appears only when CreatePermissionService proves the user can create somewhere: writer on a project intersected with a reachable foundation/project lens. The button stays hidden until that check completes (fail-closed).

Flow: rail popover lists six creatable types from shared CREATABLE_ARTIFACTS (Engage / Decide / Organize, with separators) → CreateArtifactDialog picks foundation/project via lfx-project-selector fed a writer-scoped curated items list (not the nav catalog) → Continue aligns lens, seeds ProjectContextService without URL replace, and navigates to the existing create route with ?project=<slug>. Single eligible project is auto-selected.

Supporting changes: project-selector gains curated/local-search mode and dialog-friendly popover positioning; main-layout passes bannerOffset so the create popover tracks impersonation banner shift; Playwright create-quick-link.spec.ts covers S1–S6 (skips when no writer grant).

Note: Shortcut is UX-only—route writerGuard stays authoritative; EDs without per-project writer do not see the button (documented trade-off).

Reviewed by Cursor Bugbot for commit 8853ae6. Bugbot is set up for automated code reviews on this repo. Configure here.

@manishdixitlfx
manishdixitlfx marked this pull request as draft July 17, 2026 18:49

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/lfx-one/e2e/create-quick-link.spec.ts`:
- Around line 55-61: Replace skipWhenNoCreatePermission’s UI-based entitlement
check with writer eligibility derived independently from the projects response
or a deterministic fixture, skipping only when that source confirms no eligible
projects. In apps/lfx-one/e2e/create-quick-link.spec.ts lines 98-114, derive
expected artifact types from the eligible lenses instead of assuming six; in
lines 138-158, compare rendered projects against the independently derived
writer-eligible project set. Update all three sites while preserving valid
partial-eligibility assertions and preventing writer-scope leaks.

In
`@apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.scss`:
- Line 80: Add the same targeted Stylelint suppression for
selector-pseudo-element-no-unknown to both ::ng-deep rules in the lens-switcher
component stylesheet, including the .lfx-create-popover.p-popover and the other
Angular deep selector, without changing their styling behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 83580761-c1ba-4d55-9756-e604db9e8eb1

📥 Commits

Reviewing files that changed from the base of the PR and between cb72fa6 and 18f0aeb.

📒 Files selected for processing (13)
  • apps/lfx-one/e2e/create-quick-link.spec.ts
  • apps/lfx-one/src/app/layouts/main-layout/main-layout.component.html
  • apps/lfx-one/src/app/shared/components/create-artifact-dialog/create-artifact-dialog.component.html
  • apps/lfx-one/src/app/shared/components/create-artifact-dialog/create-artifact-dialog.component.ts
  • apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.html
  • apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.scss
  • apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.ts
  • apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.ts
  • apps/lfx-one/src/app/shared/services/create-permission.service.ts
  • packages/shared/src/constants/create-artifact.constants.ts
  • packages/shared/src/constants/index.ts
  • packages/shared/src/interfaces/create-artifact.interface.ts
  • packages/shared/src/interfaces/index.ts

Comment on lines +55 to +61
// Skip when the test user has no create permission — the button is intentionally absent.
async function skipWhenNoCreatePermission(page: Page): Promise<void> {
const trigger = page.getByTestId('create-rail-button');
const visible = await trigger.isVisible().catch(() => false);
if (!visible) {
test.skip(true, 'Test user holds `writer` on no project — button hidden by design.');
}

@coderabbitai coderabbitai Bot Jul 17, 2026

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.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Derive expected permissions independently from the UI under test.

The suite currently has no entitlement oracle, allowing regressions to be skipped, valid partial eligibility to fail, and writer-scope leaks to pass.

  • apps/lfx-one/e2e/create-quick-link.spec.ts#L55-L61: determine whether the user has writer eligibility from the projects response or a deterministic fixture, not button visibility.
  • apps/lfx-one/e2e/create-quick-link.spec.ts#L98-L114: derive the expected artifact types from eligible lenses rather than always expecting six.
  • apps/lfx-one/e2e/create-quick-link.spec.ts#L138-L158: compare rendered projects with the independently derived writer-eligible project set.
📍 Affects 1 file
  • apps/lfx-one/e2e/create-quick-link.spec.ts#L55-L61 (this comment)
  • apps/lfx-one/e2e/create-quick-link.spec.ts#L98-L114
  • apps/lfx-one/e2e/create-quick-link.spec.ts#L138-L158
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/lfx-one/e2e/create-quick-link.spec.ts` around lines 55 - 61, Replace
skipWhenNoCreatePermission’s UI-based entitlement check with writer eligibility
derived independently from the projects response or a deterministic fixture,
skipping only when that source confirms no eligible projects. In
apps/lfx-one/e2e/create-quick-link.spec.ts lines 98-114, derive expected
artifact types from the eligible lenses instead of assuming six; in lines
138-158, compare rendered projects against the independently derived
writer-eligible project set. Update all three sites while preserving valid
partial-eligibility assertions and preventing writer-scope leaks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged — an independent entitlement oracle (stub /api/projects, derive expected types from eligible lenses, compare rendered vs writer-eligible set) is a real improvement, but a heavier rework that flips this suite from real-API to stubbed. Tracking as a follow-up — leaving open.

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.

@manishdixitlfx Understood—keeping this review open. Would you like me to create and assign a GitHub follow-up issue for the independent entitlement-oracle coverage?

@audigregorie audigregorie 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.

Code Review Summary

A well-structured feature: fail-closed writer-gating, a clean items input extension to project-selector, and an honest, thorough trade-off write-up. The main concerns are (1) the writer list is fetched exactly once with no retry/refresh, (2) global lens/context state is mutated before the route guard authorizes, and (3) the popover position is hard-coded layout math. None block merge, but #1 and #2 are worth addressing or documenting as trade-offs.

Major — outside the diff

  • Full-catalog scan on every app loadgetProjects() pulls every visible project and batch access-checks it; the rail icon lags until that resolves on a cold load (no transfer cache). Acknowledged as a follow-up (GET /api/projects/creatable).
  • computeIsFoundation vs sidebar isFoundation — the dialog derives isFoundation client-side via computeIsFoundation(project) (funding/model heuristics), while the sidebar's lens-items endpoint carries its own isFoundation. If the two ever disagree for the same project, the dialog will dispatch a project through setFoundation() + setLens('foundation') and route it to the foundation-lens create mount. Low probability, worth a mental check.
  • getProjects() cache vs impersonationProjectService.projectsCache is keyed by params and lives on the root service; if it isn't cleared on impersonation start/stop, the create button (and the writer list) reflect the pre-impersonation user for the whole impersonation session. Worth verifying the impersonation flow invalidates this cache.

What's done well

  • Fail-closed visibility gating is the right default, and the writer-intersection-with-lens reasoning is documented honestly, including the ED trade-off and the meeting_coordinator under-show.
  • Reusing project-selector via a curated items input (with nav-backed behavior unchanged when null) is a clean extension that avoids forking the selector UI.
  • E2E suite is name-agnostic, skips cleanly when the test user has no writer grant, and the coverage-map docstring maps assertions to S1-S5.

// request during SSR, so the transfer cache would resolve it synchronously here and render
// the button on the client's first pass while the server rendered it absent — a hydration
// mismatch. `afterNextRender` runs browser-only, once the first render is committed.
afterNextRender(() => {

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.

Major — writer list fetched exactly once; no retry and no refresh

afterNextRender fires a single time and the subscribe sets writerProjects once. The class doc claims the button is hidden "until eligibility is proven", but a transient getProjects() failure (the doc itself notes the error path relies on the BFF emitting []) leaves writerProjects = [] for the entire session — indistinguishable from "no permission". The same staleness applies to mid-session grant changes and impersonation: ProjectService.getProjects() is shareReplay(1)-cached per param key, and nothing here re-fetches when the user's grants actually change. The follow-up GET /api/projects/creatable note acknowledges latency, but the once-only subscription is a correctness gap, not just perf.

Consider re-subscribing on a persona/impersonation change signal, or at minimum retrying once on an empty result that may be transient.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed this is a correctness gap, not just perf — a transient getProjects() failure hides the button for the whole session, and grant/impersonation changes don't refresh (afterNextRender fires once; getProjects() is shareReplay-cached). It's the same surface as the deferred optimistic/refresh work and the GET /api/projects/creatable follow-up; grouping them in that PR rather than expanding this one. Leaving open.

// persona doesn't hold — `writer` and lens roles are independent grants — and there is no way
// to align the context in that case, so bail before mutating anything rather than create
// against a stale project (writerGuard's ED fast-path would not catch it).
if (!this.lensService.setLens(project.isFoundation ? 'foundation' : 'project')) {

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.

Major — global lens + context mutated before writerGuard authorizes

onContinue calls setLens(...) then setProject/setFoundation(...) (both persist cookies / mutate selectedLens + navLensSelection) before router.navigate triggers the destination's writerGuard. The inline comment says "bail before mutating anything", but that only covers the setLens-returns-false branch — a downstream guard denial (or a cancelled/replaced navigation) leaves the user's prior lens and project context already overwritten. Today the denial path is nearly unreachable because writerGuard's getProject(slug) is also shareReplay-cached off the same writer flag the dialog used, but the mutation order is still fragile.

Prefer seeding the slug via the ?project= query param and letting projectQueryParamGuard + the create page do the lens/context alignment after the guard resolves, or roll these mutations into a router.navigate resolved-handler.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point on the mutation order. We already navigate with ?project=<slug> (so writerGuard is authoritative on the target), but setLens/setProject do persist before the guard resolves — a denial would leave stale lens/context. Reworking to seed via the query param + a resolved-handler is a fair follow-up; today the denial path is near-unreachable (same shared writer flag), so I'd rather not expand this PR's surface. Leaving open for the follow-up.

Comment on lines +106 to +124
top: 58px !important;

// stylelint-disable-next-line declaration-no-important
bottom: auto !important;

// stylelint-disable-next-line declaration-no-important
margin-top: 0 !important;
}

// Applied when the host layout declares it shifts the rail down to clear a banner — `main-layout`
// under impersonation (`top-12`, 48px), taking the Create button with it. Driven by the
// `bannerOffset` input rather than the impersonation flag, because the shift is layout-local: the
// docs shell pins the rail at `top-0` and renders no banner even while impersonating. 58 + 48 = 106px.
// Both classes are stacked deliberately: matching only `.lfx-create-popover-banner-offset` ties
// the base rule on specificity, so this would win on source order alone and silently revert to
// 58px if either block moved.
::ng-deep .lfx-create-popover.lfx-create-popover-banner-offset.p-popover {
// stylelint-disable-next-line declaration-no-important
top: 106px !important;

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.

Major — popover position is hard-coded layout math

top: 58px (and 106px for the banner variant) are derived by hand from rail padding + logo height + margins. Any change to the logo size, rail padding, or a new banner height silently misaligns the popover from its trigger, and bannerOffset only covers the impersonation case — other rail shifts aren't covered. The bottom-anchored apps/user menus get away with this because they pin to the bottom; a top-anchored panel is sensitive to everything above it.

Consider measuring the trigger's getBoundingClientRect().top on open — project-selector already does this in alignPanelTop() — instead of hard-coding the offset.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed the hardcoded top: 58px/106px is fragile. Measuring getBoundingClientRect().top like project-selector.alignPanelTop() is the right fix; grouping it with the popover-positioning follow-up rather than this PR. Leaving open.

Comment thread apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.html Outdated
Address review comments from @audigregorie, copilot[bot], coderabbitai[bot]:

- create-artifact dialog: name the role=dialog via ariaLabelledBy -> the body's
  <h2 id=create-artifact-heading> (showHeader:false emits no title) (per copilot[bot])
- lens-switcher.scss: suppress stylelint selector-pseudo-element-no-unknown on both
  ::ng-deep rules so CI stylelint passes (per coderabbitai[bot])
- lens-switcher.html: aria-label 'New' -> 'Create new artifact' (per @audigregorie)
- project-selector items input: document the curated-mode divergence — role badges and
  All-tab nesting key off persona-detection, so writer-only items render flat/unbadged
  (per @audigregorie)
- e2e S6: assert Continue routes to the lens-prefixed create URL carrying ?project=<slug>
  (per copilot[bot])
- lens-switcher.html: document the shortcut is desktop-rail only by design; mobile deferred
  (per copilot[bot])

Deferred to follow-up (responded on-thread, threads left open): entitlement-oracle e2e
stub, once-only writer fetch retry/refresh, lens/context mutation-before-guard, and
dynamic popover positioning.

Resolves 10 of 16 review threads.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 17, 2026 19:24
@manishdixitlfx

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: ec90b2954

Changes Made

  • Dialog a11y — named the role="dialog" via ariaLabelledBy → the body's <h2 id="create-artifact-heading"> (showHeader:false emits no title) (per copilot[bot])
  • lens-switcher.component.scss — suppressed stylelint selector-pseudo-element-no-unknown on both ::ng-deep rules so CI stylelint passes (per coderabbitai[bot])
  • lens-switcher.component.htmlaria-label "New""Create new artifact" (per @audigregorie)
  • project-selector items input — documented the curated-mode divergence: role badges and All-tab nesting key off persona-detection, so writer-only items render flat/unbadged (per @audigregorie)
  • E2E S6 — asserts Continue routes to the lens-prefixed create URL carrying ?project=<slug> (per copilot[bot])
  • lens-switcher.component.html — documented the shortcut is desktop-rail only by design; mobile deferred (per copilot[bot])

No Change Needed

  • Six-type scope — the feature intentionally expanded to six types during review; PR title/description now document that. The 3-type wording was the original commit, superseded (flagged by copilot[bot])

Threads Resolved

10 of 16 threads resolved this iteration.

Still Open (deferred to a follow-up PR)

  • Entitlement-oracle E2E stub — make the positive path non-skippable by stubbing /api/projects; a shift from this suite's real-API design (flagged by copilot[bot], coderabbitai[bot])
  • Once-only writer fetch — retry/refresh on transient failure and on grant/impersonation change; same surface as the deferred optimistic/refresh + GET /api/projects/creatable work (per @audigregorie)
  • Lens/context mutation before writerGuard — seed via ?project= + resolved-handler instead of persisting lens/context pre-navigation (per @audigregorie)
  • Dynamic popover positioning — measure getBoundingClientRect().top instead of hardcoded top: 58px/106px (per @audigregorie)

These four are grouped into the follow-up PR (optimistic-load / latency / positioning) rather than expanding this one's surface.

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Comment on lines +68 to +70
public readonly creatableProjects: Signal<CreatableProject[]> = computed(() => {
const lenses = this.lensService.availableLenses();
return this.writerProjects().filter((project) => lenses.some((lens) => lens.id === (project.isFoundation ? 'foundation' : 'project')));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged — before persona resolves, availableLenses() uses the default persona, so there's a brief window where a writer project could render under the wrong lens. This is the same persona-readiness/loading surface as the deferred optimistic-load + refresh work; gating on personaLoaded() also trades against the icon-load latency we're separately addressing. Grouping it into that follow-up rather than expanding this PR. Leaving open.

Comment on lines +57 to +62
async function skipWhenNoCreatePermission(page: Page): Promise<void> {
const trigger = page.getByTestId('create-rail-button');
const visible = await trigger.isVisible().catch(() => false);
if (!visible) {
test.skip(true, 'Test user holds `writer` on no project — button hidden by design.');
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same as the entitlement-oracle thread above — making the positive path non-skippable needs a page.route stub of /api/projects (a deliberate shift from this suite's real-API, name-agnostic design) plus a fail-closed case. Tracked as a follow-up. Leaving open.

@manishdixitlfx
manishdixitlfx marked this pull request as ready for review July 17, 2026 19:45
@manishdixitlfx
manishdixitlfx marked this pull request as draft July 17, 2026 19:46
// The slug keeps writerGuard authoritative on the chosen project and lets
// projectQueryParamGuard re-seed real project data on the lens-prefixed mounts.
this.router.navigate([this.artifact.createRoute], { queryParams: { project: project.slug } });
this.dialogRef.close(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Context set before guards

Medium Severity

onContinue calls setLens, then setFoundation/setProject, then router.navigate. Route guards such as writerGuard run after those mutations. If navigation is denied, the user is redirected but lens and project context may already match the attempted create target instead of where they started.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ec90b29. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged (same as the mutation-order thread): onContinue persists lens/context before writerGuard runs, so a denial could leave the create-target context. We navigate with ?project= (guard authoritative) and the denial path is near-unreachable today (shared writer flag), but reworking to seed via the query param + a resolved-handler is the right fix — tracked in the follow-up. Leaving open.

takeUntilDestroyed(this.destroyRef)
)
.subscribe((projects) => this.writerProjects.set(projects));
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Create eligibility never refreshes

Medium Severity

The rail “Create” affordance loads writer projects once inside afterNextRender via a single getProjects() subscription. That observable is cached with shareReplay, so a failed or empty first emission keeps the button hidden for the whole SPA session, and later grant or impersonation changes are not picked up without a full reload.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ec90b29. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged (same as the once-only-fetch thread): afterNextRender fires once and getProjects() is shareReplay-cached, so a transient failure hides the button for the session and grant/impersonation changes don't refresh. Grouped with the deferred optimistic-load/refresh + GET /api/projects/creatable work. Leaving open.

@manishdixitlfx
manishdixitlfx marked this pull request as ready for review July 17, 2026 19:48
…lector

Address PR #1121 review from copilot[bot]: initLensTypeLabel() checked only
detectedProjects() then fell back to the active lens, so a curated (writer-scoped)
entry not present in persona-detection could show the wrong kind — a foundation
selected from a project page labelled 'Project' and vice versa. Resolve the selected
uid from the curated items() (which carry isFoundation) before the active-lens
fallback. Sidebar (non-curated) behavior unchanged.

Resolves 1 review thread.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 17, 2026 19:56
@manishdixitlfx

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed — iteration 2

Commit: 79be8aa9e

Changes Made

  • project-selector.component.ts — curated-mode kind label: initLensTypeLabel() now resolves the selected uid from the curated items() before the active-lens fallback, so a writer-reachable-but-not-persona-detected foundation/project is labelled correctly (per copilot[bot])

Threads Resolved

1 of the 5 new threads this iteration.

Still Open (deferred to the follow-up PR)

The remaining threads restate four architectural items already tracked for the follow-up (optimistic-load / refresh / positioning / guard-ordering), now also raised by copilot[bot] and cursor[bot]:

  • Persona-readiness fail-closed window — gate eligibility on personaLoaded(); trades against icon-load latency (per copilot[bot])
  • Entitlement-oracle E2E — stub /api/projects for a non-skippable positive path (per copilot[bot], coderabbitai[bot])
  • Context set before guards — seed via ?project= + resolved-handler instead of persisting lens/context pre-navigation (per @audigregorie, cursor[bot])
  • Create eligibility never refreshes — retry/refresh on transient failure and grant/impersonation change (per @audigregorie, cursor[bot])

These are grouped into the follow-up PR rather than expanding this one's surface; each has a rationale on-thread.

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Comment on lines +68 to +70
public readonly creatableProjects: Signal<CreatableProject[]> = computed(() => {
const lenses = this.lensService.availableLenses();
return this.writerProjects().filter((project) => lenses.some((lens) => lens.id === (project.isFoundation ? 'foundation' : 'project')));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same persona-readiness point as the thread above — before persona resolves, availableLenses() uses the default (contributor) persona, so there's a brief window where a writer project could surface under the wrong lens (or an ED's foundation be filtered until persona loads). Gating on personaLoaded() also trades against the icon-load latency we're separately addressing, so it's grouped into the optimistic-load/refresh follow-up rather than this PR. Leaving open.

Comment thread apps/lfx-one/e2e/create-quick-link.spec.ts Outdated
…ix assertion

Address PR #1121 review from copilot[bot]:

- project-selector: the panel forced sidebar-only 'position: fixed; left: 356px' at
  lg+, so in the create dialog the picker opened beside the sidebar instead of its
  trigger. Add a curated panel variant ('project-selector-panel-curated') with the
  cosmetics only (no fixed left/top) and skip alignPanelTop() in curated mode, so
  PrimeNG anchors the popover to its own trigger inside the dialog. Sidebar unchanged.
- e2e S6: the URL regex also matched the flat /meetings/create, so a setLens/
  lensRedirectGuard regression would still pass. Require the /(foundation|project)/
  lens prefix explicitly.

Resolves 2 review threads.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 17, 2026 20:47
@manishdixitlfx

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed — iteration 3

Commit: 5ca3c640e

Changes Made

  • project-selector (ts/html/scss) — the reused picker panel forced sidebar-only position: fixed; left: 356px at lg+, so in the create dialog it opened beside the rail instead of the trigger. Added a curated panel variant (cosmetics only, no fixed left/top) + skip alignPanelTop() in curated mode; sidebar behavior unchanged (per copilot[bot])
  • e2e S6 — tightened the URL assertion to require the /(foundation|project)/ lens prefix, so a setLens/lensRedirectGuard regression can't pass on the flat /meetings/create (per copilot[bot])

Threads Resolved

2 of the 3 new threads this iteration.

Still Open (deferred to the follow-up PR)

  • Persona-readiness fail-closed window (create-permission.service.ts:70) — gate eligibility on personaLoaded(); grouped with the optimistic-load/refresh follow-up since it trades against icon-load latency (per copilot[bot])
  • Plus the 9 previously-deferred threads (entitlement-oracle E2E, once-only fetch refresh, context-before-guard, hardcoded create-menu positioning) — all tracked for the follow-up.

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/lfx-one/e2e/create-quick-link.spec.ts`:
- Around line 203-205: Replace the interpolated regex assertion in the
quick-link destination check with structural URL validation: parse page.url()
using URL, assert the pathname matches the required foundation/project
lens-prefixed meetings/create route, and compare searchParams.get('project')
directly with slug. Preserve the existing timeout and reject bare
/meetings/create paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 83f2d536-21eb-43ff-9c4d-0455a917adc2

📥 Commits

Reviewing files that changed from the base of the PR and between 79be8aa and 5ca3c64.

📒 Files selected for processing (4)
  • apps/lfx-one/e2e/create-quick-link.spec.ts
  • apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.html
  • apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.scss
  • apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.ts

Comment thread apps/lfx-one/e2e/create-quick-link.spec.ts

Copilot AI 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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Comment thread apps/lfx-one/e2e/create-quick-link.spec.ts
@manishdixitlfx
manishdixitlfx marked this pull request as draft July 17, 2026 20:54
Copilot AI review requested due to automatic review settings July 17, 2026 21:00

Copilot AI 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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

type="button"
(click)="toggleCreateMenu($event)"
data-testid="create-rail-button"
aria-label="Create new artifact"
Comment on lines +32 to +33
* Note: this suite stops at the dialog boundary. It does not assert the post-Continue
* create page — that path is enforced by each route's writerGuard.
@manishdixitlfx
manishdixitlfx marked this pull request as ready for review July 18, 2026 00:26
@manishdixitlfx
manishdixitlfx merged commit 3479125 into main Jul 18, 2026
13 checks passed
@manishdixitlfx
manishdixitlfx deleted the feat/LFXV2-2721-create-shortcuts branch July 18, 2026 00:26

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 8853ae6. Configure here.

const dialog = page.getByTestId('create-artifact-dialog');
await dialog.getByTestId('project-selector').click();
const panel = page.getByTestId('project-selector-panel');
await expect(panel).toBeVisible({ timeout: 5_000 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

E2E panel locator ambiguous

Medium Severity

The new create dialog reuses lfx-project-selector, so a second project-selector-panel node exists whenever the sidebar already shows the project picker. S3–S6 resolve the panel with a page-level getByTestId('project-selector-panel') while only scoping the trigger to the dialog. On /, the active lens often stays project or foundation from cookies, so Playwright can hit strict-mode violations or interact with the wrong panel.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8853ae6. Configure here.

@manishdixitlfx

manishdixitlfx commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Deferred follow-ups tracked → LFXV2-2749

Thanks all for the thorough review. The four architectural items intentionally deferred from this PR (to keep the initial feature scoped) are now tracked in LFXV2-2749:

  • Session-accurate create eligibility + refresh on transient failure / grant / impersonation change (per @audigregorie, cursor[bot])
  • Fail-closed visibility while persona data is still loading (per copilot[bot])
  • Faster eligibility (dedicated creatable-projects source, replacing the full-catalog access check) — also the icon-load latency
  • Non-skipping create-flow E2E via an independent eligibility oracle (per copilot[bot], coderabbitai[bot])
  • Reliable create-menu popover positioning (per @audigregorie)

All net-new findings from the review passes were addressed in-PR (a11y ariaLabelledBy, stylelint ::ng-deep suppressions, aria-label, curated-mode kind label, tightened S6 lens-prefix assertion, and the picker panel positioning fix). The remaining open threads map 1:1 to the LFXV2-2749 items above.

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.

3 participants