Skip to content

feat: add "View activity footprint" row action in Users list#302

Merged
AllTerrainDeveloper merged 2 commits into
trunkfrom
add-activity-footprint-in-classic-user-view
Jun 7, 2026
Merged

feat: add "View activity footprint" row action in Users list#302
AllTerrainDeveloper merged 2 commits into
trunkfrom
add-activity-footprint-in-classic-user-view

Conversation

@AllTerrainDeveloper
Copy link
Copy Markdown
Collaborator

@AllTerrainDeveloper AllTerrainDeveloper commented Jun 7, 2026

  • Introduced a new row action in the classic Users list table to view a user's GitHub-style activity footprint.
  • Implemented the desktop_mode_user_footprint_row_action filter to control the visibility of the action based on user roles and context.
  • Enhanced the chromeless iframe bridge to handle clicks on the new action and open the My WordPress window without navigating away from the Users list.
  • Added necessary JavaScript and TypeScript functions to manage the footprint target across bundles, ensuring cold-start safety and shared state.
  • Created unit tests for the new functionality, covering various scenarios including action visibility and shared store behavior.
Open WordPress Playground Preview

- Introduced a new row action in the classic Users list table to view a user's GitHub-style activity footprint.
- Implemented the `desktop_mode_user_footprint_row_action` filter to control the visibility of the action based on user roles and context.
- Enhanced the chromeless iframe bridge to handle clicks on the new action and open the My WordPress window without navigating away from the Users list.
- Added necessary JavaScript and TypeScript functions to manage the footprint target across bundles, ensuring cold-start safety and shared state.
- Created unit tests for the new functionality, covering various scenarios including action visibility and shared store behavior.
@AllTerrainDeveloper
Copy link
Copy Markdown
Collaborator Author

@claude can you review?

@claude
Copy link
Copy Markdown

claude Bot commented Jun 7, 2026

Claude finished @AllTerrainDeveloper's task in 3m 53s —— View job


PR Review — feat: add "View activity footprint" row action in Users list

  • Gather context and understand request
  • Read changed PHP files
  • Read changed TypeScript files
  • Read changed test files
  • Read changed docs files
  • Analyze diff vs origin/trunk
  • Post full review

Overall this is a well-structured, thoroughly-documented feature. The cross-bundle shared-store hand-off, cold-start safety, and "source window must NOT close" are all handled correctly and follow existing patterns. A few items worth addressing before merge:


Issues

1. postId: userId — wrong field in StatusContext (minor but confusing)

renderUserFootprint passes a user ID into StatusContext.postId:

// src/my-wordpress/index.ts ~line 4599
paintStatus(
    state,
    [ ... ],
    { view: 'detail', entityId: entity.id, postId: userId },
);

StatusContext.postId is intended for a WordPress post ID, not a user ID. Plugins that inspect the desktop-mode.my-wordpress.status-bar filter's context to decide whether to append a segment will likely check postId and get confused. Either add a userId field to StatusContext, or use view: 'user-footprint' (a new value) to make the context self-describing. The existing 'detail' view value also makes the filter context misleading for plugins that distinguish list vs. detail vs. sub-list.

Fix this →


2. Window-stash → store promotion gap (design concern)

In footprint-target.ts, if setFootprintTarget is called before wp.desktop.createSharedStore is available (rare but possible early in boot), the target lands in window._wpdFootprintTarget. If readFootprintTarget is then called after the store is initialised, getStore() returns the real store (state: {userId: null, …}) and the window-stash value is silently ignored:

// footprint-target.ts ~line 129
export function readFootprintTarget(): FootprintTarget {
    const store = getStore();
    if ( store ) {
        return { ...store.state };  // ← store state is still _initial; stash is lost
    }
    ...
}

This is identical to the pre-existing behaviour in user-edit-target.ts (which this module mirrors), so it's an accepted limitation — but it's worth a short comment on getStore() to make the trade-off explicit, similar to the comment already in setFootprintTarget.


3. requestedAt set but never read (minor)

The requestedAt field is documented as letting consumers "tell a fresh request apart from a stale one and dedupe the mount-read vs. the subscribe callback", but no code in this PR actually uses it for deduplication. The subscribe callback in index.ts always navigates when userId > 0:

// index.ts ~line 6325
subscribeFootprintTarget( ( next ) => {
    if ( ! next.userId || next.userId <= 0 || ! activeState ) {
        return;
    }
    navigate( activeState, ... );
} );

A rapid double-open (two quick clicks on different users in the Users table) triggers two setFootprintTarget + notify() calls. The subscribe fires twice and navigates twice, which is correct — but if the window ever batches subscriber notifications the requestedAt field could disambiguate. Either wire the dedup now or add a // reserved for future dedup note on the field.


Positive observations

Correct placement of the footprint interceptor. The data-desktop-mode-footprint check in chromeless-bridge.php sits before classifyLink, so the fallback href (user-edit.php) is never followed inside the shell. The comment makes this explicit.

Source window not closed. handleWindowMessage in iframe-bridge.ts correctly calls openUserFootprintWindow without calling win.close(). The comment ("a row action is an auxiliary peek, not a navigation away — contrast desktop-mode-iframe-admin-link") is exactly the kind of non-obvious rationale AGENTS.md asks for.

Test coverage is solid. The Vitest suite asserts the "source window NOT closed" property directly (line 155), covers invalid userIds (0, non-number), and tests the shared-store round-trip. The PHP test covers markup escaping (Edie &amp; Co), the self-row profile.php fallback, the detached-classic-tab exclusion, and filter suppression.

Cold-start safety. The shared store mediates between the window-system bundle (which calls openUserFootprintWindow) and the lazy My WordPress bundle (which reads on mount and subscribes for warm re-targets). The module-level _store singleton plus lazy getStore() exactly mirrors user-edit-target.ts, so reviewers familiar with that module will follow the pattern immediately.

PHP escaping is correct. esc_url on the fallback URL, esc_attr on the display name (catching the & in "Edie & Co"), esc_html__ on the label text — all three vectors are covered.

Docs are complete. bridge-protocol.md, hooks-reference.md, and javascript-reference.md are all updated in the same PR per AGENTS.md requirements. The carrier-contract table in bridge-protocol.md is a particularly useful addition.


Minor nits

  • footprint-target.ts:73_initial is declared const but all its properties are primitives. Consider Object.freeze( _initial ) so callers can't accidentally mutate the sentinel, or leave it as-is since it's only spread, never mutated directly.
  • users-list-footprint.php:52(int) $user_object->ID is technically redundant (WP_User::$ID is already an integer), but it reads as intentional defensive coding — fine to keep.
  • The Vitest test does not cover the window._wpdFootprintTarget path (because createSharedStore is always present in the test environment). A single test that deletes window.wp before calling setFootprintTarget and restores it before readFootprintTarget would close that gap.

· Branch: add-activity-footprint-in-classic-user-view

@AllTerrainDeveloper AllTerrainDeveloper enabled auto-merge (squash) June 7, 2026 20:41
@AllTerrainDeveloper AllTerrainDeveloper merged commit 4bd5149 into trunk Jun 7, 2026
5 checks passed
@AllTerrainDeveloper AllTerrainDeveloper deleted the add-activity-footprint-in-classic-user-view branch June 7, 2026 20:44
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