Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Stack metadata is stored in `.git/gh-stack` (a JSON file, not committed to the r
Initialize a new stack in the current repository.

```
gh stack init [branches...] [flags]
gh stack init [flags] [branches...]
```

Creates an entry in `.git/gh-stack` to track stack state. In interactive mode (no arguments), prompts you to name branches and offers to use the current branch as the first layer. In interactive mode, you'll also be prompted to set an optional branch prefix (unless adopting existing branches). When a prefix is set, branch names you enter are automatically prefixed. When explicit branch names are given, creates any that don't already exist (branching from the trunk). The trunk defaults to the repository's default branch unless overridden with `--base`.
Expand Down Expand Up @@ -115,7 +115,7 @@ gh stack init -p feat --numbered
Add a new branch on top of the current stack.

```
gh stack add [branch] [flags]
gh stack add [flags] [branch]
```

Creates a new branch at the current HEAD, adds it to the top of the stack, and checks it out. Must be run while on the topmost branch of a stack. If no branch name is given, prompts for one.
Expand Down Expand Up @@ -190,7 +190,7 @@ gh stack checkout
Pull from remote and do a cascading rebase across the stack.

```
gh stack rebase [branch] [flags]
gh stack rebase [flags] [branch]
```

Fetches the latest changes from `origin`, then ensures each branch in the stack has the tip of the previous layer in its commit history. Rebases branches in order from trunk upward. If a branch's PR has been squash-merged, the rebase automatically switches to `--onto` mode to correctly replay commits on top of the merge target.
Expand Down Expand Up @@ -331,7 +331,7 @@ gh stack view --json
Remove a stack from local tracking and delete it on GitHub. Also available as `gh stack delete`.

```
gh stack unstack [branch] [flags]
gh stack unstack [flags] [branch]
```

If no branch is specified, uses the current branch to find the stack. Deletes the stack on GitHub first, then removes local tracking. Use `--local` to only remove the local tracking entry.
Expand Down Expand Up @@ -414,7 +414,7 @@ gh stack feedback "Support for reordering branches"
Create a short command alias so you can type less.

```
gh stack alias [name] [flags]
gh stack alias [flags] [name]
```

Installs a small wrapper script into `~/.local/bin/` that forwards all arguments to `gh stack`. The default alias name is `gs`, but you can choose any name by passing it as an argument. After setup, you can run `gs push` instead of `gh stack push`.
Expand Down
11 changes: 7 additions & 4 deletions cmd/feedback.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import (
"github.com/spf13/cobra"
)

const feedbackBaseURL = "https://github.com/github/gh-stack/discussions/new?category=feedback"
const (
feedbackURL = "https://gh.io/stacks-feedback"
feedbackFormURL = "https://gh.io/stacks-feedback-form"
)

func FeedbackCmd(cfg *config.Config) *cobra.Command {
cmd := &cobra.Command{
Expand All @@ -25,15 +28,15 @@ func FeedbackCmd(cfg *config.Config) *cobra.Command {
}

func runFeedback(cfg *config.Config, args []string) error {
feedbackURL := feedbackBaseURL
targetURL := feedbackURL

if len(args) > 0 {
title := strings.Join(args, " ")
feedbackURL += "&title=" + url.QueryEscape(title)
targetURL = feedbackFormURL + "?title=" + url.QueryEscape(title)
}

b := browser.New("", cfg.Out, cfg.Err)
if err := b.Browse(feedbackURL); err != nil {
if err := b.Browse(targetURL); err != nil {
return err
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func generatePRBody(commitBody string) string {

footer := fmt.Sprintf(
"<sub>Stack created with <a href=\"https://github.com/github/gh-stack\">GitHub Stacks CLI</a> • <a href=\"%s\">Give Feedback 💬</a></sub>",
feedbackBaseURL,
feedbackURL,
)
parts = append(parts, footer)

Expand Down
2 changes: 1 addition & 1 deletion cmd/submit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestGeneratePRBody(t *testing.T) {
commitBody: "",
wantContains: []string{
"GitHub Stacks CLI",
feedbackBaseURL,
feedbackURL,
"<sub>",
},
},
Expand Down
3 changes: 3 additions & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export default defineConfig({
head: [
{ tag: 'meta', attrs: { name: 'robots', content: 'noindex, nofollow' } },
],
components: {
SocialIcons: './src/components/CustomHeader.astro',
},
customCss: [
'./src/styles/custom.css',
],
Expand Down
Binary file not shown.
Binary file modified docs/src/assets/screenshots/stack-merge-box.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
172 changes: 172 additions & 0 deletions docs/src/components/CustomHeader.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
---
import Default from '@astrojs/starlight/components/SocialIcons.astro';
const base = import.meta.env.BASE_URL;
---

<nav class="custom-header-links" aria-label="Primary navigation">
<a href={`${base}introduction/overview/`} class="header-link">Overview</a>
<a href={`${base}getting-started/quick-start/`} class="header-link">Quick Start</a>
<a href={`${base}reference/cli/`} class="header-link">CLI</a>
<a href={`${base}guides/ui/`} class="header-link">UI</a>
<a href={`${base}faq/`} class="header-link">FAQ</a>
</nav>

<!-- Hamburger menu for narrow viewports where full nav would overflow -->
<div class="tablet-nav-wrapper">
<button class="hamburger-btn" aria-label="Toggle navigation menu" aria-expanded="false" aria-controls="tablet-nav-dropdown">
<span class="hamburger-icon" aria-hidden="true"></span>
</button>
<nav class="tablet-dropdown" id="tablet-nav-dropdown" aria-label="Primary navigation" hidden>
<a href={`${base}introduction/overview/`} class="dropdown-link">Overview</a>
<a href={`${base}getting-started/quick-start/`} class="dropdown-link">Quick Start</a>
<a href={`${base}reference/cli/`} class="dropdown-link">CLI</a>
<a href={`${base}guides/ui/`} class="dropdown-link">UI</a>
<a href={`${base}faq/`} class="dropdown-link">FAQ</a>
</nav>
</div>

<Default {...Astro.props} />

<style>
.custom-header-links {
display: flex;
align-items: center;
gap: 1rem;
margin-right: 1rem;
}

.header-link {
color: var(--sl-color-text);
text-decoration: none;
font-size: 0.875rem;
font-weight: 500;
padding: 0.25rem 0.75rem;
border-radius: 4px;
transition: color 0.15s ease, background-color 0.15s ease;
white-space: nowrap;
}

.header-link:hover {
color: var(--sl-color-text-accent);
background-color: rgba(110, 118, 129, 0.1);
}

/* Tablet navigation — hidden by default; shown via global CSS at narrow widths */
.tablet-nav-wrapper {
display: none;
position: relative;
margin-right: 0.5rem;
}

.hamburger-btn {
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
color: var(--sl-color-text);
border-radius: 4px;
min-height: 44px;
min-width: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}

.hamburger-btn:hover {
background-color: rgba(110, 118, 129, 0.1);
}

.hamburger-icon {
display: block;
width: 20px;
height: 2px;
background: currentColor;
position: relative;
}

.hamburger-icon::before,
.hamburger-icon::after {
content: '';
display: block;
width: 20px;
height: 2px;
background: currentColor;
position: absolute;
left: 0;
}

.hamburger-icon::before { top: -6px; }
.hamburger-icon::after { top: 6px; }

.tablet-dropdown {
position: absolute;
top: calc(100% + 0.5rem);
right: 0;
background: var(--sl-color-bg-nav, #161b22);
border: 1px solid rgba(48, 54, 61, 0.8);
border-radius: 8px;
padding: 0.5rem 0;
min-width: 180px;
z-index: 100;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.dropdown-link {
display: block;
padding: 0.625rem 1rem;
color: var(--sl-color-text);
text-decoration: none;
font-size: 0.875rem;
font-weight: 500;
transition: background-color 0.15s ease;
}

.dropdown-link:hover {
background-color: rgba(110, 118, 129, 0.1);
color: var(--sl-color-text-accent);
}
</style>

<script>
let hamburgerAbort: AbortController | undefined;

function initHamburgerMenu() {
hamburgerAbort?.abort();
hamburgerAbort = new AbortController();
const { signal } = hamburgerAbort;

const hamburgerBtn = document.querySelector<HTMLButtonElement>('.hamburger-btn');
const tabletDropdown = document.querySelector<HTMLElement>('.tablet-dropdown');

if (!hamburgerBtn || !tabletDropdown) return;

hamburgerBtn.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = hamburgerBtn.getAttribute('aria-expanded') === 'true';
hamburgerBtn.setAttribute('aria-expanded', String(!isOpen));
tabletDropdown.hidden = isOpen;
if (!isOpen) {
const firstLink = tabletDropdown.querySelector<HTMLAnchorElement>('.dropdown-link');
firstLink?.focus();
}
}, { signal });

document.addEventListener('click', (e) => {
if (!hamburgerBtn.contains(e.target as Node) && !tabletDropdown.contains(e.target as Node)) {
hamburgerBtn.setAttribute('aria-expanded', 'false');
tabletDropdown.hidden = true;
}
}, { signal });

document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && hamburgerBtn.getAttribute('aria-expanded') === 'true') {
hamburgerBtn.setAttribute('aria-expanded', 'false');
tabletDropdown.hidden = true;
hamburgerBtn.focus();
}
}, { signal });
}

initHamburgerMenu();
document.addEventListener('astro:page-load', initHamburgerMenu);
</script>
12 changes: 10 additions & 2 deletions docs/src/content.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { defineCollection } from 'astro:content';
import { defineCollection, z } from 'astro:content';
import { docsLoader } from '@astrojs/starlight/loaders';
import { docsSchema } from '@astrojs/starlight/schema';

export const collections = {
docs: defineCollection({
loader: docsLoader(),
schema: docsSchema(),
schema: docsSchema({
extend: z.object({
banner: z.object({
content: z.string(),
}).default({
content: 'Stacked PRs is currently in private preview. <a href="https://gh.io/stacksbeta">Sign up for the waitlist →</a>',
}),
}),
}),
}),
};
Loading
Loading