Skip to content

Replace DevSessionUI footer with tabbed interface to display more app information #6121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

nickwesselman
Copy link
Contributor

@nickwesselman nickwesselman commented Jul 16, 2025

The new app dev command for Dev Platform makes the app URL less accessible. This PR adds a new App Information tab accessible via the i key in app dev, which includes the app URL and more. The tabs can also be navigated via arrow keys.

It also adds the App URL as an output to the app dev logs, via dev session messaging on the app home component.

Demo / Screenshots

17-42-yn95m-c3ydv.mp4
image image image

Summary

  • Replaces the simple horizontal line separator in the DevSessionUI footer with a visual tabbed interface
  • Implements connected box drawing character borders (╒═╤═╕, └─┴─┘) that create a cohesive tabbed design
  • Refactors input handling to use object-based tab structure with Status, App Info, and Quit tabs
  • Moves keyboard shortcuts (p, g) into tab-specific data structures for better organization
  • Adds comprehensive app information display including app name, URL, config path, dev store, and organization
  • Always shows all tabs regardless of app readiness state for consistent UX

Test plan

  • DevSessionUI tests pass (16/16 tests)
  • Linting passes with no errors
  • Visual design shows connected tabbed interface with proper box drawing characters
  • Tab switching works correctly (s for Status, i for App Info, q for Quit)
  • Keyboard shortcuts work within Status tab (p for preview, g for GraphiQL)
  • Terminal width calculations prevent character wrapping issues
  • App information displays correctly with all provided fields

🤖 Generated with Claude Code

@nickwesselman nickwesselman requested review from a team as code owners July 16, 2025 18:59
@nickwesselman nickwesselman changed the title Replace DevSessionUI footer with tabbed interface using box drawing characters Replace DevSessionUI footer with tabbed interface to display more app information Jul 16, 2025
Copy link
Contributor

github-actions bot commented Jul 16, 2025

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements
78.11% (+0.06% 🔼)
13047/16703
🟡 Branches
72.43% (+0.17% 🔼)
6397/8832
🟡 Functions
78.31% (+0.13% 🔼)
3390/4329
🟡 Lines
78.53% (+0.07% 🔼)
12353/15731
Show new covered files 🐣
St.
File Statements Branches Functions Lines
🟢
... / TabPanel.tsx
97.73% 84% 100% 97.5%
Show files with reduced coverage 🔻
St.
File Statements Branches Functions Lines
🟢
... / app_config_app_home.ts
83.33% (-16.67% 🔻)
100%
50% (-50% 🔻)
83.33% (-16.67% 🔻)
🟢
... / info.ts
81.44%
61.19% (-1.49% 🔻)
90.32% 83.15%
🟢
... / Dev.tsx
90.59% (-2.35% 🔻)
75% (-1.79% 🔻)
86.36% (-4.55% 🔻)
92.5% (-1.25% 🔻)
🔴
... / partners-client.ts
24.7% (-1.09% 🔻)
26.09% (-3.91% 🔻)
17.74% (-0.29% 🔻)
24.53% (-0.96% 🔻)

Test suite run success

3043 tests passing in 1313 suites.

Report generated by 🧪jest coverage report action from 65547ff

@gonzaloriestra
Copy link
Contributor

/snapit

Copy link
Contributor

🫰✨ Thanks @gonzaloriestra! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

pnpm i -g @shopify/[email protected]

Tip

If you get an ETARGET error, install it with NPM and the flag --@shopify:registry=https://registry.npmjs.org

Caution

After installing, validate the version by running just shopify in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

Copy link
Contributor

@gonzaloriestra gonzaloriestra left a comment

Choose a reason for hiding this comment

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

Nice work! I love the new UI, it works perfectly and the code LGTM. Have you tested it on Windows/Linux?

Some suggestions (not blocking):

  • I'd enable the left/right arrows to switch between the tabs
  • Not sure how to fix this, but the (q) Quit tab is actually not a usable tab, but more like a button... Maybe a different style or place?
  • The App URL and dev store should be hyperlinks (they appear underscored):
Monosnap esbuild 2025-07-17 12-05-59
  • The text on the disabled tabs is a bit hard to read with light themes (see above)

@nickwesselman
Copy link
Contributor Author

The App URL and dev store should be hyperlinks (they appear underscored)

🤔 I'm not sure on this as it's meant to be informational vs clicked. Opening the App URL directly with our Remix template is actually quite confusing -- it lands you on a mock marketing page for the app. The dev store could be useful though, maybe even with an additional admin link.

@nickwesselman
Copy link
Contributor Author

/snapit

Copy link
Contributor

🫰✨ Thanks @nickwesselman! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

pnpm i -g @shopify/[email protected]

Tip

If you get an ETARGET error, install it with NPM and the flag --@shopify:registry=https://registry.npmjs.org

Caution

After installing, validate the version by running just shopify in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@nickwesselman
Copy link
Contributor Author

Implemented PR feedback and more:

  • Changed to a simple inverse for the selected tab, so that things work better in light mode and other themes
  • Added arrow-based navigation
  • Reformatted the quit "tab" to differentiate it as an action
  • Added a hyperlink for the dev store, and a link to dev store admin
  • Fixed App Info display for extension-only apps (don't display empty app info)

I also tested on Windows and Linux (via Ubuntu on WSL).

nickwesselman and others added 17 commits July 17, 2025 12:24
- Add appURL prop to DevSessionUI component interface
- Display App URL in terminal footer when available
- Pass development URL from config through renderDev to DevSessionUI
- Show App URL above Preview URL and GraphiQL URL for consistent ordering

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
This commit introduces a comprehensive app information modal accessible via the 'i' key during `shopify app dev` sessions. The modal displays essential app details including app name, app URL, config file, dev store, and organization in a clean, formatted interface.

Key features:
- Modal triggered by 'i' key (when app preview is ready)
- Escape key or 'i' key again to close
- Uses Alert component from cli-kit for consistent styling
- Hides preview/GraphiQL URLs when modal is active
- Comprehensive test coverage with 21 test cases
- Follows existing code patterns and conventions

Files changed:
- DevSessionUI: Added modal state management and keyboard handlers
- New utility: formatAppInfoBody for consistent formatting
- Enhanced prop threading through dev session components
- Updated test snapshots and added extensive test coverage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
The Box wrapper around the persistent dev alert was inadvertently removed.
This change restores the proper spacing and layout consistency by adding
back the Box component with marginTop={1} and flexDirection="column".

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
…aracters

- Convert app info modal to persistent tabbed interface (Status, App Info, Quit)
- Add connected box drawing character borders (╒═╤═╕, └─┴─┘) for visual tab connection
- Refactor input handling to use object-based tab structure for cleaner code
- Update tests to match new tabbed behavior

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Remove 'switches back to status tab when s is pressed after viewing info tab' test (covered by comprehensive tab switching test)
- Remove 'hides preview and graphiql URLs when app info tab is shown' test (covered by tab switching test)
- Maintain same functional coverage with cleaner test suite
- Only show tabbed interface when canUseShortcuts is true (raw mode supported)
- Fall back to simple border layout when shortcuts are not available
- Refactor non-interactive fallback to reuse status tab content directly
- Add test for non-interactive fallback with proper useStdin mocking
- Prevents confusing tabbed interface display in CI/non-interactive environments

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
These files were part of the old modal-based app info system that has been replaced by the tabbed interface using TabularData. The formatAppInfoBody function was designed for Alert components with "Press Esc to close" functionality, which is no longer needed.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Refactored the DevSessionUI to improve separation of concerns:

- Created new TabPanel component for reusable tab functionality
- Moved tab state management and input handling to TabPanel
- Kept global concerns (Ctrl+C, error handling) in DevSessionUI
- Cleaned up component APIs by only exporting necessary types
- Improved code organization and maintainability

The TabPanel component now handles:
- Tab state management (activeTab)
- Tab navigation input handling
- Tab rendering with box drawing characters
- Tab-specific shortcuts

DevSessionUI maintains responsibility for:
- Global input handling (Ctrl+C)
- Application lifecycle management
- Error state management
- Business logic coordination

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Add full test coverage for TabPanel component with 9 test cases
- Make initialActiveTab prop optional with fallback to first tab
- Add runtime validation to handle empty tabs gracefully
- Improve error handling with proper undefined state management
- Use nullish coalescing operator for better type safety

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Remove redundant tests for tab switching, tab rendering, and content display
- Keep simplified app info test focused on DevSessionUI behavior
- Retain raw mode fallback test as it's DevSessionUI-specific UI logic
- Improve test separation of concerns with comprehensive TabPanel coverage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Add arrow key navigation to TabPanel for content tabs only
  - Navigate between tabs with left/right arrows
  - Skip action-only tabs during navigation
  - Prevent wraparound at tab boundaries
- Enhance App Info tab with clickable store links
  - Convert Dev Store to clickable link
  - Add new Dev Store Admin entry with link
  - Filter out empty table rows
- Update tab styling and improve UX
  - Change quit tab label from "Quit" to "to quit"
  - Improve active tab styling with inverse highlighting
  - Dim action tab labels for visual distinction
- Add comprehensive test coverage for arrow key navigation

These improvements were made in response to PR feedback.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@nickwesselman nickwesselman force-pushed the feature/app-info-tabs branch from 67f55fa to 65547ff Compare July 17, 2025 16:24
Copy link
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/public/node/ui/components.d.ts
@@ -1,3 +1,4 @@
 export { ConcurrentOutput, ConcurrentOutputContext, useConcurrentOutputContext, } from '../../../private/node/ui/components/ConcurrentOutput.js';
 export { Alert } from '../../../private/node/ui/components/Alert.js';
-export { Link } from '../../../private/node/ui/components/Link.js';
\ No newline at end of file
+export { Link } from '../../../private/node/ui/components/Link.js';
+export { TabularData } from '../../../private/node/ui/components/TabularData.js';
\ No newline at end of file

@nickwesselman
Copy link
Contributor Author

/snapit

Copy link
Contributor

🫰✨ Thanks @nickwesselman! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

pnpm i -g @shopify/[email protected]

Tip

If you get an ETARGET error, install it with NPM and the flag --@shopify:registry=https://registry.npmjs.org

Caution

After installing, validate the version by running just shopify in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@megthesmith
Copy link

  • Not sure how to fix this, but the (q) Quit tab is actually not a usable tab, but more like a button... Maybe a different style or place?

I agree with this feedback. If the q tab is genuinely not navigable, but more a quick action you can take to terminate the process, I'd still house it in the main screen with the other short-keys to deal with the ongoing server process. Then the tabs can remain purely about accessing the different tabbed states in the CLI, and be cleanly and consistently arrow-navigated too.

export const TabPanel: React.FunctionComponent<TabPanelProps> = ({tabs, initialActiveTab}) => {
const {isRawModeSupported: canUseShortcuts} = useStdin()
const firstTabKey = Object.keys(tabs)[0]
const [activeTab, setActiveTab] = useState<string | undefined>(initialActiveTab ?? firstTabKey)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this really be undefined? I feel like TabPanelProps could have initialActiveTab as a mandatory property.

let newIndex
// Right arrow
if (key?.rightArrow) {
newIndex = currentIndex + 1 < contentTabs.length ? currentIndex + 1 : currentIndex
Copy link
Contributor

@isaacroldan isaacroldan Jul 18, 2025

Choose a reason for hiding this comment

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

no need to change it if you prefer your way, but is this easier to follow?:

newIndex = min(currentIndex + 1, contentTabs.length - 1)

and

newIndex = max(currentIndex - 1, 0)

Also I think we can simplify this code to have less indented ifs:

        if (key?.leftArrow || key?.rightArrow) {
          const contentTabs = Object.entries(tabs).filter(([_, tab]) => tab.content)
          const currentIndex = contentTabs.findIndex(([tabKey]) => tabKey === activeTab)
          if (currentIndex === -1) return
          const modifier = key.leftArrow ? -1 : 1
          const newIndex = Math.min(Math.max(currentIndex + modifier, 0), contentTabs.length - 1)
          const newTabEntry = contentTabs[newIndex]
          if (newTabEntry) {
            setActiveTab(newTabEntry[0])
          }
        }

Does this make sense? is it easier or harder to follow?

Copy link
Contributor

Is easy to break it by resizing the terminal. Should the tab container width be limited to the size of the tabs to prevent this?

Since it now takes the whole width, any minor resize breaks it, with a smaller container we prevent that unless you make a big resize.

Captura de pantalla 2025-07-18 a las 16.34.52.png

@nickwesselman
Copy link
Contributor Author

nickwesselman commented Jul 18, 2025

Is easy to break it by resizing the terminal. Should the tab container width be limited to the size of the tabs to prevent this?

Yeah @megthesmith has some ideas for the design that may help with this, I am going to try some iterations.

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.

4 participants