diff --git a/app/(site)/(directories)/platform-fees/[section]/page.tsx b/app/(site)/(directories)/platform-fees/[section]/page.tsx index 07c74855..b9c9b0c2 100644 --- a/app/(site)/(directories)/platform-fees/[section]/page.tsx +++ b/app/(site)/(directories)/platform-fees/[section]/page.tsx @@ -1,20 +1,14 @@ import { notFound } from 'next/navigation'; import { getRequiredDirectoryPage } from '@/lib/directory-pages'; import { getFeeDirectory } from '@/lib/platform-fees/queries'; -import { sectionCheckedAt } from '@/lib/platform-fees/model'; +import { sectionCheckedAt, type FeeSection } from '@/lib/platform-fees/model'; import { feeGroups, getFeeGroup, groupPath } from '@/lib/platform-fees/groups'; import { sectionPath, sectionSlug } from '@/lib/platform-fees/section-routes'; -import { FeeCategoryBody, FeeFilters, FeeMatchCount } from '../filters'; -import { FeeShell, FeeSourceList, SectionIcon } from '../components'; -import { FeeSources } from '../sources'; -import { FeeCard } from '../fee-card'; -import styles from '../page.module.css'; +import { FeeCategoryBody, FeeFilters, type FeeCategorySection } from '../filters'; +import { FeeShell, SectionIcon } from '../components'; +import { feeCardView, feeDate, feeSearchText } from '../card-view'; import { feeCategoryContent } from './content'; -const dateFormat = new Intl.DateTimeFormat('en-GB', { - day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC', -}); - // Build every category during the release. Unpublished categories still return 404. export function generateStaticParams() { return feeGroups.map(group => ({ section: group.slug })); @@ -32,23 +26,17 @@ export default async function FeeCategoryPage({ params }: { params: Promise<{ se const sections = directory.sections.filter(section => section.group_name === group.name); if (!sections.length) notFound(); const path = groupPath(group.name); - const blocks = sections.map(section => { + const blocks: Array = sections.map(section => { const sectionEntries = directory.entries.filter(entry => entry.section_id === section.id); - const checkedAt = sectionCheckedAt(sectionEntries); - const items = sectionEntries.map(entry => ({ - id: entry.id, - text: `${entry.name} ${entry.card.tag} ${entry.summary}`.toLocaleLowerCase('en-US'), - card: , - })); return { anchor: sectionSlug(section.id), href: sectionPath(section), name: section.name, intro: section.intro, icon: section.icon, - checkedLabel: checkedAt ?

Last checked

: null, - sources: , - items, + checked: feeDate(sectionCheckedAt(sectionEntries)), + fields: section.fields.map(field => field.label), + cards: sectionEntries.map(feeCardView), }; }); @@ -63,14 +51,10 @@ export default async function FeeCategoryPage({ params }: { params: Promise<{ se href: block.href, label: block.name, icon: , - text: block.items.map(item => item.text).join('\n'), + text: block.cards.map(feeSearchText).join('\n'), }))} > -
-

block.items)} label="platforms" />

-

US / USD unless labelled

-
; } diff --git a/app/(site)/(directories)/platform-fees/card-view.ts b/app/(site)/(directories)/platform-fees/card-view.ts new file mode 100644 index 00000000..3de2ba2e --- /dev/null +++ b/app/(site)/(directories)/platform-fees/card-view.ts @@ -0,0 +1,70 @@ +import { hiddenRateCount, type FeeEntry, type FeeSource, type ReceiptRate } from '@/lib/platform-fees/model'; + +// What a fee card and its "Sources and dates" entry render, and nothing else. +// The category page sends this compact data to the browser once. Sending the +// rendered card tree instead made each category page megabytes larger. +export type FeeDate = { iso: string; label: string }; +export type FeeRateView = Pick; +export type FeeCardView = { + id: string; + name: string; + /** Searched with the name and tag; not shown on the card. */ + summary: string; + url: string; + icon: string | null; + tag: string; + note?: string; + take: FeeRateView; + second?: FeeRateView; + lines: { label: string; value: string }[]; + groups: { label: string; rates: FeeRateView[] }[]; + /** Label of the link that opens all rates and sources. */ + more: string; + checked: FeeDate | null; + sources: FeeSource[]; + /** In the order of the section's field labels. `source` indexes `sources`. */ + facts: { value: string; source?: number }[]; +}; + +// Formatted on the server so the browser's date data cannot change the text during hydration. +const dateFormat = new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' }); +export function feeDate(iso: string | null): FeeDate | null { + return iso ? { iso, label: dateFormat.format(new Date(iso)) } : null; +} + +function rate({ value, extra, percent, label, note }: ReceiptRate): FeeRateView { + return { value, percent, label, ...(extra ? { extra } : {}), ...(note ? { note } : {}) }; +} + +export function feeCardView(entry: FeeEntry): FeeCardView { + const receipt = entry.card; + const hidden = hiddenRateCount(receipt); + const sources = entry.sources.length + ? entry.sources.filter((source, index, all) => all.findIndex(item => item.url === source.url) === index).map(({ url, label }) => ({ url, label })) + : [{ url: entry.website_url, label: 'Official website' }]; + return { + id: entry.id, + name: entry.name, + summary: entry.summary, + url: entry.website_url, + icon: entry.icon_path, + tag: receipt.tag, + ...(receipt.note ? { note: receipt.note } : {}), + take: rate(receipt.take), + ...(receipt.second ? { second: rate(receipt.second) } : {}), + lines: receipt.lines.map(({ label, value }) => ({ label, value })), + groups: receipt.groups.map(group => ({ label: group.label, rates: group.rates.map(rate) })), + more: hidden > 0 ? `+ ${hidden} more rate${hidden > 1 ? 's' : ''}` : receipt.groups.length ? 'All rates and sources' : 'Sources', + checked: feeDate(entry.checked_at), + sources, + facts: entry.facts.map(fact => { + // Validation guarantees every fact source is one of the entry's sources. + const source = fact.source_url ? sources.findIndex(item => item.url === fact.source_url) : -1; + return source >= 0 ? { value: fact.value, source } : { value: fact.value }; + }), + }; +} + +export function feeSearchText(card: Pick): string { + return `${card.name} ${card.tag} ${card.summary}`.toLocaleLowerCase('en-US'); +} diff --git a/app/(site)/(directories)/platform-fees/components.tsx b/app/(site)/(directories)/platform-fees/components.tsx index 657f8ab2..a1f097dc 100644 --- a/app/(site)/(directories)/platform-fees/components.tsx +++ b/app/(site)/(directories)/platform-fees/components.tsx @@ -5,7 +5,7 @@ import { PageMeta } from "@/app/ui/site/page-meta"; import Link from 'next/link'; import type { ReactNode } from 'react'; import { - CreditCard, ShoppingCart, Files, Cpu, ExternalLink, Smartphone, + CreditCard, ShoppingCart, Files, Cpu, Smartphone, Monitor, Watch, Gamepad2, Headset, Puzzle, Cloud, Database, Download, Palette, Type, LayoutTemplate, Code, Box, Camera, Video, Music, BookOpen, GraduationCap, Calendar, Users, Heart, Mail, Mic, Coffee, Briefcase, Shirt, @@ -21,7 +21,7 @@ import { SidebarList } from '@/app/ui/site/sidebar-list'; import { sidebarPanelClassName, sidebarPanelHeadingClassName } from '@/app/ui/site/sidebar-panel'; import { feeGroups, groupPath, type FeeGroup } from '@/lib/platform-fees/groups'; import { getBreadcrumbs } from '@/lib/breadcrumbs'; -import type { FeeEntry, FeeSection } from '@/lib/platform-fees/model'; +import type { FeeSection } from '@/lib/platform-fees/model'; import { FeeSearch, FeeSidebarAnchors, type FeeAnchor } from './filters'; import styles from './page.module.css'; @@ -31,9 +31,6 @@ const icons = { Camera, Video, Music, BookOpen, GraduationCap, Calendar, Files, Users, Heart, Mail, Mic, Coffee, Briefcase, Cpu, Shirt, }; -const dateFormat = new Intl.DateTimeFormat('en-GB', { - day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC', -}); export function SectionIcon({ icon, size = 22 }: { icon: FeeSection['icon']; size?: number }) { const Icon = icons[icon]; @@ -92,23 +89,3 @@ export function CategoryTile({ group, sections, listings }: { group: FeeGroup; s ; } - -export function FeeSourceList({ entries, section }: { entries: FeeEntry[]; section: FeeSection }) { - return
{entries.map(entry => { - const sources = entry.sources.length - ? entry.sources.filter((source, index, all) => all.findIndex(item => item.url === source.url) === index) - : [{ url: entry.website_url, label: 'Official website' }]; - return
-

{entry.name}

-

{entry.checked_at ? <>Checked : 'Not checked'}

-
{entry.facts.map(fact =>
-
{section.fields.find(field => field.key === fact.key)?.label}
-
{fact.value}{fact.source_url && <> Source}
-
)}
-
    {sources.map(source =>
  • - {source.label} - {new URL(source.url).hostname.replace(/^www\./, '')} -
  • )}
-
; - })}
; -} diff --git a/app/(site)/(directories)/platform-fees/fee-card.module.css b/app/(site)/(directories)/platform-fees/fee-card.module.css index 25caa7fc..54dd4815 100644 --- a/app/(site)/(directories)/platform-fees/fee-card.module.css +++ b/app/(site)/(directories)/platform-fees/fee-card.module.css @@ -17,30 +17,30 @@ .card > .top { padding: 0 var(--space-9) var(--space-9); } .identity { display: flex; gap: var(--space-5); align-items: center; min-width: 0; } -.identityText { display: grid; gap: 2px; min-width: 0; } +.identity > div { display: grid; gap: 2px; min-width: 0; } .identity h3 { margin: 0; font-family: var(--font-display); font-size: var(--text-h3); line-height: var(--line-heading); letter-spacing: var(--tracking-display); } .identity h3 a { display: inline-flex; gap: var(--space-3); align-items: center; color: var(--color-text); } -.identity h3 a svg { flex: none; color: var(--color-text-muted); } +.identity h3 a[target="_blank"]::after { width: 14px; height: 14px; background-color: var(--color-text-muted); } .identity p { margin: 0; color: var(--color-text-muted); font-size: var(--text-small); line-height: var(--line-ui); } -.logo { flex: none; display: grid; place-items: center; width: 40px; height: 40px; overflow: hidden; background: var(--color-surface-2); border-radius: var(--radius); color: var(--color-text-muted); font-family: var(--font-display); font-weight: var(--weight-bold); } -.logo img { width: 30px; height: 30px; object-fit: contain; } +.identity > span { flex: none; display: grid; place-items: center; width: 40px; height: 40px; overflow: hidden; background: var(--color-surface-2); border-radius: var(--radius); color: var(--color-text-muted); font-family: var(--font-display); font-weight: var(--weight-bold); } +.identity > span img { width: 30px; height: 30px; object-fit: contain; } .top { display: grid; align-content: start; gap: var(--space-8); } .rates { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-5) var(--space-10); } .rates > div { display: grid; gap: var(--space-2); min-width: 0; max-width: 100%; } .rates strong { font-family: var(--font-display); font-weight: var(--weight-bold); line-height: 1; letter-spacing: -0.03em; } -.take strong { font-size: 2.6rem; } -.take strong.reason { font-size: var(--text-h2); line-height: 1.1; overflow-wrap: anywhere; } -.second strong { font-size: 1.45rem; line-height: 1.15; overflow-wrap: anywhere; color: var(--color-text-muted); } +.rates > :first-child strong { font-size: 2.6rem; } +.rates > :first-child strong.reason { font-size: var(--text-h2); line-height: 1.1; overflow-wrap: anywhere; } +.rates > :nth-child(2) strong { font-size: 1.45rem; line-height: 1.15; overflow-wrap: anywhere; color: var(--color-text-muted); } .rates strong small { font-size: 0.45em; letter-spacing: 0; color: var(--color-text-muted); } .rates span { color: var(--color-text-muted); font-size: var(--text-small); line-height: var(--line-ui); } .split { display: grid; gap: var(--space-2); } -.splitBar { display: flex; height: 8px; overflow: hidden; background: var(--color-surface-2); border-radius: var(--radius); } -.keep { background: var(--color-data-chartreuse); } -.cut { background: var(--color-border-strong); } -.splitLegend { margin: 0; color: var(--color-text-muted); font-family: var(--font-mono); font-size: var(--text-small); } -.splitLegend b { color: var(--color-text); font-weight: var(--weight-regular); } +.split > div { display: flex; height: 8px; overflow: hidden; background: var(--color-surface-2); border-radius: var(--radius); } +.split > div > :first-child { background: var(--color-data-chartreuse); } +.split > div > :last-child { background: var(--color-border-strong); } +.split > p { margin: 0; color: var(--color-text-muted); font-family: var(--font-mono); font-size: var(--text-small); } +.split > p b { color: var(--color-text); font-weight: var(--weight-regular); } /* Torn receipt edge: dashed line with a half circle cut into each side. */ .tear { position: relative; height: 0; margin: 0 var(--space-9); border-top: 2px dashed var(--color-border-strong); } @@ -65,22 +65,36 @@ .rateTable { width: 100%; border-collapse: collapse; font-size: var(--text-small); } .rateTable caption { margin-bottom: var(--space-2); text-align: left; font-family: var(--font-display); font-size: var(--text-h3); font-weight: var(--weight-bold); } .rateTable thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } -.rateGroup th { padding: var(--space-6) 0 var(--space-2); color: var(--color-text-muted); font-weight: var(--weight-semibold); text-align: left; } +.rateTable th[scope=rowgroup] { padding: var(--space-6) 0 var(--space-2); color: var(--color-text-muted); font-weight: var(--weight-semibold); text-align: left; } .rateTable td, .rateTable th[scope=row] { padding: var(--space-3) 0; border-bottom: 1px dotted var(--color-border-strong); vertical-align: top; } .rateTable th[scope=row] { font-weight: var(--weight-regular); line-height: 1.35; text-align: left; } -.rateValue { width: 5rem; padding-right: var(--space-4) !important; font-family: var(--font-display); font-size: var(--text-body); font-weight: var(--weight-bold); line-height: 1.25; white-space: nowrap; } -.reasonValue { white-space: normal; overflow-wrap: anywhere; } -.rateExtra { display: block; color: var(--color-text-muted); font-family: var(--font-sans); font-size: var(--text-small); font-weight: var(--weight-regular); } -.rateNote { display: block; margin-top: 2px; color: var(--color-text-muted); font-family: var(--font-mono); font-size: var(--text-small); } -.note { margin: 0; font-size: var(--text-small); } +.rateTable td { width: 5rem; padding-right: var(--space-4); font-family: var(--font-display); font-size: var(--text-body); font-weight: var(--weight-bold); line-height: 1.25; white-space: nowrap; } +.rateTable td.reasonValue { white-space: normal; overflow-wrap: anywhere; } +.rateTable td span { display: block; color: var(--color-text-muted); font-family: var(--font-sans); font-size: var(--text-small); font-weight: var(--weight-regular); } +.rateTable th span { display: block; margin-top: 2px; color: var(--color-text-muted); font-family: var(--font-mono); font-size: var(--text-small); } +.allRates > p { margin: 0; font-size: var(--text-small); } .sources { display: grid; gap: var(--space-2); padding-top: var(--space-6); border-top: 1px dashed var(--color-border); color: var(--color-text-muted); font-size: var(--text-small); } .sources p { margin: 0; font-family: var(--font-mono); } -.sources .sourcesLabel { margin: var(--space-3) 0 0; font-family: var(--font-sans); font-size: var(--text-small); font-weight: var(--weight-semibold); } +.sources > p + p { margin: var(--space-3) 0 0; font-family: var(--font-sans); font-size: var(--text-small); font-weight: var(--weight-semibold); } .sources ul { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-6); margin: 0; padding: 0; list-style: none; } .sources a { display: inline-flex; gap: var(--space-1); align-items: center; } +/* One CSS icon marks every external link. An inline SVG per link made the fee pages megabytes larger. */ +.card a[target="_blank"]::after, +.panel a[target="_blank"]::after, +.links a[target="_blank"]::after { + content: ""; + flex: none; + width: 12px; + height: 12px; + background-color: currentColor; + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 3h6v6'/%3E%3Cpath d='M10 14 21 3'/%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3C/svg%3E") center / contain no-repeat; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 3h6v6'/%3E%3Cpath d='M10 14 21 3'/%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3C/svg%3E") center / contain no-repeat; +} + /* Side panel content. The panel itself comes from the shared DetailDialog. */ .panel { display: grid; gap: var(--space-9); } .panelHeading { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--space-5); } .panelHeading .identity h3 { font-size: var(--text-h2); } .panel .lines { padding-top: var(--space-7); border-top: 2px dashed var(--color-border-strong); } +.contents { display: contents; } diff --git a/app/(site)/(directories)/platform-fees/fee-card.tsx b/app/(site)/(directories)/platform-fees/fee-card.tsx index dc2c3c0e..71be351c 100644 --- a/app/(site)/(directories)/platform-fees/fee-card.tsx +++ b/app/(site)/(directories)/platform-fees/fee-card.tsx @@ -1,92 +1,76 @@ -import { ExternalLink } from 'lucide-react'; -import { hiddenRateCount, type FeeEntry, type ReceiptCard } from '@/lib/platform-fees/model'; +import type { FeeCardView, FeeRateView } from './card-view'; import { FeeRatesPanel } from './fee-rates-panel'; import styles from './fee-card.module.css'; -const dateFormat = new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' }); +const reason = (rate: FeeRateView) => rate.percent === null && rate.value.length > 12; -function Identity({ entry, tag }: { entry: FeeEntry; tag: string }) { +function Identity({ card }: { card: FeeCardView }) { return
- - {entry.icon_path ? : } + + {card.icon ? : } -
-

{entry.name}

-

{tag}

+
+

{card.name}

+

{card.tag}

; } // Clear percentage fees show the seller's share before other deductions. // Fixed pay, shared pools and base costs keep their stated unit and have no bar. -function Rates({ receipt }: { receipt: ReceiptCard }) { - const { take, second } = receipt; +function Rates({ card }: { card: FeeCardView }) { + const { take, second } = card; return
-
12 ? styles.reason : undefined}>{take.value}{take.extra && {take.extra}}{take.label}
- {second &&
{second.value}{second.extra && {second.extra}}{second.label}
} +
{take.value}{take.extra && {take.extra}}{take.label}
+ {second &&
{second.value}{second.extra && {second.extra}}{second.label}
}
{take.percent !== null &&
- }
; } -function Lines({ receipt }: { receipt: ReceiptCard }) { - return
- {receipt.lines.map(line =>
{line.label}
{line.value}
)} -
; -} - -// Always server-rendered, so search engines and AI bots can read every rate. -function AllRates({ entry, receipt }: { entry: FeeEntry; receipt: ReceiptCard }) { - const sources = entry.sources.length - ? entry.sources.filter((source, index, all) => all.findIndex(item => item.url === source.url) === index) - : [{ url: entry.website_url, label: 'Official website' }]; +// Always in the HTML, so search engines and AI bots can read every rate. +function AllRates({ card }: { card: FeeCardView }) { return
- {receipt.groups.length > 0 && - + {card.groups.length > 0 &&
All {entry.name} rates
+ - {receipt.groups.map(group => - - {group.rates.map(rate => - - + {card.groups.map((group, index) => + + {group.rates.map((rate, index) => + + )} )}
All {card.name} rates
RateWhen
{group.label}
12 ? ` ${styles.reasonValue}` : ''}`}>{rate.value}{rate.extra && {rate.extra}}{rate.label}{rate.note && {rate.note}}
{group.label}
{rate.value}{rate.extra && {rate.extra}}{rate.label}{rate.note && {rate.note}}
} - {receipt.note &&

{receipt.note}

} + {card.note &&

{card.note}

}
-

{entry.checked_at ? <>Checked : 'Not checked'}

-

Sources

-
    {sources.map(source =>
  • - {source.label} +

    {card.checked ? <>Checked : 'Not checked'}

    +

    Sources

    +
; } -export function FeeCard({ entry }: { entry: FeeEntry }) { - const receipt = entry.card; - const hidden = hiddenRateCount(receipt); - const summary = hidden > 0 ? `+ ${hidden} more rate${hidden > 1 ? 's' : ''}` : receipt.groups.length ? 'All rates and sources' : 'Sources'; - return
- - +export function FeeCard({ card }: { card: FeeCardView }) { + return
+ +
; diff --git a/app/(site)/(directories)/platform-fees/fee-rates-panel.tsx b/app/(site)/(directories)/platform-fees/fee-rates-panel.tsx index c6afc5b1..1eb8c3ad 100644 --- a/app/(site)/(directories)/platform-fees/fee-rates-panel.tsx +++ b/app/(site)/(directories)/platform-fees/fee-rates-panel.tsx @@ -1,28 +1,35 @@ "use client"; -import { useState, type ReactNode } from 'react'; +import { useRef, useState, type ReactNode } from 'react'; import { DetailDialog, DetailDialogClose } from '@/app/ui/detail-dialog'; import styles from './fee-card.module.css'; +// The card's name, rates, and lines are copied from its server-rendered markup, +// so the page data does not carry a second copy of every card for the panel. +function cardParts(from: Element | null) { + const card = from?.closest('article'); + const html = (className: string) => card?.querySelector(`:scope > .${className}, :scope > * > .${className}`)?.outerHTML ?? ''; + return { heading: html(styles.identity), top: html(styles.top) + html(styles.lines) }; +} + // The rates stay inside
in the HTML. With JavaScript, the summary opens the shared side panel instead. -export function FeeRatesPanel({ summary, label, heading, top, children }: { +export function FeeRatesPanel({ summary, label, children }: { summary: string; label: string; - heading: ReactNode; - top: ReactNode; children: ReactNode; }) { - const [open, setOpen] = useState(false); - const close = () => setOpen(false); + const ref = useRef(null); + const [parts, setParts] = useState<{ heading: string; top: string } | null>(null); + const close = () => setParts(null); return <> -
- { event.preventDefault(); setOpen(true); }}>{summary} +
+ { event.preventDefault(); setParts(cardParts(ref.current)); }}>{summary} {children}
- {open && + {parts &&
-
{heading}
- {top} +
+
{children}
} diff --git a/app/(site)/(directories)/platform-fees/filters.tsx b/app/(site)/(directories)/platform-fees/filters.tsx index 940d9fe4..75198fc5 100644 --- a/app/(site)/(directories)/platform-fees/filters.tsx +++ b/app/(site)/(directories)/platform-fees/filters.tsx @@ -1,9 +1,12 @@ "use client"; import Link from 'next/link'; -import { createContext, Fragment, useContext, useEffect, useState, type MouseEvent, type ReactNode } from 'react'; +import { createContext, useContext, useEffect, useMemo, useState, type MouseEvent, type ReactNode } from 'react'; import { UiButton, UiSearchField } from '@/app/ui/controls'; import { StatePanel } from '@/app/ui/state-panel'; import { SidebarList } from '@/app/ui/site/sidebar-list'; +import { feeSearchText, type FeeCardView, type FeeDate } from './card-view'; +import { FeeCard } from './fee-card'; +import { FeeSourceList, FeeSources } from './sources'; import styles from './page.module.css'; // The pages are static. Search runs in the browser from ?q=, which stays out of the index. @@ -90,29 +93,36 @@ export function FeeFilters({ action = '/platform-fees' }: { action?: string }) { ; } -export function FeeMatchCount({ items, label }: { items: FeeSearchItem[]; label: string }) { - return <>{matchItems(items, useFeeQuery()).length} {label}; -} - export type FeeCategorySection = { anchor: string; href: string; name: string; intro: string; - checkedLabel: ReactNode; - sources: ReactNode; - items: Array; + checked: FeeDate | null; + /** Fact labels, in the order of each card's facts. */ + fields: string[]; + cards: FeeCardView[]; }; +// Cards arrive as compact data and render here, so the page data holds one copy of each card. export function FeeCategoryBody({ path, sections }: { path: string; sections: FeeCategorySection[] }) { const query = useFeeQuery(); const followAnchor = useSamePageAnchor(); - const visible = sections.flatMap(section => { + const indexed = useMemo(() => sections.map(section => ({ + ...section, + items: section.cards.map(card => ({ id: card.id, text: feeSearchText(card), card })), + })), [sections]); + const visible = indexed.flatMap(section => { const items = matchItems(section.items, query); if (query && items.length === 0) return []; return [{ ...section, items }]; }); + const count = visible.reduce((sum, section) => sum + section.items.length, 0); return <> +
+

{count} platforms

+

US / USD unless labelled

+
{visible.length > 0 && ; } -export function FeeHubResults({ items, children }: { - items: Array; +export type FeeHubItem = { id: string; name: string; tag: string; summary: string; section: number }; + +// Section labels and links are sent once; each listing points to its section by index. +export function FeeHubResults({ sections, items, children }: { + sections: Array<{ label: string; path: string }>; + items: FeeHubItem[]; children: ReactNode; }) { const query = useFeeQuery(); + const indexed = useMemo(() => items.map(item => ({ ...item, text: feeSearchText(item) })), [items]); if (!query) return children; - const matches = matchItems(items, query); + const matches = matchItems(indexed, query); return <>

{matches.length} results

{!matches.length && All categories} />}
{matches.map(item => - + {item.name} - {item.section} + {sections[item.section]?.label ?? ''} , )}
diff --git a/app/(site)/(directories)/platform-fees/page.module.css b/app/(site)/(directories)/platform-fees/page.module.css index fd2b4ef0..c45b46cf 100644 --- a/app/(site)/(directories)/platform-fees/page.module.css +++ b/app/(site)/(directories)/platform-fees/page.module.css @@ -141,51 +141,32 @@ gap: var(--space-7); } -.sourceEntry { +.sourceList > section { padding-top: var(--space-7); border-top: 1px solid var(--color-border); overflow-wrap: anywhere; } -.sourceEntry h3 { +.sourceList > section h3 { margin: 0; font-family: var(--font-display); font-size: var(--text-h3); } -.sourceEntry p { +.sourceList > section p { margin: var(--space-2) 0 0; color: var(--color-text-muted); font-size: var(--text-small); } -.sourceEntry ul { - display: grid; - gap: var(--space-4); - padding: var(--space-4) 0 0; - margin: 0; - list-style: none; -} - -.sourceEntry li { - display: grid; - gap: var(--space-1); -} - -.sourceEntry li > span { - color: var(--color-text-muted); - font-size: var(--text-small); -} - -.sourceEntry a { +.sourceList > section a { display: inline-flex; align-items: flex-start; gap: var(--space-2); color: var(--color-text); } -.sourceEntry svg { - flex: none; +.sourceList > section a::after { margin-top: 4px; } @@ -330,23 +311,23 @@ margin: 0; } -.fullFacts { +.sourceList dl { display: grid; gap: var(--space-5); margin: var(--space-5) 0; } -.fullFacts dt { +.sourceList dl dt { font-weight: var(--weight-semibold); font-size: var(--text-small); } -.fullFacts dd { +.sourceList dl dd { margin: var(--space-1) 0 0; font-size: var(--text-small); color: var(--color-text-muted); } -.fullFacts dd a { +.sourceList dl dd a { white-space: nowrap; } diff --git a/app/(site)/(directories)/platform-fees/page.tsx b/app/(site)/(directories)/platform-fees/page.tsx index d3c9cbe4..ba6c1981 100644 --- a/app/(site)/(directories)/platform-fees/page.tsx +++ b/app/(site)/(directories)/platform-fees/page.tsx @@ -1,7 +1,7 @@ import { getRequiredDirectoryPage } from '@/lib/directory-pages'; import { getFeeDirectory } from '@/lib/platform-fees/queries'; import { feeGroups, groupPath } from '@/lib/platform-fees/groups'; -import { FeeFilters, FeeHubResults } from './filters'; +import { FeeFilters, FeeHubResults, type FeeHubItem } from './filters'; import { CategoryTile, FeeShell } from './components'; import styles from './page.module.css'; import { RegistryStructuredData } from '@/lib/registry-layout'; @@ -12,23 +12,21 @@ export default async function PlatformFeesPage() { const directoryPage = await getRequiredDirectoryPage('platform-fees'); // A failed load must surface as an error, never as a cacheable page. const directory = await getFeeDirectory(); - const sectionsById = new Map(directory.sections.map(section => [section.id, section])); - const items = directory.entries.map(entry => { - const section = sectionsById.get(entry.section_id); - return { - id: entry.id, - text: `${entry.name} ${entry.card.tag} ${entry.summary}`.toLocaleLowerCase('en-US'), - name: entry.name, - section: section ? `${section.group_name} · ${section.name}` : '', - href: section ? `${groupPath(section.group_name)}#${entry.id}` : '/platform-fees', - }; - }); + const hubSections = directory.sections.map(section => ({ label: `${section.group_name} · ${section.name}`, path: groupPath(section.group_name) })); + const sectionIndex = new Map(directory.sections.map((section, index) => [section.id, index])); + const items: FeeHubItem[] = directory.entries.map(entry => ({ + id: entry.id, + name: entry.name, + tag: entry.card.tag, + summary: entry.summary, + section: sectionIndex.get(entry.section_id) ?? -1, + })); return <> } article={
}> - +

{feeGroups.length} categories · {directory.sections.length} sections · {directory.entries.length} listings

{feeGroups.map(group => { const sections = directory.sections.filter(section => section.group_name === group.name); diff --git a/app/(site)/(directories)/platform-fees/sources.tsx b/app/(site)/(directories)/platform-fees/sources.tsx index 41f2438c..09aab383 100644 --- a/app/(site)/(directories)/platform-fees/sources.tsx +++ b/app/(site)/(directories)/platform-fees/sources.tsx @@ -2,7 +2,9 @@ import { useState, type ReactNode } from 'react'; import { DetailDialog, DetailDialogClose } from '@/app/ui/detail-dialog'; +import type { FeeCardView } from './card-view'; import styles from './page.module.css'; +import cardStyles from './fee-card.module.css'; export function FeeSources({ children }: { children: ReactNode }) { const [open, setOpen] = useState(false); @@ -21,3 +23,16 @@ export function FeeSources({ children }: { children: ReactNode }) { } ; } + +// Each card already lists its sources, so this list adds only what the cards do not show: +// every fact with its source link, and the date it was checked. +export function FeeSourceList({ cards, fields }: { cards: FeeCardView[]; fields: string[] }) { + return
{cards.map(card =>
+

{card.name}

+

{card.checked ? <>Checked : 'Not checked'}

+
{card.facts.map((fact, index) =>
+
{fields[index]}
+
{fact.value}{fact.source !== undefined && <> Source}
+
)}
+
)}
; +} diff --git a/app/(site)/(tools)/shortcuts-cheatsheet-generator/shortcuts-cheatsheet-generator.tsx b/app/(site)/(tools)/shortcuts-cheatsheet-generator/shortcuts-cheatsheet-generator.tsx index 0e52cad9..9302206b 100644 --- a/app/(site)/(tools)/shortcuts-cheatsheet-generator/shortcuts-cheatsheet-generator.tsx +++ b/app/(site)/(tools)/shortcuts-cheatsheet-generator/shortcuts-cheatsheet-generator.tsx @@ -5,12 +5,18 @@ import { useEffect, useState } from "react"; import { UiButton, UiCheckbox, UiChoiceGroup, UiSearchField, uiOption } from "@/app/ui/controls"; import { UiStatus } from "@/app/ui/display"; import { buildCheatSheet, type CheatSheetAppSelection, type CheatSheetAppSection } from "@/lib/shortcuts/cheatsheet"; +import { keycapParts } from "@/lib/shortcuts/presentation"; import { platformLabels, type AppShortcuts, type PlatformId } from "@/lib/shortcuts/types"; import type { ShortcutAppSummary } from "@/lib/shortcuts"; import styles from "./shortcuts-cheatsheet-generator.module.css"; type Props = { catalog: readonly ShortcutAppSummary[]; initialSlugs: readonly string[] }; +/** Print a binding the way the reference shows it: "Ctrl + K then Ctrl + C". */ +function keyText(keys: string[]) { + return keycapParts(keys).map(({ key, connector, separator }) => connector ? ` ${key} ` : `${separator}${key}`).join(""); +} + async function loadApp(slug: string): Promise { const response = await fetch(`/api/shortcuts/${encodeURIComponent(slug)}`); if (!response.ok) throw new Error("App shortcuts could not be loaded."); @@ -26,7 +32,7 @@ function SheetTable({ entry }: { entry: CheatSheetAppSection }) { {section.group.title} {section.shortcuts.map((shortcut) => {shortcut.action} - {entry.platforms.map((platform) => { const binding = shortcut.keys.find((key) => key.platform === platform); const bindings = binding ? [binding.keys, ...(binding.alternatives ?? [])] : []; return {bindings.length ? bindings.map((keys) => keys.join(" ")).join(" or ") : "Not documented"}; })} + {entry.platforms.map((platform) => { const binding = shortcut.keys.find((key) => key.platform === platform); const bindings = binding ? [binding.keys, ...(binding.alternatives ?? [])] : []; return {bindings.length ? bindings.map(keyText).join(" or ") : "Not documented"}; })} )} )} ; diff --git a/content_docs/directory-record-pages/plan.md b/content_docs/directory-record-pages/plan.md new file mode 100644 index 00000000..334dcdaf --- /dev/null +++ b/content_docs/directory-record-pages/plan.md @@ -0,0 +1,240 @@ +# Directory record pages: plan + +**On hold, November 1, 2026.** The owner decided not to add new pages for now: no category pages and no app pages. The current work fixes existing pages only. Keep this plan for reference. The one part going ahead is adding the missing apps (Forgejo, GitLab, OneDev, GlitchTip) to the self-host apps data. + +Planning date: November 1, 2026. This plan answers audit item T2 in [the search traffic audit](../../dev-space/2026-10-25-search-traffic-audit.md). It changes no code, data, or settings. Counts below come from read-only queries on the development database on November 1. Development copies production self-host records, so the counts should match production closely, but check them again before publishing. + +## The short version + +- Pilot the **self-host apps** directory first. +- Build **category pages** at `/self-host-apps/categories/`, such as "self-hosted password managers". Each one compares 5 to 12 reviewed apps in normal, server-rendered HTML. +- **Don't build one page per app yet.** The data can't support "Vaultwarden requirements" pages: only 24 of 4,975 published apps have a publisher RAM minimum, and 4,285 of the RAM estimates are low confidence. +- The pilot is **20 category pages**. 17 can start now. 3 need missing apps added first. +- Bing is the main judge, as the audit says. We decide at week 6 and week 10 whether to scale up or stop. + +## 1. Which directory to pilot first + +**Self-host apps.** It has the only real search signal of the large directories, the best data, and a search market that rewards category pages. + +| Factor | Self-host apps | CLI apps | Others | +| --- | --- | --- | --- | +| Search signal | 601 Google impressions at average position 62, July 26 to October 23. That's the second-highest main-site page. | No row in the audit's top pages. | Ad networks already have 40+ child pages. Launch directory children are noindex on purpose (audit T5). TLDs are code records and need different work. | +| Records | 4,975 published apps, 42 categories | 309 published apps | | +| Data per record | License for 4,121 apps, Docker image for 3,197, Compose link for 1,673, a release in the last 12 months for 1,580, every row has a check date | Smaller set with fewer facts per row | | +| How competitors win | Category pages. awesome-selfhosted.net has about 100 `tags/.html` pages. openalternative.co and selfhostyourself.com rank with category and tag pages. selfh.st/apps is one filtered app with no per-app or per-category URLs. | | | + +Searches like "self-hosted password manager" or "self-hosted Google Photos alternative" return list pages, guides, and vendor pages. Nobody ranks a single page that holds 5,000 apps. A focused, checked comparison per category is the format that already wins these results. + +CLI apps can reuse the same template later, for searches like "terminal file managers". Wait until the self-host pilot has results. + +### Why not app pages first + +The audit's example of an entity page was "Vaultwarden: self-hosting requirements". The data can't keep that promise for most apps. + +| Fact | Apps with it | +| --- | ---: | +| Publisher RAM minimum (`ram_min_mb`) | 24 | +| Publisher recommended RAM | 15 | +| Publisher storage minimum | 10 | +| RAM estimate, high confidence | 48 | +| RAM estimate, medium confidence | 576 | +| RAM estimate, low confidence | 4,285 | + +An app page made mostly of rough estimates, a license, and a Docker link repeats the project's own README. It's thin next to the official docs that already rank for " requirements". + +There's also a hard limit. The static export holds 574 pages in 7,172 files today, so 12,828 files are left under Cloudflare Free's 20,000. At today's ratio of about 12 files per page, that's room for roughly 1,000 more pages. One page per app, nearly 5,000 pages, won't fit. + +## 2. Page types + +### Category page (the pilot) + +| Part | Rule | +| --- | --- | +| URL | `/self-host-apps/categories/`, such as `/self-host-apps/categories/password-managers`. This is the path `caching.md` and `seo.md` already name for indexable categories. It leaves `/self-host-apps/` free for app pages later, and the database triggers already expect that pattern. | +| Who it's for | Someone searching "self-hosted " or "self-hosted alternative". | +| Members | A reviewed list of apps, not the raw category link. The raw links are noisy. "Photos & galleries" includes watchtower, Plex, and TeamCity. "Password managers" includes YOURLS and Sinusbot. | + +What the page shows, top to bottom: + +1. Breadcrumbs: Self-host apps, then the category name. +2. The H1 and a short intro (MDX). +3. A comparison table, one row per app: name, license or source model, install methods (Docker, Compose, package, installer), whether it needs a separate database, estimated RAM with its confidence label, latest release and date, rounded GitHub stars, and the date we checked it. +4. One short section per app, visible, with an anchor such as `#vaultwarden`: its summary, install links, the resource estimate with its basis and source link, and source and docs links. These are the same facts the hidden dialog shows today, now in plain HTML. +5. "How to choose" (MDX): which app fits which situation, in a short table. This is what makes each page more than a list. +6. A note on method and estimates. It's one shared MDX file that links to the `/self-host-apps` sections that explain estimates. +7. Links to related categories and tools, such as `/self-hosted-app-resource-planner`, `/restic-retention-simulator`, and `/software-licenses-for-self-hosting`. + +The page has no filters, search, or query state. It's one static view. + +### Which apps can be on a page + +An app is listed only when all of these hold: + +- It's published, self-hostable, and a real application. Helper containers, image variants, runners, and duplicate rows don't count. +- It has a verified install path: a Docker image, a Compose link, or an official package or install guide. +- Its license or source model is known. +- It had a release or commit in the last 24 months. +- It has a RAM estimate. The page shows the confidence label next to it. +- Its record was checked in the last 12 months. + +### When a category page can be published + +- At least 5 apps meet the rules above. +- **It's complete.** Every app that shows up in at least 2 of the top 5 search results for the page's main phrase is listed, or the brief says why it's left out. A "self-hosted Git server" page without Forgejo and GitLab would lose the reader's trust on the first look. +- It has its own intro and "How to choose" section. +- Its HTML is under 300 KB. Today each hidden detail panel adds about 5 KB of HTML: 24 panels take 128 KB of the 883 KB `/self-host-apps` page, plus a second copy in the page's script data. A 12-app page fits easily. + +### Records that don't qualify + +- They stay in the main `/self-host-apps` listing and its dialog, the same as today. They get no page of their own and stay out of the sitemap. +- A category that fails the publish rules gets no published registry row. When a live page falls below 5 qualifying apps, set it to `is_indexable = false` until it's fixed. Don't delete it. + +### App pages (later, not in this pilot) + +If the category pilot works, test about 10 app pages at `/self-host-apps/`. Only apps with publisher-documented requirements, verified install options, and at least one category page linking to them qualify. Decide this after the week-10 review. + +## 3. Pilot size and candidates + +**20 category pages.** That's enough to see a pattern in Bing and Google, and small enough to write well and check by hand. It adds about 250 files to the export, well inside the 12,828 left. + +The tables below show what development holds for each candidate today. "Strong" means the app has a license, a Docker or Compose link, and a release in the last 12 months. These are starting lists. Luna confirms them against real search results and the source pages. + +### Ready now (17) + +| # | Slug | Main search phrase | Strong apps in the data | +| ---: | --- | --- | --- | +| 1 | `password-managers` | self-hosted password manager | Vaultwarden, Bitwarden, Passbolt, AliasVault, Teampass | +| 2 | `photo-apps` | self-hosted Google Photos alternative | Immich, PhotoPrism, Ente, LibrePhotos, Lychee, Piwigo | +| 3 | `file-sync` | self-hosted Dropbox / Google Drive alternative | Syncthing, Nextcloud, copyparty, filebrowser, Cloudreve, OpenCloud, Seafile | +| 4 | `note-taking` | self-hosted Notion alternative, self-hosted notes app | Memos, Joplin Server, Outline, TriliumNext, Docs, HedgeDoc, plus AFFiNE (stored as `affine-community-edition`) | +| 5 | `web-analytics` | self-hosted Google Analytics alternative | Umami, Plausible, Matomo, PostHog, Rybbit, GoatCounter, Ackee | +| 6 | `uptime-monitoring` | self-hosted uptime monitor, status page | Uptime Kuma, Gatus, Checkmate, Healthchecks, OneUptime, Kener | +| 7 | `workflow-automation` | self-hosted Zapier alternative | n8n, Activepieces, Windmill, Kestra, Node-RED, Trigger.dev | +| 8 | `deployment-platforms` | self-hosted Heroku / Vercel alternative | Coolify, Dokploy, Dokku, CapRover, Nixopus | +| 9 | `newsletters` | self-hosted newsletter, Mailchimp alternative | Listmonk, Ghost, Mautic, Keila, Dittofeed | +| 10 | `bookmark-managers` | self-hosted bookmark manager, Pocket alternative | Karakeep, Linkwarden, linkding, Wallabag, Shiori, ArchiveBox | +| 11 | `project-management` | self-hosted Trello / Jira alternative | Plane, Huly, Wekan, OpenProject, Planka, Leantime, Kanboard, Vikunja | +| 12 | `backup` | self-hosted backup software | restic, Duplicati, Kopia, Backrest, Zerobyte | +| 13 | `single-sign-on` | self-hosted SSO, Auth0 alternative | Keycloak, Authentik, Authelia, Zitadel, Logto, Casdoor, Pocket ID, LLDAP, Kanidm | +| 14 | `ai-chat` | self-hosted ChatGPT alternative | Open WebUI, LibreChat, AnythingLLM, LocalAI, Ollama | +| 15 | `invoicing` | self-hosted invoicing software | Invoice Ninja, Akaunting, Kimai, InvoiceShelf, SolidInvoice | +| 16 | `feature-flags` | self-hosted feature flags, LaunchDarkly alternative | Unleash, GrowthBook, Flagsmith, Flipt, FeatBit | +| 17 | `url-shorteners` | self-hosted URL shortener | Shlink, Kutt, YOURLS, Dub, Sink, Chhoto URL | + +### After missing apps are added (3) + +| # | Slug | Main search phrase | What's missing | +| ---: | --- | --- | --- | +| 18 | `git-hosting` | self-hosted GitHub alternative, Git server | Forgejo, GitLab, and OneDev exist only as unpublished rows. Their runners are published. Only Gitea and Gogs are live. | +| 19 | `error-tracking` | self-hosted Sentry alternative | GlitchTip is unpublished. Sentry, SigNoz, Highlight, and Bugsink are live. | +| 20 | `team-chat` | self-hosted Slack alternative | Mattermost and Element are fine. Rocket.Chat and Zulip show no Docker or Compose link, and Synapse has no release data. These look like data gaps, not facts. | + +### Reserves + +Use these if a pilot page fails its gate: + +- RSS readers: FreshRSS, Miniflux, CommaFeed, Yarr. Several install facts look wrong. +- Homelab dashboards: Homepage, Homarr, Dashy, Glance, Homer. The data is ready, but it's a homelab topic. +- Wikis: BookStack and MediaWiki are unpublished. +- Media servers: Jellyfin, Plex, Emby. Demand is high, but these are consumer apps. + +### Data problems this check found + +These need fixing before or during the pilot: + +- Big, well-known apps are unpublished: Forgejo, GitLab, OneDev, GlitchTip, BookStack, MediaWiki, Psono, and Readeck. +- Category links mix in helper containers and unrelated apps. The audit's cleanup item Q2 covers this. +- Some install facts look wrong. Examples: Dokploy, Dokku, and CapRover show no Docker or Compose link, though they use installer scripts. Rocket.Chat and Zulip show none either. +- Analytics apps such as Umami and Plausible sit under "dashboards". The reviewed lists fix this for the pilot pages. Fixing the category itself is Q2 work. + +## 4. Technical shape + +| Area | Plan | +| --- | --- | +| Route | `app/(site)/(directories)/self-host-apps/categories/[slug]/page.tsx` with `layout.tsx` using `registryLayout("/self-host-apps/categories/[slug]")`, `revalidate = false`, and `generateStaticParams` returning every published category slug. The export already swaps in exact registry slugs with `dynamicParams = false`. | +| Parent layout | Change `/self-host-apps/layout.tsx` to `registryLayout("/self-host-apps", { structuredData: "page" })`, so the parent's JSON-LD stays out of the child HTML. This follows the rule in `seo.md`. Ad networks already work this way. | +| Membership | A new table, such as `self_host_app_collection_members` (page id, app id, display order). It changes through a reviewed sync command with preview and apply, like the platform fee records. The list itself lives in a reviewed JSON file in the repo. Page words never go in the database. | +| Data loading | One `unstable_cache` reader with `revalidate: false` and a new tag, such as `stack127-self-host-collections`. It loads the members and their facts. A failed load throws. | +| Registry | One `stack127_pages` row per page: `page_family = 'directory'`, `page_type = 'CollectionPage'`, plus a `stack127_directory_pages` row with the title and card fields. Card icon and art keys stay null, so the page shows the parent icon. | +| Publication | Use the directories publisher (`stack127:directories:publish`). Each page's manifest lists its route, a records check (a SQL count showing at least 5 qualifying members), and a smoke check. In development, publish first with `is_indexable = false`. Set `is_indexable = true` in the production batch after review. | +| Sitemap | Published, indexable rows enter `/sitemap.xml` on their own. Nothing new is needed. | +| Canonicals | Each page is its own canonical, from the registry. `/self-host-apps?category=…` stays `noindex` with its canonical pointing to `/self-host-apps`. The filtered list shows the raw, noisy category, so it isn't the same page. | +| Breadcrumbs | Home, then self-host apps, then the category. Use the shared breadcrumb helper so the visible trail and the `BreadcrumbList` JSON-LD match. Check that it handles three levels. | +| Links from the listing | Add a server-rendered "Browse by category" block on `/self-host-apps`, made of plain `` links to every published category page, near the top of the results. In the sidebar, a category filter with a published page also gets a normal link to it. | +| Hidden panels | They stay for the listing's dialog in the pilot. For an app that's on a category page, the dialog adds a normal link, such as "Compare self-hosted password managers". Category pages never use hidden panels: every fact is visible. If app pages come later, that app's panel is replaced by a link and dropped from the listing HTML. | +| Page dates | Extend the self-host date trigger so a meaningful member change touches its category pages. That covers membership, name, summary, license, install options, estimates, release version, and maintenance status. Compare displayed values only. Show stars rounded, such as "66k", so daily star refreshes don't move dates. Today's trigger already touches `self-host-apps/` children. Category pages need their own rule. | +| Cache tags | Touches queue `page` events. Those already map to `page:self-host-apps:categories:` and the sitemap. In `proxy.ts`, add `/self-host-apps/categories/*` to the `self-host-catalog` family tag, so a catalog event purges them in OVH mode. In `targets.mjs`, a `self_host_catalog` event refreshes the category paths. | +| Cloudflare export | +20 pages and about +250 files, leaving about 12,500 of the 20,000. Each data edit already rebuilds the whole export, so build count doesn't change. Each build gets a little longer. The byte-for-byte public check covers the new pages. | +| Checks | `npm run check:static -- --strict` in CI. Load each page twice; the second load must not query page data. Check real pages at desktop and mobile width. Use `scripts/stack127/publishing/verify.mjs` for canonical, robots, and sitemap. | +| IndexNow | The audit found no IndexNow setup. If Sol adds one, send the 20 new URLs once at launch. Don't send them again. | + +Docs to update in the same change: `caching.md` (the new tag and entity mapping), `seo.md` (the category path pattern), `directory-pages.md` (the child page and date rules for self-host categories), `self-host-apps-data.md` (the membership table and sync command), and `page-families.md` (the "own path" example). + +## 5. Content + +Only 20 pages are planned, and each one needs its own judgment, so each page gets its own words. The shared route uses a content map, the same way platform fee categories do. + +| File | Holds | Written | +| --- | --- | --- | +| `categories/[slug]/content//intro.mdx` | 2 or 3 sentences: the reader's situation, how many options are compared, and what to look at first | Per page | +| `categories/[slug]/content//choose.mdx` | "How to choose": a short table pairing situations with apps, such as "One person, low RAM: Vaultwarden. A team that needs shared vaults and audit logs: Passbolt", plus 1 or 2 short paragraphs | Per page | +| `categories/[slug]/content//questions.mdx` | Optional. Only questions that searchers really ask, such as "Is Vaultwarden safe to use instead of Bitwarden?" | Per page, only when needed | +| `categories/[slug]/content/shared/method.mdx` | How we check apps, what an estimate means, what each confidence label means | Once | +| Table column labels and empty values | Plain labels, with "unknown" for a missing value | In the TSX template | + +- The title, SEO title, description, and card text go in the database through the publisher. Write them with the directory writing skill. +- Put the search words for each page in `content_docs/self-host-apps-categories/brief.md`, as `writing-style.md` asks. +- Most pages list fewer than 10 apps, so there's no count in the title. Use the names people search instead, such as "Self-hosted password managers compared: Vaultwarden, Bitwarden, Passbolt". Don't use "best" unless the "How to choose" table backs it up. +- Every claim in "How to choose" must match the records or a linked source. Mark rough estimates as rough. + +## 6. Measurement + +Bing comes first and Google second, following the audit. Record a baseline before launch: `/self-host-apps` had 601 Google impressions at position 62 from July 26 to October 23. Pull its Bing numbers and any query rows that name categories, too. + +| Weekly check | Source | +| --- | --- | +| Indexed state of each of the 20 pages | Bing URL Inspection; Google URL Inspection for all 20, since the set is small | +| Impressions, clicks, and position for `/self-host-apps/categories/*` | Bing and Google page reports, main site only, comparing equal 28-day windows | +| Queries for each page | Leave out brand and `site:` queries. Check that queries match the page's main phrase. | +| `/self-host-apps` itself | Make sure the category pages aren't taking its impressions without adding any. | +| Visits by referrer | Umami, filtered to `stack127.com`: Bing, DuckDuckGo, Google, ChatGPT | +| Use | Clicks from a category page to an app's install or source link, through a minimal Umami event, if Sol adds one. Skip it if it needs new consent work. | + +Decision rules: + +- **Week 6, wave check.** If at least 5 of the first 10 pages are indexed in Bing or Google, launch the second 10. If not, look into crawl and discovery first. +- **Week 10, scale up.** If at least 10 of 20 are indexed, and at least 5 pages show non-brand impressions for their main phrase in 2 or more separate weeks, plan the next 20 categories and the 10-page app-page test. +- **Week 10, fix.** Pages that are indexed but get no relevant impressions need their topic and title checked against what ranks. Don't add pages. +- **Week 10, stop.** If fewer than 5 pages are indexed in any engine, stop adding pages. Keep accurate pages published. Reopen the sitewide crawl and indexing investigation (audit section 9), because the problem isn't these pages. +- Any page whose data drops below the publish rules goes to `is_indexable = false` until it's fixed. + +These are internal checkpoints, not traffic promises. + +## 7. Work split and order + +| Step | Lane | Work | +| --- | --- | --- | +| 0 | Coordinator or Sol | Pull Bing and Google query rows for `/self-host-apps`. Reorder the 20 if some categories already show impressions. About an hour. | +| 1 | Luna | For each of the 20 and the reserves: read the top 5 results for the main phrase, list the expected apps, and confirm or fix membership against the rules in section 2. Enrich the missing apps (Forgejo, GitLab, OneDev, GlitchTip, Psono, Readeck) and fix wrong install facts through the enrichment skill. Run the Q2 artifact cleanup for apps in these categories. Deliver the reviewed membership JSON with evidence, plus the search words for each page. | +| 2 | Sol | Migration for the membership table, the sync command with preview and apply, the date trigger rule, cache tag and `targets.mjs` mapping, the `proxy.ts` family tag, the parent layout's structured-data change, publisher records checks, and the doc updates. Runs alongside step 1. | +| 3 | Opus | The category route and template (table and app sections), the "Browse by category" block, the dialog link, breadcrumbs, and the shared method MDX. Start with 3 ready pages (password managers, web analytics, photo apps) on development data. | +| 4 | Opus | Intro, "How to choose", and titles for each page, using the directory writing skill and Luna's search words. Write `brief.md`. | +| 5 | Sol | Review all code before it goes up. Opus fixes small findings. | +| 6 | Coordinator | Publish to development with `is_indexable = false`. Check real pages at desktop and mobile width. The owner reviews 3 sample pages. | +| 7 | Coordinator | After the owner says yes: release PR, CI, merge, then the production publisher batch for wave 1 with `is_indexable = true`. Verify the sitemap and public HTML. | +| 8 | Coordinator | Weekly measurement. Wave 2 at week 6 if the rule passes. | + +Steps 1 and 2 run in parallel. Step 3 can start on development data before step 1 finishes. Pages 18 to 20 wait for Luna's enrichment. + +## 8. Decisions for the owner + +| # | Decision | Recommendation | +| ---: | --- | --- | +| 1 | Pilot directory | **Self-host apps.** It's the only large directory with a search signal and enough data. CLI apps follow if the pilot works. | +| 2 | Page type for the pilot | **Category pages only.** Hold app pages until week 10. The requirement data is too thin, and one page per app won't fit the Cloudflare Free file limit. | +| 3 | URL pattern | **`/self-host-apps/categories/`.** Our docs already use it. It keeps `/self-host-apps/` free for app pages, and the parent stays clear. The other choices are `/self-host-apps/`, which can clash with app slugs such as `docs` and `homepage`, and root URLs like `/self-hosted-password-managers`, which break the child-page rule in product direction. | +| 4 | Where membership lives | **A database table with a reviewed sync command.** Page dates and cache refreshes then stay automatic, and scaling later needs no redesign. A code file would ship faster, but every real fact change would need a manual date touch. | +| 5 | Launch shape | **Two waves of 10, six weeks apart.** If the first 10 don't get indexed, we learn that before writing the second 10. | +| 6 | Consumer and homelab topics (media servers, homelab dashboards) | **Leave them out of the pilot.** They have demand, but they're off the indie hacker focus. They stay as reserves. | +| 7 | Add the missing big apps first (Forgejo, GitLab, GlitchTip, and others) | **Yes, but only the 3 pages that need them wait.** The other 17 go ahead. | +| 8 | The full Q2 cleanup of the main listing | **Clean only the pilot categories now.** The full cleanup continues as its own task, so it doesn't block the pilot. | diff --git a/content_docs/keyboard-shortcuts/altplusctrl-backlog.md b/content_docs/keyboard-shortcuts/altplusctrl-backlog.md index 7fd47ac2..b28d601c 100644 --- a/content_docs/keyboard-shortcuts/altplusctrl-backlog.md +++ b/content_docs/keyboard-shortcuts/altplusctrl-backlog.md @@ -13,7 +13,7 @@ Research must start with the current first-party shortcut reference for the name | Abstract | `abstract` | Blocked | https://www.goabstract.com/release-notes | | ActiveCampaign | `activecampaign` | Blocked | https://help.activecampaign.com/hc/en-us/articles/14618639644956-Updated-Link-Modal | | Adobe Acrobat Pro | `adobe-acrobat-pro` | Verified | https://helpx.adobe.com/acrobat/desktop/get-started/preferences-and-settings/keyboard-shortcuts.html | -| Adobe Acrobat Reader | `adobe-acrobat-reader` | Verified | https://helpx.adobe.com/reader/desktop/accessibility-features.html | +| Adobe Acrobat Reader | `adobe-acrobat-reader` | Verified | https://helpx.adobe.com/acrobat/desktop/get-started/preferences-and-settings/keyboard-shortcuts.html | | Adobe After Effects | `adobe-after-effects` | Verified | https://helpx.adobe.com/after-effects/desktop/get-started/keyboard-shortcuts/keyboard-shortcuts-reference.html | | Adobe Audition | `adobe-audition` | Verified | https://helpx.adobe.com/audition/desktop/keyboard-shortcuts/default-keyboard-shortcuts.html | | Adobe Illustrator | `adobe-illustrator` | Verified | https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html | @@ -101,7 +101,7 @@ Research must start with the current first-party shortcut reference for the name | Google Docs | `google-docs` | Verified | https://support.google.com/docs/answer/179738?hl=en-GB | | Google Drive | `google-drive` | Verified | https://support.google.com/drive/answer/2563044?co=GENIE.Platform%3DDesktop&hl=en | | Google Forms | `google-forms` | Verified | https://support.google.com/docs/answer/6026976?hl=en | -| Google Gemini | `google-gemini` | Verified | https://support.google.com/gemini/answer/16988996?hl=en-GP | +| Google Gemini | `google-gemini` | Verified | https://support.google.com/chrome/answer/16988996?hl=en | | Google Meet | `google-meet` | Verified | https://support.google.com/meet/answer/9298571?hl=en | | Google Photos | `google-photos` | Blocked | https://support.google.com/photos | | Google Slides | `google-slides` | Verified | https://support.google.com/docs/answer/1696717?hl=en-EN | @@ -133,7 +133,7 @@ Research must start with the current first-party shortcut reference for the name | Looker | `looker` | Verified | https://docs.cloud.google.com/looker/docs/keyboard-shortcuts?hl=en | | Loom | `loom` | Verified | https://support.atlassian.com/loom/docs/use-looms-keyboard-shortcuts | | Lucidchart | `lucidchart` | Blocked | https://help.lucid.co/hc/en-us/articles/11970952773652-Welcome-to-Lucidchart | -| macOS Finder | `macos-finder` | Verified | https://support.apple.com/102650 | +| macOS Finder | `macos-finder` | Verified | https://support.apple.com/en-us/102650 | | macOS Spotlight | `macos-spotlight` | Verified | https://support.apple.com/guide/mac-help/mh26783/mac | | Mailchimp | `mailchimp` | Blocked | https://mailchimp.com/help/add-a-link-in-a-campaign/ | | Marvel | `marvel` | Blocked | https://help.marvelapp.com/ | @@ -161,7 +161,7 @@ Research must start with the current first-party shortcut reference for the name | Numbers | `numbers` | Verified | https://support.apple.com/guide/numbers/keyboard-shortcuts-tana45192591/mac | | OBS Studio | `obs-studio` | Verified | https://obsproject.com/kb/keyboard-shortcuts | | Obsidian | `obsidian` | Verified | https://obsidian.md/help/editing-shortcuts | -| OmniFocus | `omnifocus` | Verified | https://support.omnigroup.com/documentation/omnifocus/universal/4.8.11/en/commands/ | +| OmniFocus | `omnifocus` | Verified | https://support.omnigroup.com/documentation/omnifocus/universal/4.9.3/en/commands/ | | Opera | `opera` | Verified | https://help.opera.com/en/latest/shortcuts/ | | Oracle VirtualBox | `oracle-virtualbox` | Verified | https://docs.oracle.com/en/virtualization/virtualbox/7.2/user/working-with-vms.html | | Otter.ai | `otter-ai` | Verified | https://help.otter.ai/hc/en-us/articles/29431724341399-Keyboard-Shortcuts | diff --git a/lib/seo/query-indexing.mjs b/lib/seo/query-indexing.mjs new file mode 100644 index 00000000..2299f9cf --- /dev/null +++ b/lib/seo/query-indexing.mjs @@ -0,0 +1,27 @@ +// Keep origin decisions and the static frontend's Ruleset Engine expression together. +export const trackingQueryNames = Object.freeze([ + "gclid", "dclid", "gbraid", "wbraid", "fbclid", "msclkid", "ref", "mc_cid", "mc_eid", +]); + +/** @param {string} name Raw, case-sensitive parameter name, without URL decoding. */ +export function isTrackingQueryName(name) { + return name.startsWith("utm_") || trackingQueryNames.includes(name); +} + +/** @param {string} search The URL search, with or without its leading question mark. */ +export function queryNeedsNoindex(search) { + const query = search.startsWith("?") ? search.slice(1) : search; + if (!query) return false; + // Cloudflare args.names preserves encoding and case. URLSearchParams decodes names, + // which would incorrectly exempt e.g. %75tm_source only at the origin. + const arguments_ = query.split("&").filter(Boolean); + return arguments_.length === 0 || arguments_.some(argument => !isTrackingQueryName(argument.split("=", 1)[0])); +} + +export function queryNoindexExpression() { + const names = "http.request.uri.args.names"; + const allowed = trackingQueryNames.map(name => JSON.stringify(name)).join(" "); + // The predicates inside any() apply to the SAME name. Two separate any() + // calls would wrongly reject a mix of utm_source and gclid. + return `(http.host eq "stack127.com" and http.request.uri.query ne "" and (len(${names}) eq 0 or any(not starts_with(${names}[*], "utm_") and not ${names}[*] in {${allowed}})) and not starts_with(http.request.uri.path, "/api/") and not starts_with(http.request.uri.path, "/_next/") and not starts_with(http.request.uri.path, "/vitals/"))`; +} diff --git a/lib/shortcuts/apps/1password.ts b/lib/shortcuts/apps/1password.ts index 7a33aac2..3a3c6f75 100644 --- a/lib/shortcuts/apps/1password.ts +++ b/lib/shortcuts/apps/1password.ts @@ -9,10 +9,10 @@ export const onePassword: AppShortcuts = { category: "Password manager", platforms: [...platforms], versionContext: - "Current desktop defaults checked August 31, 2026. Global shortcuts can be changed in 1Password settings. Linux global shortcuts depend on X11 or user-configured Wayland bindings. iPhone and iPad shortcuts are outside stack127's current platform model.", + "Current desktop defaults checked November 1, 2026. Global shortcuts can be changed in 1Password settings. Linux global shortcuts depend on X11 or user-configured Wayland bindings. iPhone and iPad shortcuts are outside stack127's current platform model.", sourceUrl: "https://support.1password.com/keyboard-shortcuts/", sourceLabel: "official 1Password keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "global", @@ -22,7 +22,11 @@ export const onePassword: AppShortcuts = { { id: "show-quick-access", action: "Show Quick Access", - keys: combo(platforms, ["Ctrl", "Shift", "Space"], ["Shift", "Cmd", "Space"]), + keys: [ + { platform: "windows", keys: ["Ctrl", "Shift", "Space"] }, + { platform: "macos", keys: ["Option", "Cmd", "\\"], alternatives: [["Shift", "Cmd", "Space"]], note: "Use Shift+Cmd+Space on macOS 26 or earlier." }, + { platform: "linux", keys: ["Ctrl", "Shift", "Space"] }, + ], note: "The Linux default applies to X11. Wayland users configure a desktop-specific global binding.", }, { @@ -123,7 +127,15 @@ export const onePassword: AppShortcuts = { title: "Quick Access", intro: "Search accounts and act on the selected result without opening the full 1Password window.", shortcuts: [ - { id: "qa-toggle", action: "Open or close Quick Access", keys: combo(platforms, ["Ctrl", "Shift", "Space"], ["Shift", "Cmd", "Space"]) }, + { + id: "qa-toggle", + action: "Open or close Quick Access", + keys: [ + { platform: "windows", keys: ["Ctrl", "Shift", "Space"] }, + { platform: "macos", keys: ["Option", "Cmd", "\\"], alternatives: [["Shift", "Cmd", "Space"]], note: "Use Shift+Cmd+Space on macOS 26 or earlier." }, + { platform: "linux", keys: ["Ctrl", "Shift", "Space"] }, + ], + }, { id: "qa-clear-search", action: "Clear the Quick Access search field", keys: combo(platforms, ["Esc"]) }, { id: "qa-all-accounts", action: "Switch Quick Access to All Accounts", keys: combo(platforms, ["Ctrl", "1"], ["Cmd", "1"]) }, { id: "qa-account-by-number", action: "Switch accounts or collections by position", keys: combo(platforms, ["Ctrl", "2-9"], ["Cmd", "2-9"]) }, diff --git a/lib/shortcuts/apps/ableton-live.ts b/lib/shortcuts/apps/ableton-live.ts index ca311610..3ba5d5ba 100644 --- a/lib/shortcuts/apps/ableton-live.ts +++ b/lib/shortcuts/apps/ableton-live.ts @@ -19,10 +19,10 @@ export const abletonLive: AppShortcuts = { category: "Digital audio workstation", platforms: ["windows", "macos"], versionContext: - "Documented defaults for Ableton Live 12 checked October 10, 2026. Single-letter commands are affected by the Computer MIDI Keyboard. When it is active, hold Shift with a single-letter shortcut. User Key Map and MIDI Map assignments are not defaults and are not listed.", + "Documented defaults for Ableton Live 12 checked November 1, 2026. Single-letter commands are affected by the Computer MIDI Keyboard. When it is active, hold Shift with a single-letter shortcut. User Key Map and MIDI Map assignments are not defaults and are not listed.", sourceUrl: "https://www.ableton.com/en/manual/live-keyboard-shortcuts/", sourceLabel: "Ableton Live 12 keyboard shortcuts manual", - lastVerified: "2026-10-10", + lastVerified: "2026-11-01", groups: [ { id: "views", diff --git a/lib/shortcuts/apps/adobe-acrobat-pro.ts b/lib/shortcuts/apps/adobe-acrobat-pro.ts index 6e06dc21..b0ef222f 100644 --- a/lib/shortcuts/apps/adobe-acrobat-pro.ts +++ b/lib/shortcuts/apps/adobe-acrobat-pro.ts @@ -311,10 +311,10 @@ export const adobeAcrobatPro: AppShortcuts = { category: "PDF editor", platforms: ["windows", "macos"], versionContext: - "Current Acrobat desktop defaults from Adobe's September 23, 2025 reference, checked August 31, 2026. Enable Use single-key accelerators to access tools for the one-letter tool commands. Browser-hosted PDFs may reserve some bindings before Acrobat receives them.", + "Current Acrobat desktop defaults from Adobe's September 23, 2025 reference, checked November 1, 2026. Enable Use single-key accelerators to access tools for the one-letter tool commands. Browser-hosted PDFs may reserve some bindings before Acrobat receives them.", sourceUrl: "https://helpx.adobe.com/acrobat/desktop/get-started/preferences-and-settings/keyboard-shortcuts.html", sourceLabel: "Adobe Acrobat desktop keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: acrobatSharedGroups, }; diff --git a/lib/shortcuts/apps/adobe-acrobat-reader.ts b/lib/shortcuts/apps/adobe-acrobat-reader.ts index e57c9e44..641fc9a2 100644 --- a/lib/shortcuts/apps/adobe-acrobat-reader.ts +++ b/lib/shortcuts/apps/adobe-acrobat-reader.ts @@ -7,10 +7,10 @@ export const adobeAcrobatReader: AppShortcuts = { category: "PDF reader", platforms: ["windows", "macos"], versionContext: - "Current Acrobat Reader desktop defaults checked August 31, 2026. Adobe's June 21, 2026 Reader accessibility guide states that Reader and Acrobat use the same keyboard shortcuts and links to the September 23, 2025 Acrobat table. Enable Use single-key accelerators to access tools for one-letter commands.", - sourceUrl: "https://helpx.adobe.com/reader/desktop/accessibility-features.html", - sourceLabel: "official Adobe Acrobat Reader keyboard shortcuts", - lastVerified: "2026-08-31", + "Current Acrobat Reader desktop defaults from Adobe's September 23, 2025 Acrobat reference, checked November 1, 2026. Adobe's Reader accessibility guide states that Reader and Acrobat use the same keyboard shortcuts. Enable Use single-key accelerators to access tools for one-letter commands.", + sourceUrl: "https://helpx.adobe.com/acrobat/desktop/get-started/preferences-and-settings/keyboard-shortcuts.html", + sourceLabel: "Adobe Acrobat desktop keyboard shortcuts (shared with Reader)", + lastVerified: "2026-11-01", groups: acrobatReaderGroups, }; diff --git a/lib/shortcuts/apps/adobe-after-effects.ts b/lib/shortcuts/apps/adobe-after-effects.ts index 9a79018c..bedca717 100644 --- a/lib/shortcuts/apps/adobe-after-effects.ts +++ b/lib/shortcuts/apps/adobe-after-effects.ts @@ -19,10 +19,10 @@ export const adobeAfterEffects: AppShortcuts = { category: "Motion graphics and compositing", platforms: ["windows", "macos"], versionContext: - "After Effects Default preset on a supported U.S. English keyboard, from Adobe's May 5, 2026 reference and checked August 31, 2026. Custom shortcut sets are not listed. Numeric-keypad commands need Num Lock on Windows. macOS function keys can conflict with system shortcuts, and panel shortcuts override application shortcuts while that panel has focus.", + "After Effects Default preset on a supported U.S. English keyboard, from Adobe's May 5, 2026 reference and checked November 1, 2026. Custom shortcut sets are not listed. Numeric-keypad commands need Num Lock on Windows. macOS function keys can conflict with system shortcuts, and panel shortcuts override application shortcuts while that panel has focus.", sourceUrl: "https://helpx.adobe.com/after-effects/desktop/get-started/keyboard-shortcuts/keyboard-shortcuts-reference.html", sourceLabel: "Adobe After Effects keyboard shortcuts reference", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "general-and-projects", diff --git a/lib/shortcuts/apps/adobe-audition.ts b/lib/shortcuts/apps/adobe-audition.ts index ee3d74bb..18cabbc6 100644 --- a/lib/shortcuts/apps/adobe-audition.ts +++ b/lib/shortcuts/apps/adobe-audition.ts @@ -19,10 +19,10 @@ export const adobeAudition: AppShortcuts = { category: "Audio editor", platforms: ["windows", "macos"], versionContext: - "Adobe's current expert-selected default list, updated June 17, 2026 and checked August 31, 2026. Adobe labels the web reference as partial and directs users to Edit > Keyboard Shortcuts for the complete installed keymap. CD-authoring commands removed in Audition 26.3 are not included, and custom shortcut sets are outside this reference.", + "Adobe's current expert-selected default list, updated June 17, 2026 and checked November 1, 2026. Adobe labels the web reference as partial and directs users to Edit > Keyboard Shortcuts for the complete installed keymap. CD-authoring commands removed in Audition 26.3 are not included, and custom shortcut sets are outside this reference.", sourceUrl: "https://helpx.adobe.com/audition/desktop/keyboard-shortcuts/default-keyboard-shortcuts.html", sourceLabel: "Adobe Audition default keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "playback-zoom-markers", diff --git a/lib/shortcuts/apps/adobe-indesign.ts b/lib/shortcuts/apps/adobe-indesign.ts index 44b07d49..1cac6773 100644 --- a/lib/shortcuts/apps/adobe-indesign.ts +++ b/lib/shortcuts/apps/adobe-indesign.ts @@ -18,10 +18,10 @@ export const adobeInDesign: AppShortcuts = { category: "Page layout and publishing", platforms: ["windows", "macos"], versionContext: - "Current InDesign desktop defaults from Adobe's keyboard shortcut reference checked August 31, 2026. The tables use the InDesign Default shortcut set, not its optional Illustrator, Photoshop, PageMaker, QuarkXPress, or custom sets.", + "Current InDesign desktop defaults from Adobe's keyboard shortcut reference checked November 1, 2026. The tables use the InDesign Default shortcut set, not its optional Illustrator, Photoshop, PageMaker, QuarkXPress, or custom sets.", sourceUrl: "https://helpx.adobe.com/indesign/desktop/get-started/settings-and-preferences/keyboard-shortcuts.html", sourceLabel: "Adobe InDesign keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "selection-and-navigation-tools", diff --git a/lib/shortcuts/apps/adobe-lightroom-classic.ts b/lib/shortcuts/apps/adobe-lightroom-classic.ts index 6f3b4775..23fa3b65 100644 --- a/lib/shortcuts/apps/adobe-lightroom-classic.ts +++ b/lib/shortcuts/apps/adobe-lightroom-classic.ts @@ -27,10 +27,10 @@ export const adobeLightroomClassic: AppShortcuts = { category: "Photo catalog and editor", platforms: ["windows", "macos"], versionContext: - "Current Lightroom Classic desktop defaults from Adobe's reference updated July 5, 2026 and checked August 31, 2026. Adobe's August 2026 release notes identify Lightroom Classic 15.5 as the current release.", + "Current Lightroom Classic desktop defaults from Adobe's reference updated July 5, 2026 and checked November 1, 2026. Adobe's August 2026 release notes identify Lightroom Classic 15.5 as the current release.", sourceUrl: "https://helpx.adobe.com/lightroom-classic/desktop/introduction-to-lightroom-classic/keyboard-shortcuts.html", sourceLabel: "Adobe Lightroom Classic keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "workspace-and-modules", diff --git a/lib/shortcuts/apps/adobe-premiere-pro.ts b/lib/shortcuts/apps/adobe-premiere-pro.ts index 42ed2640..a9f2bb42 100644 --- a/lib/shortcuts/apps/adobe-premiere-pro.ts +++ b/lib/shortcuts/apps/adobe-premiere-pro.ts @@ -18,10 +18,10 @@ export const adobePremierePro: AppShortcuts = { category: "Video editor", platforms: ["windows", "macos"], versionContext: - "Current Adobe Premiere desktop default shortcut set from Adobe's reference updated January 7, 2026 and checked August 31, 2026. Custom presets and unassigned commands are not listed.", + "Current Adobe Premiere desktop default shortcut set from Adobe's reference updated January 7, 2026 and checked November 1, 2026. Custom presets and unassigned commands are not listed.", sourceUrl: "https://helpx.adobe.com/premiere/desktop/get-started/keyboard-shortcuts/default-keyboard-shortcuts.html", sourceLabel: "Adobe Premiere default keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "projects-and-files", diff --git a/lib/shortcuts/apps/adobe-substance-3d-painter.ts b/lib/shortcuts/apps/adobe-substance-3d-painter.ts index 946f1650..7e81f96e 100644 --- a/lib/shortcuts/apps/adobe-substance-3d-painter.ts +++ b/lib/shortcuts/apps/adobe-substance-3d-painter.ts @@ -19,10 +19,10 @@ export const adobeSubstance3dPainter: AppShortcuts = { category: "3D texturing", platforms: ["windows", "macos"], versionContext: - "Current defaults from Adobe's July 3, 2026 shortcut reference, checked August 31, 2026. Editable assignments can be changed in Settings > Shortcuts. Fixed shortcuts are identified by their group, and actions whose official shortcut cell is blank are omitted.", + "Current defaults from Adobe's July 3, 2026 shortcut reference, checked November 1, 2026. Editable assignments can be changed in Settings > Shortcuts. Fixed shortcuts are identified by their group, and actions whose official shortcut cell is blank are omitted.", sourceUrl: "https://experienceleague.adobe.com/en/docs/substance-3d-painter/using/interface/settings/shortcuts", sourceLabel: "Adobe Substance 3D Painter shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "application-and-projects", diff --git a/lib/shortcuts/apps/affinity-designer.ts b/lib/shortcuts/apps/affinity-designer.ts index d26dacfb..69071423 100644 --- a/lib/shortcuts/apps/affinity-designer.ts +++ b/lib/shortcuts/apps/affinity-designer.ts @@ -19,10 +19,10 @@ export const affinityDesigner: AppShortcuts = { category: "Vector design", platforms: ["windows", "macos"], versionContext: - "Affinity Designer 2 desktop defaults from Serif's May 23, 2024 Windows and macOS shortcut sheets, cross-checked against the current Affinity V2 online help on August 31, 2026. The sheets use English UK keyboard layouts, so physical key positions can differ. Customized shortcuts are not listed.", + "Affinity Designer 2 desktop defaults from Serif's May 23, 2024 Windows and macOS shortcut sheets, cross-checked against the current Affinity V2 online help on November 1, 2026. The sheets use English UK keyboard layouts, so physical key positions can differ. Customized shortcuts are not listed.", sourceUrl: "https://affinity.help/designer2/English.lproj/pages/Workspace/shortcuts.html", sourceLabel: "Affinity Designer 2 keyboard shortcuts help", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "documents-and-editing", @@ -106,7 +106,7 @@ export const affinityDesigner: AppShortcuts = { ["zoom-200", "Zoom to 200%", [["Ctrl", "2"]], [["Cmd", "2"]]], ["zoom-400", "Zoom to 400%", [["Ctrl", "3"]], [["Cmd", "3"]]], ["zoom-800", "Zoom to 800%", [["Ctrl", "4"]], [["Cmd", "4"]]], - ["opacity", "Set opacity from 10% through 100%", [["1-9", "or", "0"]]], + ["opacity", "Set opacity via numeric keys", [["Number keys"]], undefined, "Type one or more digits for the target percentage; for example, 43 sets opacity to 43%."], ["brush-smaller", "Decrease brush size", [["["]]], ["brush-larger", "Increase brush size", [["]"]]], ["toggle-selector", "Switch the active fill or stroke selector", [["X"]]], diff --git a/lib/shortcuts/apps/affinity-photo.ts b/lib/shortcuts/apps/affinity-photo.ts index 28986351..d70e08fb 100644 --- a/lib/shortcuts/apps/affinity-photo.ts +++ b/lib/shortcuts/apps/affinity-photo.ts @@ -19,10 +19,10 @@ export const affinityPhoto: AppShortcuts = { category: "Photo editing", platforms: ["windows", "macos"], versionContext: - "Affinity Photo 2 desktop defaults from Serif's September 19, 2023 Windows and macOS shortcut sheets, cross-checked against the current Affinity V2 online help on August 31, 2026. The sheets use English UK or British keyboard layouts, so physical key positions can differ. Customized shortcuts are not listed.", + "Affinity Photo 2 desktop defaults from Serif's September 19, 2023 Windows and macOS shortcut sheets, cross-checked against the current Affinity V2 online help on November 1, 2026. The sheets use English UK or British keyboard layouts, so physical key positions can differ. Customized shortcuts are not listed.", sourceUrl: "https://affinity.help/photo2/English.lproj/pages/Workspace/shortcuts.html", sourceLabel: "Affinity Photo 2 keyboard shortcuts help", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "documents-and-layers", @@ -119,7 +119,6 @@ export const affinityPhoto: AppShortcuts = { ["overlay-gradient", "Activate the Overlay Gradient tool", [["G"]]], ["develop-crop", "Activate the Crop tool", [["C"]]], ["crop-overlay", "Cycle the crop overlay", [["O"]]], - ["brush-hardness", "Set brush hardness from 10% through 90%, or 0%", [["1-9", "or", "0"]]], ]), }, { @@ -135,7 +134,7 @@ export const affinityPhoto: AppShortcuts = { ["zoom-200", "Zoom to 200%", [["Ctrl", "2"]], [["Cmd", "2"]]], ["zoom-400", "Zoom to 400%", [["Ctrl", "3"]], [["Cmd", "3"]]], ["zoom-800", "Zoom to 800%", [["Ctrl", "4"]], [["Cmd", "4"]]], - ["opacity", "Set opacity from 10% through 100%", [["1-9", "or", "0"]]], + ["opacity", "Set opacity via numeric keys", [["Number keys"]], undefined, "Type one or more digits for the target percentage; for example, 43 sets opacity to 43%."], ["brush-smaller", "Decrease brush size", [["["]]], ["brush-larger", "Increase brush size", [["]"]]], ["toggle-selector", "Switch the active colour selector", [["X"]]], diff --git a/lib/shortcuts/apps/aider.ts b/lib/shortcuts/apps/aider.ts index be0c9529..82865b4e 100644 --- a/lib/shortcuts/apps/aider.ts +++ b/lib/shortcuts/apps/aider.ts @@ -14,10 +14,10 @@ export const aider: AppShortcuts = { category: "AI pair programming", platforms: [...platforms], versionContext: - "Current Aider command-line documentation checked August 31, 2026. Emacs bindings are the default. The vi group applies only when Aider is started with --vim. Terminal emulators and operating systems can intercept some control or meta combinations before Aider receives them.", + "Current Aider command-line documentation checked November 1, 2026. Emacs bindings are the default. The vi group applies only when Aider is started with --vim. Terminal emulators and operating systems can intercept some control or meta combinations before Aider receives them.", sourceUrl: "https://aider.chat/docs/usage/commands.html", sourceLabel: "Aider in-chat commands and keybindings", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "prompt-and-multiline", diff --git a/lib/shortcuts/apps/airtable.ts b/lib/shortcuts/apps/airtable.ts index e529aee1..7d293a6d 100644 --- a/lib/shortcuts/apps/airtable.ts +++ b/lib/shortcuts/apps/airtable.ts @@ -18,10 +18,10 @@ export const airtable: AppShortcuts = { category: "Collaborative database", platforms: ["windows", "macos"], versionContext: - "Airtable web keyboard shortcuts from the current official support article checked August 31, 2026. At that check, the article displayed a relative 'Last updated 23 days ago' label rather than a stable revision date. Airtable publishes separate Mac and Windows tables. Browser and operating-system shortcuts, screenshots, and modifier-plus-pointer gestures are omitted. No Linux keymap is documented on the source page.", + "Airtable web keyboard shortcuts from the official support article, last updated October 20, 2026 and checked November 1, 2026. Airtable publishes separate Mac and Windows tables. Browser and operating-system shortcuts, screenshots, and modifier-plus-pointer gestures are omitted. No Linux keymap is documented on the source page.", sourceUrl: "https://support.airtable.com/articles/7980233311-airtable-keyboard-shortcuts", sourceLabel: "Airtable keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "general", diff --git a/lib/shortcuts/apps/alfred.ts b/lib/shortcuts/apps/alfred.ts index 11140636..75ec973c 100644 --- a/lib/shortcuts/apps/alfred.ts +++ b/lib/shortcuts/apps/alfred.ts @@ -14,10 +14,10 @@ export const alfred: AppShortcuts = { category: "Productivity launcher", platforms: ["macos"], versionContext: - "Default Alfred 5 hotkeys from the current official cheatsheet checked August 31, 2026. Alfred lets users change hotkeys, so modified installations can differ. Universal Actions, File System Navigation, Clipboard History, snippets, contacts, email, workflows, and the Music Mini Player require the Powerpack where noted.", + "Default Alfred 5 hotkeys from the current official cheatsheet checked November 1, 2026. Alfred lets users change hotkeys, so modified installations can differ. Universal Actions, File System Navigation, Clipboard History, snippets, contacts, email, workflows, and the Music Mini Player require the Powerpack where noted.", sourceUrl: "https://www.alfredapp.com/help/getting-started/cheatsheet/", sourceLabel: "Alfred 5 cheatsheet", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "search-and-results", diff --git a/lib/shortcuts/apps/amplitude.ts b/lib/shortcuts/apps/amplitude.ts index c9325eac..c61a1fc1 100644 --- a/lib/shortcuts/apps/amplitude.ts +++ b/lib/shortcuts/apps/amplitude.ts @@ -1,6 +1,6 @@ import type { AppShortcuts, Shortcut } from "../types.ts"; -const platforms = ["windows", "macos", "linux"] as const; +const platforms = ["windows", "macos"] as const; const shared = (items: Array<[string, string, string[], string?]>): Shortcut[] => items.map(([id, action, combination, note]) => ({ id, @@ -15,10 +15,10 @@ export const amplitude: AppShortcuts = { category: "Product analytics", platforms: [...platforms], versionContext: - "Current Amplitude Analytics chart basics documentation checked August 31, 2026. These browser-app shortcuts work on chart creation pages for all chart types except Data Tables. The official table does not publish operating-system variants, so the documented combinations are shown unchanged across desktop platforms.", + "Amplitude Analytics chart basics shortcuts work on chart creation pages for all chart types except Data Tables. The official table publishes no operating-system variants, so the listed combinations are shown for Windows and macOS only.", sourceUrl: "https://amplitude.com/docs/analytics/charts/chart-basics#keyboard-shortcuts", sourceLabel: "Amplitude chart basics keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "chart-definition", diff --git a/lib/shortcuts/apps/android-studio.ts b/lib/shortcuts/apps/android-studio.ts index 1f3e04ba..408ed8ef 100644 --- a/lib/shortcuts/apps/android-studio.ts +++ b/lib/shortcuts/apps/android-studio.ts @@ -19,10 +19,10 @@ export const androidStudio: AppShortcuts = { category: "Android IDE", platforms: ["windows", "macos", "linux"], versionContext: - "Default Android Studio keymaps from Google's reference updated March 6, 2026 and checked August 31, 2026. Android Studio supports preset and custom keymaps, which can replace these defaults. Mouse-only gestures and modifier-plus-pointer alternatives are omitted.", + "Default Android Studio keymaps from Google's reference updated March 6, 2026 and checked November 1, 2026. Android Studio supports preset and custom keymaps, which can replace these defaults. Mouse-only gestures and modifier-plus-pointer alternatives are omitted.", sourceUrl: "https://developer.android.com/studio/intro/keyboard-shortcuts", sourceLabel: "Android Studio keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "tool-windows", @@ -63,7 +63,7 @@ export const androidStudio: AppShortcuts = { "Search actions, symbols, classes, and files, move through editor history and tabs, inspect hierarchies, and jump to a precise line or source location.", shortcuts: [ ...rows([ - ["search-everywhere", "Search everywhere, including code and menus", ["Shift", "Shift"], undefined, "Press Shift twice."], + ["search-everywhere", "Search everywhere, including code and menus", ["Shift", "then", "Shift"]], ["find", "Find in the current context", ["Control", "F"], ["Cmd", "F"]], ["find-next", "Find the next match", ["F3"], ["Cmd", "G"]], ["find-previous", "Find the previous match", ["Shift", "F3"], ["Cmd", "Shift", "G"]], diff --git a/lib/shortcuts/apps/anydesk.ts b/lib/shortcuts/apps/anydesk.ts index d72512ae..e9a2a20d 100644 --- a/lib/shortcuts/apps/anydesk.ts +++ b/lib/shortcuts/apps/anydesk.ts @@ -13,10 +13,10 @@ export const anydesk: AppShortcuts = { category: "Remote desktop", platforms: ["windows"], versionContext: - "Current Windows hotkeys from AnyDesk's July 30, 2025 reference, checked August 31, 2026. Mouse capture requires AnyDesk 8 or later. Remote-system shortcuts and keyboard translation examples are not AnyDesk commands and are not listed.", + "Current Windows hotkeys from AnyDesk's July 30, 2025 reference, checked November 1, 2026. Mouse capture requires AnyDesk 8 or later. Remote-system shortcuts and keyboard translation examples are not AnyDesk commands and are not listed.", sourceUrl: "https://support.anydesk.com/keyboard-hotkeys", sourceLabel: "AnyDesk keyboard and hotkeys", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "session-navigation", diff --git a/lib/shortcuts/apps/apple-notes.ts b/lib/shortcuts/apps/apple-notes.ts index d29d2861..e4c28259 100644 --- a/lib/shortcuts/apps/apple-notes.ts +++ b/lib/shortcuts/apps/apple-notes.ts @@ -99,7 +99,7 @@ export const appleNotes: AppShortcuts = { mac("select-row-range", "Extend the cell selection across a row", ["Shift", "Right"], [["Shift", "Left"]]), mac("select-column-range", "Extend the cell selection across a column", ["Shift", "Down"], [["Shift", "Up"]]), mac("select-cell", "Select the current cell's contents", ["Cmd", "A"]), - mac("select-table", "Select the entire table", ["Cmd", "A"], undefined, "Press Command+A twice while the insertion point is in the table."), + mac("select-table", "Select the entire table", ["Cmd", "A", "then", "Cmd", "A"], undefined, "Put the insertion point in the table first."), ], }, ], diff --git a/lib/shortcuts/apps/asana.ts b/lib/shortcuts/apps/asana.ts index 7b1820ae..5179407f 100644 --- a/lib/shortcuts/apps/asana.ts +++ b/lib/shortcuts/apps/asana.ts @@ -19,10 +19,10 @@ export const asana: AppShortcuts = { category: "Work management", platforms: ["windows", "macos"], versionContext: - "Current defaults from Asana's official Help Center, checked August 31, 2026. Some shortcuts may not work with non-English keyboard input. Desktop-window commands apply to the Asana desktop app. Tab+B requires the Tab-B hack to be enabled in Settings.", + "Current defaults from Asana's official Help Center, checked November 1, 2026. Some shortcuts may not work with non-English keyboard input. Desktop-window commands apply to the Asana desktop app. Tab+B requires the Tab-B hack to be enabled in Settings.", sourceUrl: "https://help.asana.com/s/article/keyboard-shortcuts", sourceLabel: "Asana keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "inbox", @@ -90,7 +90,7 @@ export const asana: AppShortcuts = { ["assign-selected", "Assign the selected task", [["Tab", "A"]]], ["focus-subtasks", "Focus the selected task's subtasks", [["Tab", "S"]]], ["search", "Search for a project, tag, person, or task", [["Tab", "/"]]], - ["field-navigation", "Enable list-view field navigation", [["Right"]], undefined, "Press Right Arrow twice."], + ["field-navigation", "Enable list-view field navigation", [["Right", "then", "Right"]]], ]), }, { diff --git a/lib/shortcuts/apps/audacity.ts b/lib/shortcuts/apps/audacity.ts index 731f1f18..9f98264d 100644 --- a/lib/shortcuts/apps/audacity.ts +++ b/lib/shortcuts/apps/audacity.ts @@ -19,10 +19,10 @@ export const audacity: AppShortcuts = { category: "Audio editor", platforms: ["windows", "macos", "linux"], versionContext: - "Current Audacity Development Manual checked August 31, 2026. This record includes the default Standard set only. The manual maps Ctrl to Command and Alt to Option on macOS, with explicit Mac exceptions preserved. Users can customize or replace the defaults in Shortcuts Preferences.", + "Current Audacity Development Manual checked November 1, 2026. This record includes the default Standard set only. The manual maps Ctrl to Command and Alt to Option on macOS, with explicit Mac exceptions preserved. Users can customize or replace the defaults in Shortcuts Preferences.", sourceUrl: "https://manual.audacityteam.org/man/keyboard_shortcut_reference.html", sourceLabel: "Audacity commands and keyboard shortcut reference", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "projects-files", diff --git a/lib/shortcuts/apps/autocad.ts b/lib/shortcuts/apps/autocad.ts index 7ed71bd7..bbdaca91 100644 --- a/lib/shortcuts/apps/autocad.ts +++ b/lib/shortcuts/apps/autocad.ts @@ -14,10 +14,10 @@ export const autocad: AppShortcuts = { category: "Computer-aided design", platforms: ["windows"], versionContext: - "Default AutoCAD 2026 shortcut keys from Autodesk's CUIx reference checked August 31, 2026. AutoCAD can customize these assignments, so edited CUIx files can differ. Entries marked AutoCAD only exclude AutoCAD LT. Autodesk publishes a separate product and keymap for AutoCAD on Mac, which is not mixed into this Windows record.", + "Default AutoCAD 2026 shortcut keys from Autodesk's CUIx reference checked November 1, 2026. AutoCAD can customize these assignments, so edited CUIx files can differ. Entries marked AutoCAD only exclude AutoCAD LT. Autodesk publishes a separate product and keymap for AutoCAD on Mac, which is not mixed into this Windows record.", sourceUrl: "https://help.autodesk.com/cloudhelp/2026/ENU/AutoCAD-Customization/files/GUID-B6A9C126-230D-47F5-8973-464BC80E3736.htm", sourceLabel: "AutoCAD 2026 shortcut keys reference", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "application-drawings", diff --git a/lib/shortcuts/apps/autodesk-maya.ts b/lib/shortcuts/apps/autodesk-maya.ts index 21769c94..2c4d526e 100644 --- a/lib/shortcuts/apps/autodesk-maya.ts +++ b/lib/shortcuts/apps/autodesk-maya.ts @@ -7,7 +7,6 @@ const rows = (items: Row[]): Shortcut[] => action, keys: [ { platform: "windows", keys }, - { platform: "linux", keys }, { platform: "macos", keys: macos }, ], ...(note ? { note } : {}), @@ -17,12 +16,12 @@ export const autodeskMaya: AppShortcuts = { slug: "autodesk-maya", name: "Autodesk Maya", category: "3D modeling and animation", - platforms: ["windows", "macos", "linux"], + platforms: ["windows", "macos"], versionContext: - "Maya 2026 default hotkeys from Autodesk's searchable reference checked August 31, 2026. On macOS, Autodesk maps Alt to Option and Ctrl to Control unless a command explicitly lists Command. The Hotkey Editor can replace these defaults. Some function keys and US-keyboard punctuation can conflict with macOS or non-US layouts.", + "Maya 2026 default hotkeys from Autodesk's searchable reference checked November 1, 2026. The reference documents Windows and macOS defaults only; no Linux defaults are included. On macOS, Autodesk maps Alt to Option and Ctrl to Control unless a command explicitly lists Command. The Hotkey Editor can replace these defaults. Some function keys and US-keyboard punctuation can conflict with macOS or non-US layouts.", sourceUrl: "https://help.autodesk.com/cloudhelp/2026/ENU/Maya-KeyboardShortcuts/files/GUID-30CACC9D-8FBE-4B85-8A8F-C5ADF32DDD4E.htm", sourceLabel: "Maya 2026 all hotkeys", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "tools-actions", diff --git a/lib/shortcuts/apps/autodesk-revit.ts b/lib/shortcuts/apps/autodesk-revit.ts index 771a9263..9b366626 100644 --- a/lib/shortcuts/apps/autodesk-revit.ts +++ b/lib/shortcuts/apps/autodesk-revit.ts @@ -15,10 +15,10 @@ export const autodeskRevit: AppShortcuts = { category: "Building information modeling", platforms: ["windows"], versionContext: - "Default Revit 2026 shortcuts from Autodesk's versioned PDF checked August 31, 2026. Letter shortcuts are sequences: press the keys in order rather than as a chord. Revit runs on Windows, and its Keyboard Shortcuts dialog can add, remove, or replace non-reserved assignments.", + "Default Revit 2026 shortcuts from Autodesk's versioned PDF checked November 1, 2026. Letter shortcuts are sequences: press the keys in order rather than as a chord. Revit runs on Windows, and its Keyboard Shortcuts dialog can add, remove, or replace non-reserved assignments.", sourceUrl: "https://www.autodesk.com/revit-keyboard-shortcut-list-pdf-2026-enu", sourceLabel: "Revit 2026 default keyboard shortcuts PDF", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "interface-views", diff --git a/lib/shortcuts/apps/axure-rp.ts b/lib/shortcuts/apps/axure-rp.ts index b93be365..c604d840 100644 --- a/lib/shortcuts/apps/axure-rp.ts +++ b/lib/shortcuts/apps/axure-rp.ts @@ -18,10 +18,10 @@ export const axureRp: AppShortcuts = { category: "Prototyping and wireframing", platforms: ["windows", "macos"], versionContext: - "Current Axure RP 11 keyboard shortcut reference checked August 31, 2026. Windows and Mac columns are preserved. Single-key shortcuts are enabled by default but can be disabled in Canvas preferences. Pointer gestures and modifier-only mouse behaviors are omitted.", + "Current Axure RP 11 keyboard shortcut reference checked November 1, 2026. Windows and Mac columns are preserved. Single-key shortcuts are enabled by default but can be disabled in Canvas preferences. Pointer gestures and modifier-only mouse behaviors are omitted.", sourceUrl: "https://docs.axure.com/axure-rp/reference/keyboard-shortcuts/", sourceLabel: "Axure RP 11 keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "common-text", diff --git a/lib/shortcuts/apps/balsamiq-wireframes.ts b/lib/shortcuts/apps/balsamiq-wireframes.ts index a08e5b22..5b2bca9e 100644 --- a/lib/shortcuts/apps/balsamiq-wireframes.ts +++ b/lib/shortcuts/apps/balsamiq-wireframes.ts @@ -35,10 +35,10 @@ export const balsamiqWireframes: AppShortcuts = { category: "Wireframing", platforms: ["windows", "macos"], versionContext: - "Current Balsamiq Wireframes shortcut reference checked August 31, 2026. The main table prints Control-based editing commands without a Mac equivalent; those rows are therefore labeled Windows. macOS entries appear only where Balsamiq explicitly publishes a Mac binding or a Control/Command pair.", + "Current Balsamiq Wireframes shortcut reference checked November 1, 2026. The main table prints Control-based editing commands without a Mac equivalent; those rows are therefore labeled Windows. macOS entries appear only where Balsamiq explicitly publishes a Mac binding or a Control/Command pair.", sourceUrl: "https://balsamiq.com/support/docs/creating-and-editing/shortcuts/", sourceLabel: "Balsamiq Wireframes keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "select-and-transform", diff --git a/lib/shortcuts/apps/basecamp.ts b/lib/shortcuts/apps/basecamp.ts index f9581b05..ad6eadf8 100644 --- a/lib/shortcuts/apps/basecamp.ts +++ b/lib/shortcuts/apps/basecamp.ts @@ -22,7 +22,7 @@ export const basecamp: AppShortcuts = { "Current Basecamp 5 keyboard cheat sheet checked August 31, 2026. Commands are shared by Windows and macOS except where Basecamp explicitly publishes Command or Option alternatives.", sourceUrl: "https://basecamp.com/keyboard", sourceLabel: "Basecamp 5 keyboard cheat sheet", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "jump-and-global", diff --git a/lib/shortcuts/apps/bear.ts b/lib/shortcuts/apps/bear.ts index 5e7e9d2e..56c219e3 100644 --- a/lib/shortcuts/apps/bear.ts +++ b/lib/shortcuts/apps/bear.ts @@ -16,7 +16,7 @@ export const bear: AppShortcuts = { "Current Bear for Mac shortcut reference checked August 31, 2026. Bear Web has a separate beta keymap and is not mixed into this native macOS reference. Back and forward shortcuts can vary with keyboard layout and localization.", sourceUrl: "https://bear.app/faq/mac-keyboard-shortcuts/", sourceLabel: "Bear for Mac keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "formatting", diff --git a/lib/shortcuts/apps/bitbucket.ts b/lib/shortcuts/apps/bitbucket.ts index bc7d9e84..cc67d821 100644 --- a/lib/shortcuts/apps/bitbucket.ts +++ b/lib/shortcuts/apps/bitbucket.ts @@ -1,6 +1,6 @@ import type { AppShortcuts, Shortcut } from "../types.ts"; -const platforms = ["windows", "macos", "linux"] as const; +const platforms = ["windows", "macos"] as const; const all = (id: string, action: string, keys: string[], note?: string): Shortcut => ({ id, action, @@ -14,10 +14,10 @@ export const bitbucket: AppShortcuts = { category: "Code hosting", platforms: [...platforms], versionContext: - "Current Bitbucket Cloud shortcut reference checked August 31, 2026. Single-letter commands work in the page context named by each group. Bitbucket lets users disable all keyboard shortcuts in Personal Bitbucket settings.", + "Current Bitbucket Cloud shortcut reference checked November 1, 2026. Single-letter commands work in the page context named by each group. Bitbucket lets users disable all keyboard shortcuts in Personal Bitbucket settings.", sourceUrl: "https://support.atlassian.com/bitbucket-cloud/docs/keyboard-shortcuts/", sourceLabel: "Bitbucket Cloud keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "global", diff --git a/lib/shortcuts/apps/bluebeam-revu.ts b/lib/shortcuts/apps/bluebeam-revu.ts index 4ab3cb93..39e6d656 100644 --- a/lib/shortcuts/apps/bluebeam-revu.ts +++ b/lib/shortcuts/apps/bluebeam-revu.ts @@ -18,7 +18,7 @@ export const bluebeamRevu: AppShortcuts = { "Official Revu 21 and Revu 20 keyboard reference checked August 31, 2026. These are default Windows assignments. Revu can reassign existing commands and add custom shortcuts, so a customized installation may differ.", sourceUrl: "https://support.bluebeam.com/revu/resources/keyboard-shortcuts.html", sourceLabel: "Bluebeam Revu keyboard shortcuts and mouse navigation", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "markup", diff --git a/lib/shortcuts/apps/brave.ts b/lib/shortcuts/apps/brave.ts index c911be7b..d60fd61e 100644 --- a/lib/shortcuts/apps/brave.ts +++ b/lib/shortcuts/apps/brave.ts @@ -12,7 +12,7 @@ export const brave: AppShortcuts = { "Documented defaults from the Brave Help Center checked August 26, 2026. The Windows and Linux table shares bindings, while macOS has its own table. Brave also allows shortcut changes in Settings, System, Shortcuts.", sourceUrl: "https://support.brave.app/hc/en-us/articles/360032272171-What-keyboard-shortcuts-can-I-use-in-Brave", sourceLabel: "official Brave keyboard shortcuts", - lastVerified: "2026-08-26", + lastVerified: "2026-11-01", groups: [ { id: "tabs-and-windows", @@ -41,7 +41,7 @@ export const brave: AppShortcuts = { { id: "close-window", action: "Close the current window", keys: combo(platforms, ["Ctrl", "Shift", "W"], ["Cmd", "Shift", "W"]) }, { id: "minimize-window", action: "Minimize the current window", keys: perPlatform(platforms, { windows: ["Alt", "Space", "N"], macos: ["Cmd", "M"], linux: ["Alt", "Space", "N"] }) }, { id: "maximize-window", action: "Maximize the current window", keys: perPlatform(platforms, { windows: ["Alt", "Space", "X"], linux: ["Alt", "Space", "X"] }), note: "No macOS shortcut is listed by Brave for this action." }, - { id: "quit", action: "Quit Brave", keys: perPlatform(platforms, { windows: ["Alt", "F4"], macos: ["Cmd", "Q"], linux: ["Alt", "F4"] }), note: "On Windows and Linux, Alt+F then X is also documented." }, + { id: "quit", action: "Quit Brave", keys: perPlatform(platforms, { windows: ["Alt", "F", "then", "X"], macos: ["Cmd", "Q"], linux: ["Alt", "F", "then", "X"] }) }, ], }, { @@ -71,7 +71,7 @@ export const brave: AppShortcuts = { shortcuts: [ { id: "focus-address-bar", action: "Focus the address bar", keys: perPlatform(platforms, { windows: ["Ctrl", "L"], macos: ["Cmd", "L"], linux: ["Ctrl", "L"] }), note: "Alt+D or F6 also works on Windows and Linux." }, { id: "search-anywhere", action: "Search from anywhere on the page", keys: perPlatform(platforms, { windows: ["Ctrl", "K"], macos: ["Cmd", "K"], linux: ["Ctrl", "K"] }), note: "Ctrl+E also works on Windows and Linux." }, - { id: "search-other-engine", action: "Search with another search engine", keys: combo(platforms, ["Tab"]), note: "Type the engine's shortcut name in the address bar, then press Tab. Brave documents this for all platforms." }, + { id: "search-other-engine", action: "Search with another search engine", keys: combo(platforms, ["{shortcut name}", "then", "Tab"]), note: "Type the engine's shortcut name in the address bar first." }, { id: "address-www-com", action: "Add www. and .com to a site name", keys: combo(platforms, ["Ctrl", "Enter"], ["Ctrl", "Return"]) }, { id: "address-new-tab", action: "Open an address-bar search in a new tab", keys: perPlatform(platforms, { windows: ["Alt", "Enter"], macos: ["Ctrl", "Return"], linux: ["Alt", "Enter"] }) }, { id: "remove-prediction", action: "Remove a highlighted address-bar prediction", keys: perPlatform(platforms, { windows: ["Down", "Shift", "Delete"], macos: ["Down", "Shift", "Fn", "Delete"], linux: ["Down", "Shift", "Delete"] }) }, diff --git a/lib/shortcuts/apps/buffer.ts b/lib/shortcuts/apps/buffer.ts index 8e769cd2..eff0b6e5 100644 --- a/lib/shortcuts/apps/buffer.ts +++ b/lib/shortcuts/apps/buffer.ts @@ -1,6 +1,6 @@ import type { AppShortcuts, PlatformId, Shortcut } from "../types.ts"; -const platforms: PlatformId[] = ["windows", "macos", "linux"]; +const platforms: PlatformId[] = ["windows", "macos"]; const same = (id: string, action: string, keys: string[], note?: string): Shortcut => ({ id, action, @@ -18,7 +18,6 @@ const modified = ( action, keys: [ { platform: "windows", keys: controlKeys }, - { platform: "linux", keys: controlKeys }, { platform: "macos", keys: commandKeys }, ], ...(note ? { note } : {}), @@ -30,10 +29,10 @@ export const buffer: AppShortcuts = { category: "Social media management", platforms, versionContext: - "Current Buffer Help Center shortcut list checked August 31, 2026. Navigation commands are two-key sequences, not simultaneous chords, and global shortcuts are suppressed while the cursor is in a text field. Buffer Home must be enabled for its navigation command.", + "Current Buffer Help Center shortcut list checked November 1, 2026. Navigation commands are two-key sequences, not simultaneous chords, and global shortcuts are suppressed while the cursor is in a text field. Buffer Home must be enabled for its navigation command.", sourceUrl: "https://support.buffer.com/en-us/articles/using-buffers-keyboard-shortcuts-jvc735UOsa", sourceLabel: "Using Buffer's keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "reference-command-bar", @@ -46,7 +45,6 @@ export const buffer: AppShortcuts = { action: "Open the Keyboard Shortcuts dialog", keys: [ { platform: "windows", keys: ["Shift", "/"], alternatives: [["Ctrl", "/"]] }, - { platform: "linux", keys: ["Shift", "/"], alternatives: [["Ctrl", "/"]] }, { platform: "macos", keys: ["Shift", "/"], alternatives: [["Cmd", "/"]] }, ], note: "Shift+/ produces the question mark character on standard keyboard layouts.", diff --git a/lib/shortcuts/apps/chrome.ts b/lib/shortcuts/apps/chrome.ts index 6b0e9afd..0a552db4 100644 --- a/lib/shortcuts/apps/chrome.ts +++ b/lib/shortcuts/apps/chrome.ts @@ -28,7 +28,7 @@ export const chrome: AppShortcuts = { { id: "tab-by-number", action: "Jump to a tab by position", keys: combo(platforms, ["Ctrl", "1"], ["Cmd", "1"]), note: "1 through 8 choose a tab by position. 9 goes to the last tab." }, { id: "last-tab", action: "Jump to the last tab", keys: combo(platforms, ["Ctrl", "9"], ["Cmd", "9"]) }, { id: "move-tab-left", action: "Move the current tab left", keys: combo(platforms, ["Ctrl", "Shift", "Page Up"], ["Ctrl", "Shift", "Page Up"]), note: "Use Page Down instead of Page Up to move it right. Chrome documents Ctrl for this command on macOS." }, - { id: "select-tabs", action: "Select multiple tabs", keys: perPlatform(platforms, { windows: ["F6"], macos: ["Ctrl", "F6"], linux: ["F6"] }), note: "Press F6 twice on Windows and Linux. On macOS press Ctrl+F6, then use arrow keys and Shift+arrow keys. The exact focus path matters." }, + { id: "select-tabs", action: "Select multiple tabs", keys: perPlatform(platforms, { windows: ["F6", "then", "F6", "then", "Shift", "Arrow"], macos: ["Ctrl", "F6", "then", "Shift", "Arrow"], linux: ["F6", "then", "F6", "then", "Shift", "Arrow"] }), note: "Use the arrow keys alone to move between tabs. The exact focus path matters." }, { id: "fullscreen", action: "Toggle full-screen mode", keys: perPlatform(platforms, { windows: ["F11"], macos: ["Fn", "F"], linux: ["F11"] }) }, { id: "minimize-window", action: "Minimize the current window", keys: perPlatform(platforms, { windows: ["Alt", "Space", "N"], macos: ["Cmd", "M"], linux: ["Alt", "Space", "N"] }) }, { id: "maximize-window", action: "Maximize the current window", keys: perPlatform(platforms, { windows: ["Alt", "Space", "X"], linux: ["Alt", "Space", "X"] }), note: "Chrome's official macOS list does not provide a maximize binding." }, @@ -57,7 +57,7 @@ export const chrome: AppShortcuts = { shortcuts: [ { id: "focus-address-bar", action: "Focus the address bar", keys: perPlatform(platforms, { windows: ["Ctrl", "L"], macos: ["Cmd", "L"], linux: ["Ctrl", "L"] }), note: "F6 or Alt+D also works on Windows and Linux." }, { id: "search-anywhere", action: "Search from anywhere on the page", keys: perPlatform(platforms, { windows: ["Ctrl", "K"], macos: ["Cmd", "K"], linux: ["Ctrl", "K"] }), note: "Ctrl+E also works on Windows and Linux." }, - { id: "search-other-engine", action: "Search with another search engine", keys: combo(platforms, ["Tab"]), note: "Type the engine's shortcut name in the address bar, then press Tab." }, + { id: "search-other-engine", action: "Search with another search engine", keys: combo(platforms, ["{shortcut name}", "then", "Tab"]), note: "Type the engine's shortcut name in the address bar first." }, { id: "add-www-com", action: "Add www. and .com to a site name", keys: combo(platforms, ["Ctrl", "Enter"], ["Cmd", "Enter"]), note: "Type the site name first." }, { id: "address-new-window", action: "Open the typed address in a new window", keys: combo(platforms, ["Ctrl", "Shift", "Enter"], ["Cmd", "Shift", "Enter"]), note: "Type an address in the address bar first." }, { id: "address-new-tab", action: "Open the typed address in a new tab", keys: perPlatform(platforms, { windows: ["Alt", "Enter"], macos: ["Cmd", "Return"], linux: ["Alt", "Enter"] }), note: "Type an address or search first." }, diff --git a/lib/shortcuts/apps/cinema-4d.ts b/lib/shortcuts/apps/cinema-4d.ts index 4a8c806a..b64e84b1 100644 --- a/lib/shortcuts/apps/cinema-4d.ts +++ b/lib/shortcuts/apps/cinema-4d.ts @@ -26,7 +26,7 @@ export const cinema4d: AppShortcuts = { "Cinema 4D 2026 default shortcut table checked August 31, 2026. Maxon says macOS users press Command instead of Ctrl. Letter pairs are hierarchical shortcuts pressed in sequence. Command Manager presets and manager-specific assignments can change the active set.", sourceUrl: "https://help.maxon.net/c4d/2026/en-us/Content/html/Shortcuts.html", sourceLabel: "Cinema 4D 2026 keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "animation", diff --git a/lib/shortcuts/apps/claude-code.ts b/lib/shortcuts/apps/claude-code.ts index 92312e19..775a36f4 100644 --- a/lib/shortcuts/apps/claude-code.ts +++ b/lib/shortcuts/apps/claude-code.ts @@ -22,7 +22,7 @@ export const claudeCode: AppShortcuts = { { id: "interrupt-escape", action: "Interrupt Claude mid-turn", keys: combo(platforms, ["Esc"]), note: "Keeps work completed so far." }, { id: "interrupt-control-c", action: "Cancel current input or generation", keys: combo(platforms, ["Ctrl", "C"]), note: "The first press clears a draft or stops work. A second press exits." }, { id: "rewind", action: "Clear a draft, or open the rewind menu when the prompt is empty", keys: combo(platforms, ["Esc", "Esc"]) }, - { id: "stop-background-work", action: "Stop background agents and artifact replies", keys: combo(platforms, ["Ctrl", "X", "Ctrl", "K"]), note: "Repeat the sequence to confirm." }, + { id: "stop-background-work", action: "Stop background agents and artifact replies", keys: combo(platforms, ["Ctrl", "X", "Ctrl", "K", "then", "Ctrl", "X", "Ctrl", "K"]), note: "The second press confirms." }, { id: "background-bash", action: "Move a running Bash command to the background", keys: combo(platforms, ["Ctrl", "B"]) }, { id: "exit-session", action: "Exit the session", keys: combo(platforms, ["Ctrl", "D"]), note: "Claude Code asks for confirmation when needed." }, { id: "clear-screen", action: "Redraw the terminal screen", keys: combo(platforms, ["Ctrl", "L"]) }, @@ -64,7 +64,7 @@ export const claudeCode: AppShortcuts = { { id: "delete-previous-word", action: "Delete the previous word", keys: combo(platforms, ["Ctrl", "W"]) }, { id: "delete-next-word", action: "Delete the next word", keys: combo(platforms, ["Alt", "D"]), note: "Requires Option as Meta on macOS." }, { id: "paste-deleted", action: "Paste the most recently deleted text", keys: combo(platforms, ["Ctrl", "Y"]) }, - { id: "cycle-paste-history", action: "Cycle through deleted-text history", keys: combo(platforms, ["Alt", "Y"]), note: "Press after Ctrl+Y. Requires Option as Meta on macOS." }, + { id: "cycle-paste-history", action: "Cycle through deleted-text history", keys: combo(platforms, ["Ctrl", "Y", "then", "Alt", "Y"]), note: "Requires Option as Meta on macOS." }, { id: "undo-input-edit", action: "Undo the last input edit", keys: combo(platforms, ["Ctrl", "_"]), note: "Ctrl+Shift+- also works." }, { id: "paste-image", action: "Paste an image from the clipboard", keys: perPlatform(platforms, { macos: ["Ctrl", "V"], windows: ["Alt", "V"], linux: ["Ctrl", "V"] }), note: "iTerm2 on macOS also accepts Cmd+V; Windows and WSL use Alt+V for image paste." }, ], diff --git a/lib/shortcuts/apps/clickup.ts b/lib/shortcuts/apps/clickup.ts index eb78fb82..8b3056c0 100644 --- a/lib/shortcuts/apps/clickup.ts +++ b/lib/shortcuts/apps/clickup.ts @@ -21,7 +21,7 @@ export const clickup: AppShortcuts = { "Current ClickUp Help shortcut table checked August 31, 2026. Workspace shortcuts are disabled by default and must be enabled in personal settings. Most global commands require focus outside a text field. Chat and SyncUps commands depend on those ClickApps being available.", sourceUrl: "https://help.clickup.com/hc/en-us/articles/6309030550167-Use-keyboard-shortcuts", sourceLabel: "ClickUp keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "global-navigation", diff --git a/lib/shortcuts/apps/clip-studio-paint.ts b/lib/shortcuts/apps/clip-studio-paint.ts index 91b97aea..a7400dbf 100644 --- a/lib/shortcuts/apps/clip-studio-paint.ts +++ b/lib/shortcuts/apps/clip-studio-paint.ts @@ -22,7 +22,7 @@ export const clipStudioPaint: AppShortcuts = { "Clip Studio Paint 5.0 default menu shortcuts at initial startup, checked August 31, 2026. The official guide describes Windows notation and directs macOS users to replace Control with Command and Alt with Option. Canvas switching retains the separately published Control-based Mac chords.", sourceUrl: "https://help.clip-studio.com/en-us/manual_en/780_shortcuts/Menu_Shortcuts.htm", sourceLabel: "Clip Studio Paint 5.0 menu shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "files-and-settings", diff --git a/lib/shortcuts/apps/clockify.ts b/lib/shortcuts/apps/clockify.ts index 35277e54..426e8538 100644 --- a/lib/shortcuts/apps/clockify.ts +++ b/lib/shortcuts/apps/clockify.ts @@ -3,7 +3,7 @@ import type { AppShortcuts, Shortcut } from "../types.ts"; const web = (id: string, action: string, keys: string[], note?: string): Shortcut => ({ id, action, - keys: (["windows", "macos", "linux"] as const).map((platform) => ({ platform, keys })), + keys: (["windows", "macos"] as const).map((platform) => ({ platform, keys })), ...(note ? { note } : {}), }); const mac = (id: string, action: string, keys: string[]): Shortcut => ({ id, action, keys: [{ platform: "macos", keys }] }); @@ -12,12 +12,12 @@ export const clockify: AppShortcuts = { slug: "clockify", name: "Clockify", category: "Time tracking", - platforms: ["windows", "macos", "linux"], + platforms: ["windows", "macos"], versionContext: - "Current Clockify web and Mac desktop documentation checked August 31, 2026. Favorite-entry number keys require the workspace feature to be enabled and entries pinned. User-configurable global shortcuts are not listed as defaults.", + "Current Clockify web and Mac desktop documentation checked November 1, 2026. Favorite-entry number keys require the workspace feature to be enabled and entries pinned. User-configurable global shortcuts are not listed as defaults.", sourceUrl: "https://clockify.me/help/track-time-and-expenses/creating-a-time-entry", sourceLabel: "Clockify time-entry keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "web-time-tracker", @@ -28,7 +28,16 @@ export const clockify: AppShortcuts = { web("continue", "Continue the latest time entry", ["C"]), web("stop", "Stop the current timer", ["S"]), web("manual", "Switch to manual time-entry mode", ["M"]), - web("favorite", "Quick-start favorite time entry 1 through 5", ["1-5"], "Requires Favorite entries to be enabled and pinned in the favorites bar."), + { + id: "favorite", + action: "Quick-start a favorite time entry", + keys: (["windows", "macos"] as const).map((platform) => ({ + platform, + keys: ["1"], + alternatives: [["2"], ["3"], ["4"], ["5"]], + })), + note: "Requires Favorite entries to be enabled and pinned in the favorites bar.", + }, ], }, { diff --git a/lib/shortcuts/apps/coda.ts b/lib/shortcuts/apps/coda.ts index 55d639ea..f97c0f5d 100644 --- a/lib/shortcuts/apps/coda.ts +++ b/lib/shortcuts/apps/coda.ts @@ -18,7 +18,7 @@ export const coda: AppShortcuts = { "Current complete Coda Mac and PC shortcut reference checked August 31, 2026. Coda labels Windows shortcuts as PC. Identical keys are retained where its platform tables agree, including Control-based indentation on Mac.", sourceUrl: "https://help.coda.io/hc/en-us/articles/39555894316173-Keyboard-shortcuts", sourceLabel: "Coda keyboard shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "general", diff --git a/lib/shortcuts/apps/confluence.ts b/lib/shortcuts/apps/confluence.ts index 261937ad..e1812ad2 100644 --- a/lib/shortcuts/apps/confluence.ts +++ b/lib/shortcuts/apps/confluence.ts @@ -18,7 +18,7 @@ export const confluence: AppShortcuts = { "Current Confluence Cloud reference checked August 31, 2026. Windows and Mac columns are preserved literally, including Control-based Mac commands and the Option-based text-formatting rows published by Atlassian. Confluence Data Center and the legacy editor are outside scope.", sourceUrl: "https://support.atlassian.com/confluence-cloud/docs/keyboard-shortcuts-markdown-and-autocomplete/", sourceLabel: "Atlassian Confluence Cloud keyboard shortcuts and autocomplete", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "global", diff --git a/lib/shortcuts/apps/craft.ts b/lib/shortcuts/apps/craft.ts index 0109c4e4..c5a6dac9 100644 --- a/lib/shortcuts/apps/craft.ts +++ b/lib/shortcuts/apps/craft.ts @@ -43,7 +43,7 @@ export const craft: AppShortcuts = { "Current Craft keyboard shortcut reference checked September 1, 2026. Craft publishes separate Mac and Windows lists; users can also add app-specific shortcuts through macOS settings.", sourceUrl: "https://support.craft.do/en/introduction/shortcuts", sourceLabel: "Craft keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "general", diff --git a/lib/shortcuts/apps/cubase.ts b/lib/shortcuts/apps/cubase.ts index deaeb173..9f01efbe 100644 --- a/lib/shortcuts/apps/cubase.ts +++ b/lib/shortcuts/apps/cubase.ts @@ -12,7 +12,7 @@ export const cubase: AppShortcuts = { "Cubase Pro 15.0 key commands checked September 1, 2026. Steinberg documents Windows and macOS defaults and allows users to customize or reset the key command preset.", sourceUrl: "https://www.steinberg.help/r/cubase-pro/15.0/en/cubase_nuendo/topics/key_commands/key_commands_default_c.html", sourceLabel: "official Cubase Pro 15.0 default key commands", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "files-and-editing", diff --git a/lib/shortcuts/apps/dbeaver.ts b/lib/shortcuts/apps/dbeaver.ts index f651fb06..c0d47857 100644 --- a/lib/shortcuts/apps/dbeaver.ts +++ b/lib/shortcuts/apps/dbeaver.ts @@ -41,7 +41,7 @@ export const dbeaver: AppShortcuts = { "Current DBeaver shortcut wiki edited August 18, 2026 and checked September 1, 2026. The wiki lists Windows/Linux and macOS columns, and notes that users can change bindings or select the DBeaver Keyboard Only scheme.", sourceUrl: "https://github.com/dbeaver/dbeaver/wiki/Shortcuts", sourceLabel: "DBeaver shortcut reference", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "connection", diff --git a/lib/shortcuts/apps/deepl.ts b/lib/shortcuts/apps/deepl.ts index 04f24012..51072411 100644 --- a/lib/shortcuts/apps/deepl.ts +++ b/lib/shortcuts/apps/deepl.ts @@ -16,7 +16,7 @@ export const deepl: AppShortcuts = { "Current DeepL desktop-app defaults checked August 31, 2026. Translation and improvement shortcuts can be disabled or customized in app settings, so these entries describe the published defaults.", sourceUrl: "https://support.deepl.com/hc/en-us/articles/360020613059-Use-DeepL-shortcuts-for-desktop-apps", sourceLabel: "DeepL desktop app shortcuts", - lastVerified: "2026-08-31", + lastVerified: "2026-11-01", groups: [ { id: "selected-text-and-app", diff --git a/lib/shortcuts/apps/descript.ts b/lib/shortcuts/apps/descript.ts index ea3c683f..46b16c8f 100644 --- a/lib/shortcuts/apps/descript.ts +++ b/lib/shortcuts/apps/descript.ts @@ -28,7 +28,7 @@ export const descript: AppShortcuts = { "Current Descript blog and Help Center pages checked September 1, 2026. Descript's defaults apply to its Mac and Windows editor; focus on the script editor or timeline where noted.", sourceUrl: "https://www.descript.com/blog/article/how-to-use-descript", sourceLabel: "Descript keyboard shortcuts guide", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "tools", diff --git a/lib/shortcuts/apps/eclipse.ts b/lib/shortcuts/apps/eclipse.ts index cd5143d9..98f921c0 100644 --- a/lib/shortcuts/apps/eclipse.ts +++ b/lib/shortcuts/apps/eclipse.ts @@ -27,7 +27,7 @@ export const eclipse: AppShortcuts = { "Eclipse IDE 2026-06 (4.40) documentation and the current Eclipse IDE keybindings card checked September 1, 2026. The entries describe the Default scheme where the documentation provides a default and retain Eclipse's platform-specific Command mappings.", sourceUrl: "https://help.eclipse.org/latest/topic/org.eclipse.platform.doc.user/concepts/concepts-keys.htm", sourceLabel: "Eclipse Platform keys and keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "general", diff --git a/lib/shortcuts/apps/evernote.ts b/lib/shortcuts/apps/evernote.ts index d8536360..43338493 100644 --- a/lib/shortcuts/apps/evernote.ts +++ b/lib/shortcuts/apps/evernote.ts @@ -35,7 +35,7 @@ export const evernote: AppShortcuts = { "Current Evernote keyboard shortcut reference updated July 31, 2026 and checked September 1, 2026. Some shortcuts can be customized or disabled from Evernote's shortcut drawer.", sourceUrl: "https://help.evernote.com/hc/en-us/articles/34296687388307-Keyboard-shortcuts", sourceLabel: "Evernote keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "global", diff --git a/lib/shortcuts/apps/excalidraw.ts b/lib/shortcuts/apps/excalidraw.ts index e9a3519c..fd8a81ee 100644 --- a/lib/shortcuts/apps/excalidraw.ts +++ b/lib/shortcuts/apps/excalidraw.ts @@ -50,7 +50,7 @@ export const excalidraw: AppShortcuts = { "The current Excalidraw shortcut map in the official repository was checked September 1, 2026. Excalidraw can be embedded in other products, where host shortcuts or browser behavior may take precedence.", sourceUrl: "https://raw.githubusercontent.com/excalidraw/excalidraw/master/packages/excalidraw/actions/shortcuts.ts", sourceLabel: "Excalidraw shortcut map", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "file-and-command", diff --git a/lib/shortcuts/apps/fantastical.ts b/lib/shortcuts/apps/fantastical.ts index 5680a4a6..787367b2 100644 --- a/lib/shortcuts/apps/fantastical.ts +++ b/lib/shortcuts/apps/fantastical.ts @@ -35,7 +35,7 @@ export const fantastical: AppShortcuts = { "Current Fantastical keyboard shortcut pages checked September 1, 2026. The Mac page lists more commands than the Windows page, and the global open shortcut can be customized.", sourceUrl: "https://flexibits.com/fantastical/help/keyboard-shortcuts", sourceLabel: "Fantastical keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "application", diff --git a/lib/shortcuts/apps/figjam.ts b/lib/shortcuts/apps/figjam.ts index 8e35613d..08fd07a9 100644 --- a/lib/shortcuts/apps/figjam.ts +++ b/lib/shortcuts/apps/figjam.ts @@ -36,7 +36,7 @@ export const figjam: AppShortcuts = { "Current Figma Help Center FigJam pages checked September 1, 2026. The shortcuts apply to the FigJam board and may require a board, text object, table, or selection to have focus.", sourceUrl: "https://help.figma.com/hc/en-us/articles/1500004292221-Select-move-and-order-objects-in-FigJam", sourceLabel: "Figma Help Center FigJam shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "selection-and-order", diff --git a/lib/shortcuts/apps/filezilla.ts b/lib/shortcuts/apps/filezilla.ts index d8d26e99..cdd344c1 100644 --- a/lib/shortcuts/apps/filezilla.ts +++ b/lib/shortcuts/apps/filezilla.ts @@ -18,7 +18,7 @@ export const filezilla: AppShortcuts = { "Current FileZilla Wiki keyboard shortcut page checked September 1, 2026. The wiki publishes one desktop shortcut set without platform columns, so those values are retained across Windows, macOS, and Linux.", sourceUrl: "https://wiki.filezilla-project.org/Keyboard_shortcuts", sourceLabel: "FileZilla keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "views-and-files", diff --git a/lib/shortcuts/apps/firefox.ts b/lib/shortcuts/apps/firefox.ts index 2b270670..49440697 100644 --- a/lib/shortcuts/apps/firefox.ts +++ b/lib/shortcuts/apps/firefox.ts @@ -12,7 +12,7 @@ export const firefox: AppShortcuts = { "Mozilla's keyboard reference checked August 26, 2026. Windows and Linux generally share the Ctrl bindings, while macOS uses Command or Option. A few backspace, tab-order, and link-opening shortcuts depend on Firefox preferences.", sourceUrl: "https://support.mozilla.org/en-US/kb/keyboard-shortcuts-perform-firefox-tasks-quickly", sourceLabel: "official Firefox keyboard shortcuts", - lastVerified: "2026-08-26", + lastVerified: "2026-11-01", groups: [ { id: "navigation", diff --git a/lib/shortcuts/apps/fl-studio.ts b/lib/shortcuts/apps/fl-studio.ts index acba6b26..f83c7238 100644 --- a/lib/shortcuts/apps/fl-studio.ts +++ b/lib/shortcuts/apps/fl-studio.ts @@ -42,7 +42,7 @@ export const flStudio: AppShortcuts = { "Current FL Studio online manual keyboard shortcut page checked September 1, 2026. The manual says shortcuts depend on the active window or plugin and that its list is not exhaustive.", sourceUrl: "https://www.image-line.com/fl-studio-learning/fl-studio-online-manual/html/basics_shortcuts.htm", sourceLabel: "FL Studio keyboard and mouse shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "global", diff --git a/lib/shortcuts/apps/freshdesk.ts b/lib/shortcuts/apps/freshdesk.ts index a15538b3..b6afe3e5 100644 --- a/lib/shortcuts/apps/freshdesk.ts +++ b/lib/shortcuts/apps/freshdesk.ts @@ -12,7 +12,7 @@ const freshdesk: AppShortcuts = { "The current Freshdesk support reference covers Freshdesk Omni accounts and notes that accounts created before December 2025 may see a different shortcut set. Shortcuts must be enabled from Profile > Keyboard Shortcuts.", sourceUrl: "https://support.freshdesk.com/support/solutions/articles/186758-keyboard-shortcuts-in-freshdesk", sourceLabel: "official Freshdesk keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "global-navigation", diff --git a/lib/shortcuts/apps/front.ts b/lib/shortcuts/apps/front.ts index ab6bf3ea..d6d24f2a 100644 --- a/lib/shortcuts/apps/front.ts +++ b/lib/shortcuts/apps/front.ts @@ -3,16 +3,26 @@ import type { AppShortcuts } from "../types.ts"; const platforms = ["windows", "macos"] as const; +const paired = ( + windows: string[], + macos: string[], + windowsAlternatives: string[][] = [], + macosAlternatives: string[][] = [], +) => [ + { platform: "windows" as const, keys: windows, ...(windowsAlternatives.length ? { alternatives: windowsAlternatives } : {}) }, + { platform: "macos" as const, keys: macos, ...(macosAlternatives.length ? { alternatives: macosAlternatives } : {}) }, +]; + const front: AppShortcuts = { slug: "front", name: "Front", category: "Shared inbox", platforms: [...platforms], versionContext: - "Front publishes four selectable shortcut sets. This record uses the Front set. Personal preferences, the browser versus desktop app, and single-key settings can change what is active. Windows uses Control where the Mac reference uses Command, and Alt where it uses Option.", + "Front publishes four selectable shortcut sets. This record uses the Front set. Personal preferences and the browser versus desktop app can change what is active; the desktop app excludes single-key alternatives by default. Windows uses Control where the Mac reference uses Command, and Alt where it uses Option.", sourceUrl: "https://help.front.com/en/articles/2189", sourceLabel: "official Front keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "conversation-navigation", @@ -40,18 +50,17 @@ const front: AppShortcuts = { { id: "search", action: "Search Front", - keys: combo(platforms, ["Ctrl", "Shift", "F"], ["Cmd", "Shift", "F"]), - note: "Shift+F is also listed by Front for this action in the Front set.", + keys: paired(["Ctrl", "Shift", "F"], ["Cmd", "Shift", "F"], [["Shift", "F"]], [["Shift", "F"]]), }, { id: "back", action: "Go back", keys: combo(platforms, ["Ctrl", "["] , ["Cmd", "["]) }, { id: "forward-history", action: "Go forward", keys: combo(platforms, ["Ctrl", "]"], ["Cmd", "]"]) }, - { id: "quick-switcher", action: "Open the quick switcher", keys: combo(platforms, ["Ctrl", "K"], ["Cmd", "K"]) }, - { id: "nav-inbox", action: "Open Inbox", keys: combo(platforms, ["Ctrl", "1"], ["Cmd", "1"]) }, - { id: "nav-calendar", action: "Open Calendar", keys: combo(platforms, ["Ctrl", "2"], ["Cmd", "2"]) }, - { id: "nav-contacts", action: "Open Contacts", keys: combo(platforms, ["Ctrl", "3"], ["Cmd", "3"]) }, - { id: "nav-analytics", action: "Open Analytics", keys: combo(platforms, ["Ctrl", "4"], ["Cmd", "4"]) }, - { id: "nav-help-center", action: "Open Help Center", keys: combo(platforms, ["Ctrl", "5"], ["Cmd", "5"]) }, - { id: "nav-sequences", action: "Open Sequences", keys: combo(platforms, ["Ctrl", "6"], ["Cmd", "6"]) }, + { id: "quick-switcher", action: "Open the quick switcher", keys: paired(["Ctrl", "K"], ["Cmd", "K"], [["Ctrl", "Shift", "P"], ["Ctrl", "Alt", "Shift", "P"]], [["Cmd", "Shift", "P"], ["Cmd", "Option", "Shift", "P"]]) }, + { id: "nav-inbox", action: "Open Inbox", keys: paired(["Ctrl", "1"], ["Cmd", "1"], [["Alt", "1"]], [["Ctrl", "1"]]) }, + { id: "nav-calendar", action: "Open Calendar", keys: paired(["Ctrl", "2"], ["Cmd", "2"], [["Alt", "2"]], [["Ctrl", "2"]]) }, + { id: "nav-contacts", action: "Open Contacts", keys: paired(["Ctrl", "3"], ["Cmd", "3"], [["Alt", "3"]], [["Ctrl", "3"]]) }, + { id: "nav-analytics", action: "Open Analytics", keys: paired(["Ctrl", "4"], ["Cmd", "4"], [["Alt", "4"]], [["Ctrl", "4"]]) }, + { id: "nav-help-center", action: "Open Help Center", keys: paired(["Ctrl", "5"], ["Cmd", "5"], [["Alt", "5"]], [["Ctrl", "5"]]) }, + { id: "nav-sequences", action: "Open Sequences", keys: paired(["Ctrl", "6"], ["Cmd", "6"], [["Alt", "6"]], [["Ctrl", "6"]]) }, ], }, { @@ -63,11 +72,10 @@ const front: AppShortcuts = { { id: "reply-new-window", action: "Reply in a new window", - keys: combo(platforms, ["Alt", "R"], ["Option", "R"]), - note: "The Mac set also lists Command+Option+R and the Windows set Control+Alt+R.", + keys: paired(["Alt", "R"], ["Option", "R"], [["Ctrl", "Alt", "R"]], [["Cmd", "Option", "R"]]), }, { id: "reply-all", action: "Reply all", keys: combo(platforms, ["Shift", "R"]) }, - { id: "forward", action: "Forward the message", keys: combo(platforms, ["Ctrl", "J"], ["Cmd", "J"]) }, + { id: "forward", action: "Forward the message", keys: paired(["Ctrl", "J"], ["Cmd", "J"], [["J"]], [["J"]]) }, { id: "send-archive", action: "Send and archive", keys: combo(platforms, ["Ctrl", "Enter"], ["Cmd", "Enter"]) }, { id: "send-without-archive", @@ -90,16 +98,16 @@ const front: AppShortcuts = { title: "Conversation actions", intro: "Archive, assign, tag, snooze, and otherwise manage the selected conversation from the inbox.", shortcuts: [ - { id: "archive", action: "Archive or unarchive", keys: combo(platforms, ["Ctrl", "E"], ["Cmd", "E"]) }, - { id: "tag", action: "Add or remove a tag", keys: combo(platforms, ["Ctrl", "T"], ["Cmd", "T"]) }, - { id: "assign-self", action: "Assign to yourself", keys: combo(platforms, ["Ctrl", "Shift", "A"], ["Cmd", "Shift", "A"]) }, - { id: "assign-teammate", action: "Assign to a teammate", keys: combo(platforms, ["Ctrl", "Shift", "G"], ["Cmd", "Shift", "G"]) }, - { id: "move", action: "Move the conversation", keys: combo(platforms, ["Ctrl", "Shift", "M"], ["Cmd", "Shift", "M"]) }, + { id: "archive", action: "Archive or unarchive", keys: paired(["Ctrl", "E"], ["Cmd", "E"], [["E"]], [["E"]]) }, + { id: "tag", action: "Add or remove a tag", keys: paired(["Ctrl", "T"], ["Cmd", "T"], [["T"]], [["T"]]) }, + { id: "assign-self", action: "Assign to yourself", keys: paired(["Ctrl", "Shift", "A"], ["Cmd", "Shift", "A"], [["Shift", "A"]], [["Shift", "A"]]) }, + { id: "assign-teammate", action: "Assign to a teammate", keys: paired(["Ctrl", "Shift", "G"], ["Cmd", "Shift", "G"], [["Shift", "G"]], [["Shift", "G"]]) }, + { id: "move", action: "Move the conversation", keys: paired(["Ctrl", "Shift", "M"], ["Cmd", "Shift", "M"], [["Shift", "M"]], [["Shift", "M"]]) }, { id: "trash", action: "Move to trash", keys: combo(platforms, ["Delete"]) }, - { id: "spam", action: "Mark as spam", keys: combo(platforms, ["Ctrl", "Shift", "J"], ["Cmd", "Shift", "J"]) }, - { id: "snooze", action: "Snooze the conversation", keys: combo(platforms, ["Ctrl", "S"], ["Cmd", "S"]) }, - { id: "subscribe", action: "Subscribe or unsubscribe", keys: combo(platforms, ["Ctrl", "Shift", "U"], ["Cmd", "Shift", "U"]) }, - { id: "conversation-info", action: "Show conversation information", keys: combo(platforms, ["Ctrl", "Shift", "I"], ["Cmd", "Shift", "I"]) }, + { id: "spam", action: "Mark as spam", keys: paired(["Ctrl", "Shift", "J"], ["Cmd", "Shift", "J"], [["Shift", "J"]], [["Shift", "J"]]) }, + { id: "snooze", action: "Snooze the conversation", keys: paired(["Ctrl", "S"], ["Cmd", "S"], [["S"]], [["S"]]) }, + { id: "subscribe", action: "Subscribe or unsubscribe", keys: paired(["Ctrl", "Shift", "U"], ["Cmd", "Shift", "U"], [["Shift", "U"]], [["Shift", "U"]]) }, + { id: "conversation-info", action: "Show conversation information", keys: paired(["Ctrl", "Shift", "I"], ["Cmd", "Shift", "I"], [["Shift", "I"]], [["Shift", "I"]]) }, { id: "star", action: "Star the conversation", keys: combo(platforms, ["Alt", "S"], ["Option", "S"]) }, { id: "expand-messages", action: "Expand messages", keys: combo(platforms, [";"]) }, { id: "collapse-messages", action: "Collapse messages", keys: combo(platforms, ["Shift", ";"]) }, diff --git a/lib/shortcuts/apps/fusion-360.ts b/lib/shortcuts/apps/fusion-360.ts index efe942ee..0b377af3 100644 --- a/lib/shortcuts/apps/fusion-360.ts +++ b/lib/shortcuts/apps/fusion-360.ts @@ -12,7 +12,7 @@ const fusion360: AppShortcuts = { "Autodesk's current Fusion keyboard reference labels Windows and macOS separately. Command replaces Control on macOS for the shared document and modeling commands, while Autodesk-specific Option and Command combinations remain platform-specific.", sourceUrl: "https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-F0491540-0324-470A-B651-2238D0EFAC30", sourceLabel: "official Autodesk Fusion keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "documents-and-layout", diff --git a/lib/shortcuts/apps/gimp.ts b/lib/shortcuts/apps/gimp.ts index 2163f074..da8b6203 100644 --- a/lib/shortcuts/apps/gimp.ts +++ b/lib/shortcuts/apps/gimp.ts @@ -12,7 +12,7 @@ const gimp: AppShortcuts = { "The official GIMP one-page quick reference publishes one set of desktop bindings without operating-system columns. The listed keys are kept as published on Windows, macOS, and Linux; individual installations may differ.", sourceUrl: "https://docs.gimp.org/quickreference/gimp-keys-en.pdf", sourceLabel: "official GIMP keyboard shortcuts quick reference", - lastVerified: "2026-10-10", + lastVerified: "2026-11-01", groups: [ { id: "toolbox", @@ -24,7 +24,7 @@ const gimp: AppShortcuts = { { id: "rectangle-select", action: "Rectangle Select", keys: combo(platforms, ["R"]) }, { id: "ellipse-select", action: "Ellipse Select", keys: combo(platforms, ["E"]) }, { id: "free-select", action: "Free Select", keys: combo(platforms, ["F"]) }, - { id: "fuzzy-select", action: "Fuzzy Select", keys: combo(platforms, ["U"]) }, + { id: "fuzzy-select", action: "Fuzzy Select", keys: combo(platforms, ["Z"]) }, { id: "select-by-color", action: "Select by Color", keys: combo(platforms, ["Shift", "O"]) }, { id: "scissors", action: "Scissors Select", keys: combo(platforms, ["I"]) }, { id: "paths", action: "Paths", keys: combo(platforms, ["B"]) }, @@ -38,7 +38,7 @@ const gimp: AppShortcuts = { { id: "flip", action: "Flip", keys: combo(platforms, ["Shift", "F"]) }, { id: "text", action: "Text", keys: combo(platforms, ["T"]) }, { id: "bucket-fill", action: "Bucket Fill", keys: combo(platforms, ["Shift", "B"]) }, - { id: "blend", action: "Blend", keys: combo(platforms, ["G"]) }, + { id: "blend", action: "Blend", keys: combo(platforms, ["L"]) }, { id: "pencil", action: "Pencil", keys: combo(platforms, ["N"]) }, { id: "paintbrush", action: "Paintbrush", keys: combo(platforms, ["P"]) }, { id: "eraser", action: "Eraser", keys: combo(platforms, ["Shift", "E"]) }, diff --git a/lib/shortcuts/apps/github-copilot.ts b/lib/shortcuts/apps/github-copilot.ts index 78d2ef45..6e37b10e 100644 --- a/lib/shortcuts/apps/github-copilot.ts +++ b/lib/shortcuts/apps/github-copilot.ts @@ -12,7 +12,7 @@ const githubCopilot: AppShortcuts = { "GitHub's current shortcut page separates Visual Studio Code, JetBrains, Visual Studio, Eclipse, Vim/Neovim, and Xcode. This record uses the Visual Studio Code defaults for Windows, macOS, and Linux. Host keymaps and user rebinding can change the active commands.", sourceUrl: "https://docs.github.com/en/copilot/reference/keyboard-shortcuts?tool=vscode", sourceLabel: "official GitHub Copilot IDE keyboard shortcuts", - lastVerified: "2026-10-10", + lastVerified: "2026-11-01", groups: [ { id: "inline-suggestions", diff --git a/lib/shortcuts/apps/github-desktop.ts b/lib/shortcuts/apps/github-desktop.ts index c8453981..8f0fd26f 100644 --- a/lib/shortcuts/apps/github-desktop.ts +++ b/lib/shortcuts/apps/github-desktop.ts @@ -12,7 +12,7 @@ const githubDesktop: AppShortcuts = { "GitHub publishes separate macOS and Windows tables. This record covers the desktop application, not browser shortcuts or terminal commands. Windows uses Control where macOS uses Command, with the platform-specific full-screen and developer-tool commands preserved.", sourceUrl: "https://docs.github.com/en/desktop/overview/github-desktop-keyboard-shortcuts", sourceLabel: "official GitHub Desktop keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "application", diff --git a/lib/shortcuts/apps/github.ts b/lib/shortcuts/apps/github.ts index 75df01fc..fd7f5d28 100644 --- a/lib/shortcuts/apps/github.ts +++ b/lib/shortcuts/apps/github.ts @@ -12,7 +12,7 @@ export const github: AppShortcuts = { "Shortcuts documented on docs.github.com in August 2026. Character key shortcuts can be disabled in accessibility settings, and the GitHub Command Palette is in public preview and disabled by default.", sourceUrl: "https://docs.github.com/en/get-started/accessibility/keyboard-shortcuts", sourceLabel: "official keyboard shortcuts", - lastVerified: "2026-08-26", + lastVerified: "2026-11-01", groups: [ { id: "site-navigation", diff --git a/lib/shortcuts/apps/gitkraken.ts b/lib/shortcuts/apps/gitkraken.ts index f1707ed1..fffd827d 100644 --- a/lib/shortcuts/apps/gitkraken.ts +++ b/lib/shortcuts/apps/gitkraken.ts @@ -12,7 +12,7 @@ const gitkraken: AppShortcuts = { "The current GitKraken Desktop reference documents Command for macOS and Control for Windows and Linux. Some keys act on the commit graph or the panel under focus, so the group notes call out those contexts.", sourceUrl: "https://help.gitkraken.com/gitkraken-desktop/keyboard-shortcuts/", sourceLabel: "official GitKraken Desktop keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "repository-work", @@ -20,7 +20,7 @@ const gitkraken: AppShortcuts = { intro: "Fetch, stage, commit, and open repository tools while keeping the current GitKraken repository in focus.", shortcuts: [ { id: "create-branch", action: "Create a branch", keys: combo(platforms, ["Ctrl", "B"], ["Cmd", "B"]) }, - { id: "fetch-all", action: "Fetch all", keys: combo(platforms, ["L"]) }, + { id: "fetch-all", action: "Fetch all", keys: combo(platforms, ["Ctrl", "L"], ["Cmd", "L"]) }, { id: "stage-current", action: "Stage the current file", keys: combo(platforms, ["S"]) }, { id: "stage-all", action: "Stage all files", keys: combo(platforms, ["Ctrl", "Shift", "S"], ["Cmd", "Shift", "S"]) }, { id: "unstage-current", action: "Unstage the current file", keys: combo(platforms, ["U"]) }, @@ -38,12 +38,12 @@ const gitkraken: AppShortcuts = { title: "Graph navigation", intro: "Move through commits, branches, and panels with the graph navigation keys used by GitKraken Desktop.", shortcuts: [ - { id: "previous-item", action: "Move to the previous graph item", keys: combo(platforms, ["J"]), note: "Down is also documented for this movement." }, - { id: "next-item", action: "Move to the next graph item", keys: combo(platforms, ["K"]), note: "Up is also documented for this movement." }, - { id: "move-right", action: "Move right in the graph", keys: combo(platforms, ["L"]), note: "Right is also documented for this movement." }, - { id: "move-left", action: "Move left in the graph", keys: combo(platforms, ["H"]), note: "Left is also documented for this movement." }, - { id: "next-branch", action: "Move to the next branch", keys: combo(platforms, ["Shift", "Down"], ["Shift", "J"]) }, - { id: "previous-branch", action: "Move to the previous branch", keys: combo(platforms, ["Shift", "Up"], ["Shift", "K"]) }, + { id: "previous-item", action: "Move to the previous graph item", keys: platforms.map((platform) => ({ platform, keys: ["J"], alternatives: [["Down"]] })) }, + { id: "next-item", action: "Move to the next graph item", keys: platforms.map((platform) => ({ platform, keys: ["K"], alternatives: [["Up"]] })) }, + { id: "move-right", action: "Move right in the graph", keys: platforms.map((platform) => ({ platform, keys: ["L"], alternatives: [["Right"]] })) }, + { id: "move-left", action: "Move left in the graph", keys: platforms.map((platform) => ({ platform, keys: ["H"], alternatives: [["Left"]] })) }, + { id: "next-branch", action: "Move to the next branch", keys: platforms.map((platform) => ({ platform, keys: ["Shift", "Down"], alternatives: [["Shift", "J"]] })) }, + { id: "previous-branch", action: "Move to the previous branch", keys: platforms.map((platform) => ({ platform, keys: ["Shift", "Up"], alternatives: [["Shift", "K"]] })) }, { id: "first-commit", action: "Move to the first commit", diff --git a/lib/shortcuts/apps/gitlab.ts b/lib/shortcuts/apps/gitlab.ts index a9325a49..367a94de 100644 --- a/lib/shortcuts/apps/gitlab.ts +++ b/lib/shortcuts/apps/gitlab.ts @@ -1,7 +1,7 @@ import { combo } from "../build.ts"; import type { AppShortcuts } from "../types.ts"; -const platforms = ["windows", "macos", "linux"] as const; +const platforms = ["windows", "macos"] as const; const gitlab: AppShortcuts = { slug: "gitlab", @@ -9,10 +9,10 @@ const gitlab: AppShortcuts = { category: "Code hosting", platforms: [...platforms], versionContext: - "GitLab publishes one web shortcut reference with macOS modifier differences where they matter. Windows and Linux share the Control-based bindings in the source. Some commands depend on the current project, issue, or merge request view.", + "GitLab publishes one web shortcut reference with macOS modifier differences where they matter. Some commands depend on the current project, issue, or merge request view.", sourceUrl: "https://docs.gitlab.com/user/shortcuts/", sourceLabel: "official GitLab keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "global-navigation", @@ -32,9 +32,9 @@ const gitlab: AppShortcuts = { { id: "merge-requests", action: "Open Merge Requests", keys: combo(platforms, ["Shift", "M"]) }, { id: "review-requests", action: "Open Review Requests", keys: combo(platforms, ["Shift", "R"]) }, { id: "todo", action: "Open To-Do", keys: combo(platforms, ["Shift", "T"]) }, - { id: "performance-bar", action: "Open the performance bar", keys: combo(platforms, ["P", "then", "B"]) }, + { id: "performance-bar", action: "Open the performance bar", keys: combo(platforms, ["P", "B"]) }, { id: "hide-overlay", action: "Hide the current overlay", keys: combo(platforms, ["Esc"]) }, - { id: "toggle-gitlab-next", action: "Toggle GitLab Next", keys: combo(platforms, ["G", "then", "X"]) }, + { id: "toggle-gitlab-next", action: "Toggle GitLab Next", keys: combo(platforms, ["G", "X"]) }, { id: "web-ide", action: "Open the Web IDE", keys: combo(platforms, ["."]) }, { id: "duo-chat", action: "Open GitLab Duo Chat", keys: combo(platforms, ["D"]) }, ], @@ -44,24 +44,24 @@ const gitlab: AppShortcuts = { title: "Project navigation", intro: "Jump between project overview, files, commits, pipelines, environments, and collaboration areas with G routes.", shortcuts: [ - { id: "project-overview", action: "Open project overview", keys: combo(platforms, ["G", "then", "O"]) }, - { id: "project-activity", action: "Open project activity", keys: combo(platforms, ["G", "then", "V"]) }, - { id: "releases", action: "Open releases", keys: combo(platforms, ["G", "then", "R"]) }, - { id: "files", action: "Open project files", keys: combo(platforms, ["G", "then", "F"]) }, + { id: "project-overview", action: "Open project overview", keys: combo(platforms, ["G", "O"]) }, + { id: "project-activity", action: "Open project activity", keys: combo(platforms, ["G", "V"]) }, + { id: "releases", action: "Open releases", keys: combo(platforms, ["G", "R"]) }, + { id: "files", action: "Open project files", keys: combo(platforms, ["G", "F"]) }, { id: "find-file", action: "Search project files", keys: combo(platforms, ["T"]) }, - { id: "commits", action: "Open commits", keys: combo(platforms, ["G", "then", "C"]) }, - { id: "graph", action: "Open the repository graph", keys: combo(platforms, ["G", "then", "N"]) }, - { id: "analytics", action: "Open analytics", keys: combo(platforms, ["G", "then", "D"]) }, - { id: "work-items", action: "Open work items", keys: combo(platforms, ["G", "then", "I"]) }, + { id: "commits", action: "Open commits", keys: combo(platforms, ["G", "C"]) }, + { id: "graph", action: "Open the repository graph", keys: combo(platforms, ["G", "N"]) }, + { id: "analytics", action: "Open analytics", keys: combo(platforms, ["G", "D"]) }, + { id: "work-items", action: "Open work items", keys: combo(platforms, ["G", "I"]) }, { id: "new-issue", action: "Create a new issue", keys: combo(platforms, ["I"]) }, - { id: "boards", action: "Open issue boards", keys: combo(platforms, ["G", "then", "B"]) }, - { id: "project-merge-requests", action: "Open project merge requests", keys: combo(platforms, ["G", "then", "M"]) }, - { id: "pipelines", action: "Open pipelines", keys: combo(platforms, ["G", "then", "P"]) }, - { id: "jobs", action: "Open jobs", keys: combo(platforms, ["G", "then", "J"]) }, - { id: "environments", action: "Open environments", keys: combo(platforms, ["G", "then", "E"]) }, - { id: "clusters", action: "Open clusters", keys: combo(platforms, ["G", "then", "K"]) }, - { id: "project-snippets", action: "Open project snippets", keys: combo(platforms, ["G", "then", "S"]) }, - { id: "wiki", action: "Open the project wiki", keys: combo(platforms, ["G", "then", "W"]) }, + { id: "boards", action: "Open issue boards", keys: combo(platforms, ["G", "B"]) }, + { id: "project-merge-requests", action: "Open project merge requests", keys: combo(platforms, ["G", "M"]) }, + { id: "pipelines", action: "Open pipelines", keys: combo(platforms, ["G", "P"]) }, + { id: "jobs", action: "Open jobs", keys: combo(platforms, ["G", "J"]) }, + { id: "environments", action: "Open environments", keys: combo(platforms, ["G", "E"]) }, + { id: "clusters", action: "Open clusters", keys: combo(platforms, ["G", "K"]) }, + { id: "project-snippets", action: "Open project snippets", keys: combo(platforms, ["G", "S"]) }, + { id: "wiki", action: "Open the project wiki", keys: combo(platforms, ["G", "W"]) }, ], }, { @@ -73,7 +73,7 @@ const gitlab: AppShortcuts = { { id: "assign", action: "Change the assignee", keys: combo(platforms, ["A"]) }, { id: "milestone", action: "Change the milestone", keys: combo(platforms, ["M"]) }, { id: "label", action: "Change labels", keys: combo(platforms, ["L"]) }, - { id: "copy-reference", action: "Copy the issue reference", keys: combo(platforms, ["C", "then", "R"]) }, + { id: "copy-reference", action: "Copy the issue reference", keys: combo(platforms, ["C", "R"]) }, { id: "comment", action: "Focus the comment box", keys: combo(platforms, ["R"]) }, { id: "previous-design", action: "Show the previous design item", keys: combo(platforms, ["Left"]) }, { id: "next-design", action: "Show the next design item", keys: combo(platforms, ["Right"]) }, diff --git a/lib/shortcuts/apps/gmail.ts b/lib/shortcuts/apps/gmail.ts index 45ce11af..b82e1aa4 100644 --- a/lib/shortcuts/apps/gmail.ts +++ b/lib/shortcuts/apps/gmail.ts @@ -1,7 +1,7 @@ import { combo, perPlatform } from "../build.ts"; import type { AppShortcuts } from "../types.ts"; -const platforms = ["windows", "macos", "linux"] as const; +const platforms = ["windows", "macos"] as const; export const gmail: AppShortcuts = { slug: "gmail", @@ -12,7 +12,7 @@ export const gmail: AppShortcuts = { "Google's official Gmail Help page checked in August 2026. Gmail keyboard shortcuts must be turned on in Settings, and Google notes that language and keyboard layout can affect support. Gmail also lets users customize the single-key actions after enabling custom shortcuts.", sourceUrl: "https://support.google.com/mail/answer/6594", sourceLabel: "official Gmail keyboard shortcuts", - lastVerified: "2026-08-26", + lastVerified: "2026-11-01", groups: [ { id: "compose-and-chat", @@ -76,12 +76,12 @@ export const gmail: AppShortcuts = { { id: "forward", action: "Forward", keys: combo(platforms, ["f"]) }, { id: "forward-new-window", action: "Forward in a new window", keys: combo(platforms, ["Shift", "f"]) }, { id: "update-conversation", action: "Update the conversation", keys: combo(platforms, ["Shift", "n"]) }, - { id: "archive-next", action: "Archive and go to the next conversation", keys: combo(platforms, ["]"]), note: "[ archives and goes to the previous conversation." }, + { id: "archive-next", action: "Archive and move to the previous or next conversation", keys: combo(platforms, ["]"]).map((entry) => ({ ...entry, alternatives: [["["]] })) }, { id: "undo", action: "Undo the last action", keys: combo(platforms, ["z"]) }, { id: "mark-read", action: "Mark as read", keys: combo(platforms, ["Shift", "i"]) }, { id: "mark-unread", action: "Mark as unread", keys: combo(platforms, ["Shift", "u"]) }, { id: "mark-unread-selected-message", action: "Mark unread from the selected message", keys: combo(platforms, ["_"]) }, - { id: "mark-important", action: "Mark as important", keys: combo(platforms, ["+"]), note: "= is also documented." }, + { id: "mark-important", action: "Mark as important", keys: combo(platforms, ["+"]).map((entry) => ({ ...entry, alternatives: [["="]] })) }, { id: "mark-not-important", action: "Mark as not important", keys: combo(platforms, ["-"]) }, { id: "snooze", action: "Snooze the conversation", keys: combo(platforms, ["b"]), note: "Not available in classic Gmail." }, { id: "expand-conversation", action: "Expand the entire conversation", keys: combo(platforms, [";"]) }, @@ -102,7 +102,7 @@ export const gmail: AppShortcuts = { { id: "go-all-mail", action: "Go to All Mail", keys: combo(platforms, ["g", "a"]) }, { id: "go-tasks", action: "Go to Tasks", keys: combo(platforms, ["g", "k"]) }, { id: "go-label", action: "Go to a label", keys: combo(platforms, ["g", "l"]) }, - { id: "side-panel-next", action: "Move between the side panel and inbox", keys: perPlatform(platforms, { windows: ["Ctrl", "Alt", "."], macos: ["Cmd", "Alt", "."], linux: ["Ctrl", "Alt", "."] }), note: "The comma variant moves in the other direction." }, + { id: "side-panel-next", action: "Switch between the side panel and inbox", keys: perPlatform(platforms, { windows: ["Ctrl", "Alt", "."], macos: ["Cmd", "Alt", "."] }).map((entry) => ({ ...entry, alternatives: [[entry.platform === "macos" ? "Cmd" : "Ctrl", "Alt", ","]] })) }, ], }, { @@ -121,7 +121,7 @@ export const gmail: AppShortcuts = { { id: "back-to-threadlist", action: "Return to the thread list", keys: combo(platforms, ["u"]) }, { id: "newer-conversation", action: "Move to a newer conversation", keys: combo(platforms, ["k"]) }, { id: "older-conversation", action: "Move to an older conversation", keys: combo(platforms, ["j"]) }, - { id: "open-conversation", action: "Open the selected conversation", keys: combo(platforms, ["o"]), note: "Enter is also documented." }, + { id: "open-conversation", action: "Open the selected conversation", keys: combo(platforms, ["o"]).map((entry) => ({ ...entry, alternatives: [["Enter"]] })) }, { id: "next-inbox-section", action: "Go to the next Inbox section", keys: combo(platforms, ["`"]) }, { id: "previous-inbox-section", action: "Go to the previous Inbox section", keys: combo(platforms, ["~"]) }, ], diff --git a/lib/shortcuts/apps/gnome.ts b/lib/shortcuts/apps/gnome.ts index e7726c8d..6232e22a 100644 --- a/lib/shortcuts/apps/gnome.ts +++ b/lib/shortcuts/apps/gnome.ts @@ -9,7 +9,7 @@ export const gnome: AppShortcuts = { "Defaults from the current GNOME Help keyboard-shortcuts page checked in August 2026. Window-manager and terminal shortcuts can vary when a distribution changes GNOME settings or when another desktop component claims the same key.", sourceUrl: "https://help.gnome.org/gnome-help/shell-keyboard-shortcuts.html", sourceLabel: "official GNOME Help keyboard shortcuts", - lastVerified: "2026-08-26", + lastVerified: "2026-11-01", groups: [ { id: "shell-navigation", diff --git a/lib/shortcuts/apps/godot-engine.ts b/lib/shortcuts/apps/godot-engine.ts index 12c14356..45af5ea2 100644 --- a/lib/shortcuts/apps/godot-engine.ts +++ b/lib/shortcuts/apps/godot-engine.ts @@ -12,7 +12,7 @@ const godotEngine: AppShortcuts = { "The Godot 4.6 manual documents separate macOS workspace commands and shared editor commands for Windows, Linux, and macOS. All shortcuts can be changed in Editor Settings > Shortcuts, and some canvas keys depend on the active tool.", sourceUrl: "https://docs.godotengine.org/en/4.6/tutorials/editor/default_key_mapping.html", sourceLabel: "official Godot default key mapping", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "editor-and-workspaces", @@ -64,7 +64,6 @@ const godotEngine: AppShortcuts = { { id: "save-scene", action: "Save the current scene", keys: combo(platforms, ["Ctrl", "S"], ["Cmd", "S"]) }, { id: "filter-files", action: "Filter files", keys: combo(platforms, ["Ctrl", "Alt", "P"], ["Option", "Cmd", "P"]) }, { id: "bottom-panel", action: "Toggle the bottom panel", keys: combo(platforms, ["Ctrl", "J"]) }, - { id: "output-panel", action: "Open the Output panel", keys: combo(platforms, ["Ctrl", "J", "then", "O"]) }, ], }, { diff --git a/lib/shortcuts/apps/goodnotes.ts b/lib/shortcuts/apps/goodnotes.ts index 75810973..c0484179 100644 --- a/lib/shortcuts/apps/goodnotes.ts +++ b/lib/shortcuts/apps/goodnotes.ts @@ -12,7 +12,7 @@ const goodnotes: AppShortcuts = { "Goodnotes publishes separate Mac, Windows, iPadOS, and web columns. This record includes the Mac and Windows desktop bindings only. Some tools are available only in a document or text editor, as noted by their group.", sourceUrl: "https://support.goodnotes.com/hc/en-us/articles/17220521238159-Keyboard-Shortcuts-in-Goodnotes", sourceLabel: "official Goodnotes keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "files-and-tabs", diff --git a/lib/shortcuts/apps/google-drive.ts b/lib/shortcuts/apps/google-drive.ts index 833f808c..0a5283b2 100644 --- a/lib/shortcuts/apps/google-drive.ts +++ b/lib/shortcuts/apps/google-drive.ts @@ -12,24 +12,24 @@ const googleDrive: AppShortcuts = { "Google publishes PC and Mac columns for Drive. The navigation sequences below are entered in order, not pressed as one chord. Some commands require the file list, details pane, or preview to have focus.", sourceUrl: "https://support.google.com/drive/answer/2563044?co=GENIE.Platform%3DDesktop&hl=en", sourceLabel: "official Google Drive keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "navigation", title: "Navigation", intro: "Move focus to Drive's navigation areas, change the list view, and open status or details panes.", shortcuts: [ - { id: "navigation-panel", action: "Focus the navigation panel", keys: perPlatform(platforms, { windows: ["Alt", "G", "then", "N"], macos: ["Option", "G", "then", "N"] }) }, - { id: "folders", action: "Focus folders", keys: perPlatform(platforms, { windows: ["Alt", "G", "then", "F"], macos: ["Option", "G", "then", "F"] }) }, - { id: "items", action: "Focus items", keys: perPlatform(platforms, { windows: ["Alt", "G", "then", "I"], macos: ["Option", "G", "then", "I"] }) }, - { id: "grid-list", action: "Switch between grid and list view", keys: perPlatform(platforms, { windows: ["Alt", "V", "then", "L"], macos: ["Option", "V", "then", "L"] }) }, - { id: "page-content", action: "Focus page content", keys: perPlatform(platforms, { windows: ["Alt", "G", "then", "C"], macos: ["Option", "G", "then", "C"] }) }, - { id: "details-pane", action: "Open the details pane", keys: perPlatform(platforms, { windows: ["Alt", "V", "then", "D"], macos: ["Option", "V", "then", "D"] }) }, - { id: "activity-pane", action: "Open the activity pane", keys: perPlatform(platforms, { windows: ["Alt", "V", "then", "A"], macos: ["Option", "V", "then", "A"] }) }, - { id: "top-of-page", action: "Move to the top of the page", keys: perPlatform(platforms, { windows: ["Alt", "G", "then", "T"], macos: ["Option", "G", "then", "T"] }) }, - { id: "download-status", action: "Open download status", keys: perPlatform(platforms, { windows: ["Alt", "G", "then", "A"], macos: ["Option", "G", "then", "A"] }) }, - { id: "upload-status", action: "Open upload status", keys: perPlatform(platforms, { windows: ["Alt", "G", "then", "U"], macos: ["Option", "G", "then", "U"] }) }, - { id: "approvals", action: "Open approvals", keys: combo(platforms, ["Alt", "V", "then", "E"], ["Option", "V", "then", "E"]) }, + { id: "navigation-panel", action: "Focus the navigation panel", keys: perPlatform(platforms, { windows: ["Alt", "G", "N"], macos: ["Option", "G", "N"] }) }, + { id: "folders", action: "Focus folders", keys: perPlatform(platforms, { windows: ["Alt", "G", "F"], macos: ["Option", "G", "F"] }) }, + { id: "items", action: "Focus items", keys: perPlatform(platforms, { windows: ["Alt", "G", "I"], macos: ["Option", "G", "I"] }) }, + { id: "grid-list", action: "Switch between grid and list view", keys: perPlatform(platforms, { windows: ["Alt", "V", "L"], macos: ["Option", "V", "L"] }) }, + { id: "page-content", action: "Focus page content", keys: perPlatform(platforms, { windows: ["Alt", "G", "C"], macos: ["Option", "G", "C"] }) }, + { id: "details-pane", action: "Open the details pane", keys: perPlatform(platforms, { windows: ["Alt", "V", "D"], macos: ["Option", "V", "D"] }) }, + { id: "activity-pane", action: "Open the activity pane", keys: perPlatform(platforms, { windows: ["Alt", "V", "A"], macos: ["Option", "V", "A"] }) }, + { id: "top-of-page", action: "Move to the top of the page", keys: perPlatform(platforms, { windows: ["Alt", "G", "T"], macos: ["Option", "G", "T"] }) }, + { id: "download-status", action: "Open download status", keys: perPlatform(platforms, { windows: ["Alt", "G", "A"], macos: ["Option", "G", "A"] }) }, + { id: "upload-status", action: "Open upload status", keys: perPlatform(platforms, { windows: ["Alt", "G", "U"], macos: ["Option", "G", "U"] }) }, + { id: "approvals", action: "Open approvals", keys: combo(platforms, ["Alt", "V", "E"], ["Option", "V", "E"]) }, ], }, { @@ -60,15 +60,15 @@ const googleDrive: AppShortcuts = { shortcuts: [ { id: "cut", action: "Cut the selected item", keys: combo(platforms, ["Ctrl", "X"], ["Cmd", "X"]) }, { id: "paste", action: "Paste an item", keys: combo(platforms, ["Ctrl", "Shift", "V"], ["Cmd", "Shift", "V"]) }, - { id: "upload-file", action: "Upload files", keys: perPlatform(platforms, { windows: ["Alt", "C", "then", "U"], macos: ["Ctrl", "C", "then", "U"] }) }, - { id: "upload-folder", action: "Upload a folder", keys: perPlatform(platforms, { windows: ["Alt", "C", "then", "I"], macos: ["Ctrl", "C", "then", "I"] }) }, + { id: "upload-file", action: "Upload files", keys: perPlatform(platforms, { windows: ["Alt", "C", "U"], macos: ["Ctrl", "C", "U"] }) }, + { id: "upload-folder", action: "Upload a folder", keys: perPlatform(platforms, { windows: ["Alt", "C", "I"], macos: ["Ctrl", "C", "I"] }) }, { id: "new-menu", action: "Open the New menu", keys: perPlatform(platforms, { windows: ["Alt", "N"], macos: ["Ctrl", "N"] }) }, - { id: "new-document", action: "Create a Google document", keys: perPlatform(platforms, { windows: ["Alt", "C", "then", "T"], macos: ["Ctrl", "C", "then", "T"] }) }, - { id: "new-presentation", action: "Create a presentation", keys: perPlatform(platforms, { windows: ["Alt", "C", "then", "P"], macos: ["Ctrl", "C", "then", "P"] }) }, - { id: "new-spreadsheet", action: "Create a spreadsheet", keys: perPlatform(platforms, { windows: ["Alt", "C", "then", "S"], macos: ["Ctrl", "C", "then", "S"] }) }, - { id: "new-drawing", action: "Create a drawing", keys: perPlatform(platforms, { windows: ["Alt", "C", "then", "D"], macos: ["Ctrl", "C", "then", "D"] }) }, - { id: "new-folder", action: "Create a folder", keys: perPlatform(platforms, { windows: ["Alt", "C", "then", "F"], macos: ["Ctrl", "C", "then", "F"] }) }, - { id: "new-form", action: "Create a form", keys: perPlatform(platforms, { windows: ["Alt", "C", "then", "O"], macos: ["Ctrl", "C", "then", "O"] }) }, + { id: "new-document", action: "Create a Google document", keys: perPlatform(platforms, { windows: ["Alt", "C", "T"], macos: ["Ctrl", "C", "T"] }) }, + { id: "new-presentation", action: "Create a presentation", keys: perPlatform(platforms, { windows: ["Alt", "C", "P"], macos: ["Ctrl", "C", "P"] }) }, + { id: "new-spreadsheet", action: "Create a spreadsheet", keys: perPlatform(platforms, { windows: ["Alt", "C", "S"], macos: ["Ctrl", "C", "S"] }) }, + { id: "new-drawing", action: "Create a drawing", keys: perPlatform(platforms, { windows: ["Alt", "C", "D"], macos: ["Ctrl", "C", "D"] }) }, + { id: "new-folder", action: "Create a folder", keys: perPlatform(platforms, { windows: ["Alt", "C", "F"], macos: ["Ctrl", "C", "F"] }) }, + { id: "new-form", action: "Create a form", keys: perPlatform(platforms, { windows: ["Alt", "C", "O"], macos: ["Ctrl", "C", "O"] }) }, ], }, { @@ -76,14 +76,14 @@ const googleDrive: AppShortcuts = { title: "Menus and preview", intro: "Open action menus, search Drive, change density, and control a selected file in preview.", shortcuts: [ - { id: "more-actions", action: "Open the More actions menu", keys: perPlatform(platforms, { windows: ["Shift", "F10", "then", "A"], macos: ["Shift", "F10"] }) }, + { id: "more-actions", action: "Open the More actions menu", keys: perPlatform(platforms, { windows: ["Shift", "F10", "Alt", "A"], macos: ["Shift", "F10", "Control", "A"] }) }, { id: "folder-actions", action: "Open actions for the current folder", keys: perPlatform(platforms, { windows: ["Alt", "Shift", "A"], macos: ["Ctrl", "Shift", "A"] }) }, { id: "sort", action: "Open the sort menu", keys: combo(platforms, ["Alt", "R"], ["Ctrl", "R"]) }, { id: "settings", action: "Open Drive settings", keys: combo(platforms, ["Ctrl", "Shift", "S"], ["Cmd", "Shift", "S"]) }, { id: "help", action: "Show keyboard shortcuts", keys: combo(platforms, ["Ctrl", "/"], ["Cmd", "/"]) }, { id: "find", action: "Find in Drive", keys: combo(platforms, ["Ctrl", "F"], ["Cmd", "F"]) }, { id: "undo", action: "Undo", keys: combo(platforms, ["Ctrl", "Z"], ["Cmd", "Z"]) }, - { id: "last-message", action: "Open the last message", keys: perPlatform(platforms, { windows: ["Alt", "G", "then", "K"], macos: ["Option", "G", "then", "K"] }) }, + { id: "last-message", action: "Open the last message", keys: perPlatform(platforms, { windows: ["Alt", "G", "K"], macos: ["Option", "G", "K"] }) }, { id: "preview", action: "Preview the selected item", keys: combo(platforms, ["Ctrl", "Alt", "P"], ["Cmd", "Option", "P"]) }, { id: "close-preview", action: "Close preview", keys: combo(platforms, ["Esc"]) }, { id: "preview-play", action: "Play media in preview", keys: combo(platforms, ["Space"]) }, diff --git a/lib/shortcuts/apps/google-gemini.ts b/lib/shortcuts/apps/google-gemini.ts index eb6b2b56..41761f96 100644 --- a/lib/shortcuts/apps/google-gemini.ts +++ b/lib/shortcuts/apps/google-gemini.ts @@ -10,9 +10,9 @@ const googleGemini: AppShortcuts = { platforms: [...platforms], versionContext: "Gemini in Chrome is an opt-in feature that Google is rolling out gradually. Its shortcuts can be changed in Chrome settings, and this record does not present them as shortcuts for the general Gemini web app.", - sourceUrl: "https://support.google.com/gemini/answer/16988996?hl=en-GP", - sourceLabel: "official Google Gemini in Chrome keyboard shortcuts", - lastVerified: "2026-09-01", + sourceUrl: "https://support.google.com/chrome/answer/16988996?hl=en", + sourceLabel: "official Google Chrome Help for Gemini keyboard shortcuts", + lastVerified: "2026-11-01", groups: [ { id: "gemini-in-chrome", diff --git a/lib/shortcuts/apps/google-meet.ts b/lib/shortcuts/apps/google-meet.ts index d23f22f8..56c1750e 100644 --- a/lib/shortcuts/apps/google-meet.ts +++ b/lib/shortcuts/apps/google-meet.ts @@ -12,7 +12,7 @@ const googleMeet: AppShortcuts = { "Google publishes separate Windows and Mac meeting tables. The complete in-meeting shortcut list is available from Meet's help dialog with Shift+question mark; some commands depend on account or meeting permissions.", sourceUrl: "https://support.google.com/meet/answer/9298571?hl=en", sourceLabel: "official Google Meet keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "meeting-controls", diff --git a/lib/shortcuts/apps/google-slides.ts b/lib/shortcuts/apps/google-slides.ts index bee58ad5..487e1f28 100644 --- a/lib/shortcuts/apps/google-slides.ts +++ b/lib/shortcuts/apps/google-slides.ts @@ -12,7 +12,7 @@ const googleSlides: AppShortcuts = { "Google publishes PC and Mac columns for Slides. Commands such as filmstrip movement and speaker notes require focus in the matching Slides area, while formatting commands apply to selected text or objects.", sourceUrl: "https://support.google.com/docs/answer/1696717?hl=en-EN", sourceLabel: "official Google Slides keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "slide-editing", diff --git a/lib/shortcuts/apps/grammarly.ts b/lib/shortcuts/apps/grammarly.ts index 29fafdc8..4804b49c 100644 --- a/lib/shortcuts/apps/grammarly.ts +++ b/lib/shortcuts/apps/grammarly.ts @@ -12,7 +12,7 @@ const grammarly: AppShortcuts = { "The source covers Grammarly for Windows and Mac, not every browser or editor integration. Tab and Shift+Tab move focus, while Enter or Space activates a focused control. Grammarly's separate quick-fix Tab behavior is being discontinued and is not included.", sourceUrl: "https://support.grammarly.com/hc/en-us/articles/24059143083277-Can-I-use-my-keyboard-to-navigate-Grammarly-for-Windows-and-Mac", sourceLabel: "official Grammarly keyboard navigation", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "desktop-surfaces", @@ -20,7 +20,6 @@ const grammarly: AppShortcuts = { intro: "Open the Grammarly Assistant or sidebar, close it, and move focus through its controls using the documented desktop commands.", shortcuts: [ { id: "open-assistant", action: "Open the Grammarly Assistant", keys: perPlatform(platforms, { windows: ["Shift", "Ctrl", "Alt", "G"], macos: ["Shift", "Ctrl", "Cmd", "G"] }) }, - { id: "close-surface", action: "Close the current Grammarly surface", keys: combo(platforms, ["Esc"]) }, { id: "open-sidebar", action: "Open the Grammarly sidebar", keys: perPlatform(platforms, { windows: ["Shift", "Ctrl", "G"], macos: ["Shift", "Cmd", "G"] }) }, { id: "next-control", action: "Move focus to the next control", keys: combo(platforms, ["Tab"]) }, { id: "previous-control", action: "Move focus to the previous control", keys: combo(platforms, ["Shift", "Tab"]) }, diff --git a/lib/shortcuts/apps/harvest.ts b/lib/shortcuts/apps/harvest.ts index 2e180ed9..d907fe0c 100644 --- a/lib/shortcuts/apps/harvest.ts +++ b/lib/shortcuts/apps/harvest.ts @@ -12,7 +12,7 @@ const harvest: AppShortcuts = { "Harvest documents Windows defaults and marks several commands as customizable in preferences. Its Mac preferences page describes customization without publishing stable default key values, so this record is Windows-only.", sourceUrl: "https://support.getharvest.com/hc/en-us/articles/360048685411-Windows-app-Keyboard-shortcuts", sourceLabel: "official Harvest Windows app keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "timer-and-timesheet", diff --git a/lib/shortcuts/apps/httpie.ts b/lib/shortcuts/apps/httpie.ts index a754cdb7..c6f9ba95 100644 --- a/lib/shortcuts/apps/httpie.ts +++ b/lib/shortcuts/apps/httpie.ts @@ -12,7 +12,7 @@ const httpie: AppShortcuts = { "The official HTTPie Desktop preview documentation publishes macOS, Windows, and Linux bindings. Some commands are desktop-only, and the preview feature set may change as HTTPie Desktop evolves.", sourceUrl: "https://httpie.io/docs/desktop/preview", sourceLabel: "official HTTPie Desktop keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "tabs-and-app", diff --git a/lib/shortcuts/apps/ia-writer.ts b/lib/shortcuts/apps/ia-writer.ts index eeb53566..90d0b930 100644 --- a/lib/shortcuts/apps/ia-writer.ts +++ b/lib/shortcuts/apps/ia-writer.ts @@ -12,7 +12,7 @@ const iaWriter: AppShortcuts = { "The official iA Writer page publishes the Mac app shortcuts and notes that bindings can change with app updates. The Windows page was not used here because the current public reference did not expose an equivalent complete table.", sourceUrl: "https://ia.net/writer/support/basics/shortcuts", sourceLabel: "official iA Writer Mac shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "files-and-search", diff --git a/lib/shortcuts/apps/insomnia.ts b/lib/shortcuts/apps/insomnia.ts index 4c3f1180..dd95210c 100644 --- a/lib/shortcuts/apps/insomnia.ts +++ b/lib/shortcuts/apps/insomnia.ts @@ -12,7 +12,7 @@ const insomnia: AppShortcuts = { "Kong's Insomnia documentation publishes Windows and macOS columns. Several commands depend on the request editor, response panel, or sidebar having focus, and all shortcuts remain user-configurable through the app.", sourceUrl: "https://developer.konghq.com/insomnia/keyboard-shortcuts/", sourceLabel: "official Insomnia keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "navigation", diff --git a/lib/shortcuts/apps/intellij-idea.ts b/lib/shortcuts/apps/intellij-idea.ts index 7d8a1454..fce231dd 100644 --- a/lib/shortcuts/apps/intellij-idea.ts +++ b/lib/shortcuts/apps/intellij-idea.ts @@ -13,7 +13,7 @@ const intellijIdea: AppShortcuts = { "JetBrains documents predefined Windows and macOS keymaps for IntelliJ IDEA 2026.2. All bindings can be changed in Settings > Keymap, and function keys may also be claimed by the operating system.", sourceUrl: "https://www.jetbrains.com/help/idea/mastering-keyboard-shortcuts.html", sourceLabel: "official JetBrains IntelliJ IDEA keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "top-commands", diff --git a/lib/shortcuts/apps/intercom.ts b/lib/shortcuts/apps/intercom.ts index b74731dd..56078be5 100644 --- a/lib/shortcuts/apps/intercom.ts +++ b/lib/shortcuts/apps/intercom.ts @@ -12,7 +12,7 @@ const intercom: AppShortcuts = { "Intercom documents the same direct shortcuts for Mac and Windows with Command or Control modifiers. The direct keys work outside the composer on QWERTY keyboards in Chrome or Edge, and the Command-K menu remains available from the Inbox.", sourceUrl: "https://www.intercom.com/help/en/articles/6272267-how-to-use-command-k-with-intercom-inbox", sourceLabel: "official Intercom Inbox keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "conversation-actions", diff --git a/lib/shortcuts/apps/jira.ts b/lib/shortcuts/apps/jira.ts index 93190b4c..7bd47ed1 100644 --- a/lib/shortcuts/apps/jira.ts +++ b/lib/shortcuts/apps/jira.ts @@ -1,7 +1,7 @@ import { combo } from "../build.ts"; import type { AppShortcuts } from "../types.ts"; -const platforms = ["windows", "macos", "linux"] as const; +const platforms = ["windows", "macos"] as const; const jira: AppShortcuts = { slug: "jira", @@ -12,7 +12,7 @@ const jira: AppShortcuts = { "Atlassian says the exact Jira shortcut set depends on the current page and can be inspected from Help > Keyboard shortcuts or with question mark. The direct keys below are published for Jira Cloud and do not assume a platform-specific modifier.", sourceUrl: "https://support.atlassian.com/jira-software-cloud/docs/use-keyboard-shortcuts/", sourceLabel: "official Atlassian Jira Cloud keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "jira-cloud", diff --git a/lib/shortcuts/apps/joplin.ts b/lib/shortcuts/apps/joplin.ts index 76c9556c..7e42ac64 100644 --- a/lib/shortcuts/apps/joplin.ts +++ b/lib/shortcuts/apps/joplin.ts @@ -9,10 +9,10 @@ export const joplin: AppShortcuts = { category: "Notes and writing", platforms: [...platforms], versionContext: - "Default Joplin Terminal keymap from the current official terminal-app guide, checked September 1, 2026. The terminal client is available on macOS, Linux, and Windows through WSL; its keymap uses sequential keys rather than simultaneous modifier chords.", + "Default Joplin Terminal keymap from the current official terminal-app guide, checked November 1, 2026. The terminal client is available on macOS, Linux, and Windows through WSL; its keymap uses sequential keys rather than simultaneous modifier chords.", sourceUrl: "https://joplinapp.org/help/apps/terminal/", sourceLabel: "official Joplin Terminal keymap", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "navigation", diff --git a/lib/shortcuts/apps/jupyter-notebook.ts b/lib/shortcuts/apps/jupyter-notebook.ts index d43993f4..255ba811 100644 --- a/lib/shortcuts/apps/jupyter-notebook.ts +++ b/lib/shortcuts/apps/jupyter-notebook.ts @@ -1,7 +1,7 @@ import { combo } from "../build.ts"; import type { AppShortcuts } from "../types.ts"; -const platforms = ["windows", "macos", "linux"] as const; +const platforms = ["windows", "macos"] as const; export const jupyterNotebook: AppShortcuts = { slug: "jupyter-notebook", @@ -9,10 +9,10 @@ export const jupyterNotebook: AppShortcuts = { category: "Developer tools", platforms: [...platforms], versionContext: - "Essential shortcuts from the Jupyter Notebook 7.6.0 documentation, checked September 1, 2026. The full context-sensitive map is available from Help > Keyboard Shortcuts inside the running notebook; this page keeps the defaults explicitly documented in the public guide.", + "Essential shortcuts from the Jupyter Notebook 7.6.0 documentation, checked November 1, 2026. The full context-sensitive map is available from Help > Keyboard Shortcuts inside the running notebook; this page keeps the defaults explicitly documented in the public guide.", sourceUrl: "https://jupyter-notebook.readthedocs.io/en/v7.6.0/notebook.html", sourceLabel: "Jupyter Notebook 7.6.0 keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "notebook-editing", diff --git a/lib/shortcuts/apps/keynote.ts b/lib/shortcuts/apps/keynote.ts index 3c5f92af..32b3e790 100644 --- a/lib/shortcuts/apps/keynote.ts +++ b/lib/shortcuts/apps/keynote.ts @@ -12,7 +12,7 @@ export const keynote: AppShortcuts = { "Keyboard shortcuts from Apple's current Keynote for Mac User Guide, checked September 1, 2026. Some commands are context-sensitive, and presentation-mode keys apply only while a slideshow is running.", sourceUrl: "https://support.apple.com/guide/keynote/keyboard-shortcuts-tanfde4a3e6d/mac", sourceLabel: "Apple Keynote for Mac keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "files-and-window", diff --git a/lib/shortcuts/apps/libreoffice-writer.ts b/lib/shortcuts/apps/libreoffice-writer.ts index dfabd1bc..39118a4e 100644 --- a/lib/shortcuts/apps/libreoffice-writer.ts +++ b/lib/shortcuts/apps/libreoffice-writer.ts @@ -30,7 +30,7 @@ export const libreOfficeWriter: AppShortcuts = { "Default shortcuts from LibreOffice 26.8 Help, checked September 1, 2026. The source notes that desktop environments can claim a key and that users can change assignments in Tools > Customize > Keyboard. Windows and Linux use Ctrl or Alt; the documented Command and Option forms are preserved for macOS.", sourceUrl: "https://help.libreoffice.org/latest/en-US/text/swriter/04/01020000.html?DbPAR=SHARED", sourceLabel: "LibreOffice 26.8 Writer shortcut keys", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "function-keys", diff --git a/lib/shortcuts/apps/linkedin.ts b/lib/shortcuts/apps/linkedin.ts index 96ffa966..5f63920c 100644 --- a/lib/shortcuts/apps/linkedin.ts +++ b/lib/shortcuts/apps/linkedin.ts @@ -1,7 +1,7 @@ import { combo } from "../build.ts"; import type { AppShortcuts } from "../types.ts"; -const platforms = ["windows", "macos", "linux"] as const; +const platforms = ["windows", "macos"] as const; export const linkedin: AppShortcuts = { slug: "linkedin", @@ -9,10 +9,10 @@ export const linkedin: AppShortcuts = { category: "Social networking", platforms: [...platforms], versionContext: - "Desktop-only shortcuts from LinkedIn's current Keyboard Shortcuts FAQ, checked September 1, 2026. LinkedIn says the shortcuts can be enabled or disabled from the keyboard menu and are not available in its mobile apps.", + "Desktop-only shortcuts from LinkedIn's Keyboard Shortcuts FAQ, checked November 1, 2026. LinkedIn says the shortcuts can be enabled or disabled from the keyboard menu and are not available in its mobile apps.", sourceUrl: "https://www.linkedin.com/help/linkedin/answer/a6246187?lang=en-US", sourceLabel: "official LinkedIn Keyboard Shortcuts FAQ", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "feed", diff --git a/lib/shortcuts/apps/linux-kde-plasma.ts b/lib/shortcuts/apps/linux-kde-plasma.ts index ffdce500..d36f3306 100644 --- a/lib/shortcuts/apps/linux-kde-plasma.ts +++ b/lib/shortcuts/apps/linux-kde-plasma.ts @@ -9,10 +9,10 @@ export const linuxKdePlasma: AppShortcuts = { category: "Desktop environment", platforms: [...platforms], versionContext: - "Defaults from the current KDE Plasma 6 Fundamentals and Plasma Desktop handbooks, checked September 1, 2026. Global shortcuts are configurable in System Settings, and distributions or user profiles can change them. The handbooks disagree on the direction of one Meta-plus zoom binding, so that conflicting zoom pair is omitted.", + "Defaults from the current KDE Plasma 6 Fundamentals and Plasma Desktop handbooks, checked November 1, 2026. Global shortcuts are configurable in System Settings, and distributions or user profiles can change them. The handbooks disagree on the direction of one Meta-plus zoom binding, so that conflicting zoom pair is omitted.", sourceUrl: "https://docs.kde.org/stable_kf6/en/khelpcenter/fundamentals/fundamentals.pdf", sourceLabel: "KDE Plasma 6 Fundamentals keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "windows", @@ -65,7 +65,7 @@ export const linuxKdePlasma: AppShortcuts = { { id: "next-widget", action: "Focus the next widget", keys: combo(platforms, ["Alt", "D", "then", "N"]) }, { id: "previous-widget", action: "Focus the previous widget", keys: combo(platforms, ["Alt", "D", "then", "P"]) }, { id: "widget-settings", action: "Open widget settings", keys: combo(platforms, ["Alt", "D", "then", "S"]) }, - { id: "activity-settings", action: "Open activity settings", keys: combo(platforms, ["Alt", "D", "then", "Alt", "S"]) }, + { id: "activity-settings", action: "Open Desktop Settings", keys: combo(platforms, ["Alt", "D", "then", "Alt", "S"]) }, { id: "remove-activity", action: "Remove the current activity", keys: combo(platforms, ["Alt", "D", "then", "Alt", "R"]) }, { id: "add-activity", action: "Open Activities management", keys: combo(platforms, ["Alt", "D", "then", "Alt", "A"]) }, { id: "associated-application", action: "Run the associated application", keys: combo(platforms, ["Alt", "D", "then", "T"]) }, diff --git a/lib/shortcuts/apps/logseq.ts b/lib/shortcuts/apps/logseq.ts index addca605..932ad20c 100644 --- a/lib/shortcuts/apps/logseq.ts +++ b/lib/shortcuts/apps/logseq.ts @@ -35,10 +35,10 @@ export const logseq: AppShortcuts = { category: "Knowledge management", platforms: [...platforms], versionContext: - "Current default Logseq keymap source checked September 1, 2026. The project defines a platform-neutral `mod` modifier, which maps to Ctrl on Windows and Linux and Command on macOS. Users can change the keymap in Logseq settings or the source configuration.", + "Current default Logseq keymap source checked November 1, 2026. The project defines a platform-neutral `mod` modifier, which maps to Ctrl on Windows and Linux and Command on macOS. Users can change the keymap in Logseq settings or the source configuration.", sourceUrl: "https://github.com/logseq/logseq/blob/master/src/main/frontend/modules/shortcut/config.cljs", sourceLabel: "official Logseq shortcut configuration", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "block-editing", diff --git a/lib/shortcuts/apps/looker.ts b/lib/shortcuts/apps/looker.ts index 447383b3..ee35a550 100644 --- a/lib/shortcuts/apps/looker.ts +++ b/lib/shortcuts/apps/looker.ts @@ -18,10 +18,10 @@ export const looker: AppShortcuts = { category: "Analytics", platforms: [...platforms], versionContext: - "Shortcuts from the current Google Cloud Looker keyboard-shortcuts page, last updated July 22, 2025 and checked September 1, 2026. Availability depends on the current Looker location, permissions, and browser; the published table covers Windows and macOS.", + "Shortcuts from the current Google Cloud Looker keyboard-shortcuts page, last updated July 22, 2025 and checked November 1, 2026. Availability depends on the current Looker location, permissions, and browser; the published table covers Windows and macOS.", sourceUrl: "https://docs.cloud.google.com/looker/docs/keyboard-shortcuts?hl=en", sourceLabel: "Google Cloud Looker keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "global", @@ -31,7 +31,7 @@ export const looker: AppShortcuts = { pair("search", "Open search", ["Ctrl", "Shift", "K"], ["Cmd", "Shift", "K"]), shared("shortcut-help", "Show or hide shortcut help", ["?"]), pair("developer-mode", "Toggle Development Mode", ["Ctrl", "Shift", "D"], ["Ctrl", "Shift", "D"]), - pair("navigation", "Focus Looker navigation", ["Ctrl", ";"], ["Ctrl", ";"]), + pair("navigation", "Show or hide the left navigation menu", ["Ctrl", ";"], ["Ctrl", ";"]), ], }, { @@ -55,7 +55,7 @@ export const looker: AppShortcuts = { pair("add-text", "Add a text tile", ["Alt", "Shift", "T"], ["Option", "Shift", "T"]), pair("add-query-tile", "Add a query tile", ["Alt", "Shift", "V"], ["Option", "Shift", "V"]), pair("save", "Save dashboard changes", ["Ctrl", "S"], ["Cmd", "S"]), - pair("filter", "Focus dashboard filters", ["Alt", "Shift", "F"], ["Option", "Shift", "F"]), + pair("filter", "Add a dashboard filter", ["Alt", "Shift", "F"], ["Option", "Shift", "F"]), ], }, { @@ -101,17 +101,17 @@ export const looker: AppShortcuts = { intro: "Search files, save and format LookML, edit indentation, and move through the IDE's code structure.", shortcuts: [ pair("search-file", "Search for a file", ["Ctrl", "J"], ["Cmd", "J"]), - pair("file-browser", "Open the file browser", ["Ctrl", "B"], ["Cmd", "B"]), + pair("file-browser", "Show or hide the IDE file browser", ["Ctrl", "B"], ["Cmd", "B"]), pair("fold-all", "Fold all code", ["Ctrl", "Alt", "0"], ["Option", "Cmd", "0"]), pair("unfold-all", "Unfold all code", ["Ctrl", "Alt", "="], ["Option", "Cmd", "="]), pair("save-file", "Save the file", ["Ctrl", "S"], ["Cmd", "S"]), pair("undo", "Undo the last edit", ["Ctrl", "Z"], ["Cmd", "Z"]), pair("redo", "Redo the last edit", ["Ctrl", "Shift", "Z"], ["Cmd", "Shift", "Z"]), pair("comment", "Comment or uncomment a line", ["Ctrl", "/"], ["Cmd", "/"]), - pair("indent", "Indent the selected lines", ["Ctrl", "]"], ["Ctrl", "]"]), - pair("outdent", "Outdent the selected lines", ["Ctrl", "["], ["Ctrl", "["]), - pair("delete-line", "Delete the current line", ["Ctrl", "D"], ["Ctrl", "D"]), - pair("duplicate-line", "Duplicate the current line", ["Ctrl", "Shift", "D"], ["Ctrl", "Shift", "D"]), + pair("indent", "Indent the selected lines", ["Ctrl", "]"], ["Cmd", "]"]), + pair("outdent", "Outdent the selected lines", ["Ctrl", "["], ["Cmd", "["]), + pair("delete-line", "Delete the current or selected lines", ["Ctrl", "D"], ["Cmd", "D"]), + pair("duplicate-line", "Duplicate the current line", ["Ctrl", "Shift", "D"], ["Cmd", "Shift", "D"]), pair("move-line-up", "Move the current line up", ["Alt", "Up"], ["Option", "Up"]), pair("move-line-down", "Move the current line down", ["Alt", "Down"], ["Option", "Down"]), pair("format", "Format LookML", ["Ctrl", "Shift", "F"], ["Cmd", "Shift", "F"]), diff --git a/lib/shortcuts/apps/loom.ts b/lib/shortcuts/apps/loom.ts index 41485428..d55dc337 100644 --- a/lib/shortcuts/apps/loom.ts +++ b/lib/shortcuts/apps/loom.ts @@ -3,7 +3,7 @@ import type { AppShortcuts, Shortcut } from "../types.ts"; const platforms = ["windows", "macos"] as const; -const desktop = (id: string, action: string, windows: string[], macos: string[], note?: string): Shortcut => ({ +const desktop = (id: string, action: string, windows?: string[], macos?: string[], note?: string): Shortcut => ({ id, action, keys: perPlatform(platforms, { windows, macos }), @@ -22,24 +22,29 @@ export const loom: AppShortcuts = { category: "Screen recording", platforms: [...platforms], versionContext: - "Current Loom help shortcuts checked September 1, 2026. Desktop bindings can be changed in Preferences, Chrome extension bindings can be changed at chrome://extensions/shortcuts, and Loom documents QWERTY support for the desktop set.", + "Current Loom help shortcuts checked November 1, 2026. Desktop bindings can be changed in Settings, Chrome extension bindings can be changed at chrome://extensions/shortcuts, and Loom documents QWERTY support for the desktop set.", sourceUrl: "https://support.atlassian.com/loom/docs/use-looms-keyboard-shortcuts", sourceLabel: "official Loom keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "desktop-recorder", title: "Desktop recorder", intro: "Control an active Loom recording or capture a screenshot from the desktop app.", shortcuts: [ - desktop("start-stop", "Start or stop recording", ["Ctrl", "Shift", "L"], ["Cmd", "Shift", "L"]), - desktop("pause-resume", "Pause or resume recording", ["Alt", "Shift", "P"], ["Option", "Shift", "P"]), - desktop("cancel", "Cancel recording", ["Alt", "Shift", "C"], ["Option", "Shift", "C"]), - desktop("full-screen-screenshot", "Capture a full-screen screenshot", ["Ctrl", "Shift", "1"], ["Cmd", "Shift", "1"]), - desktop("custom-screenshot", "Capture a custom-size screenshot", ["Ctrl", "Shift", "2"], ["Cmd", "Shift", "2"]), - desktop("quick-restart", "Quickly restart recording", ["Ctrl", "Shift", "R"], ["Cmd", "Shift", "R"]), - desktop("draw", "Open the drawing tool", ["Ctrl", "Shift", "D"], ["Cmd", "Shift", "D"]), - desktop("confetti", "Trigger confetti", ["Ctrl", "Alt", "C"], ["Ctrl", "Cmd", "C"]), + desktop("start-stop", "Start or stop recording", ["Ctrl", "Shift", "L"]), + desktop("pause-resume", "Pause or resume recording", ["Alt", "Shift", "P"]), + desktop("cancel", "Cancel recording", ["Alt", "Shift", "C"]), + desktop("full-screen-screenshot", "Capture the display", ["Ctrl", "Shift", "1"], ["Option", "Shift", "1"]), + desktop("custom-screenshot", "Capture a custom-size screenshot", ["Ctrl", "Shift", "2"], ["Option", "Shift", "4"]), + desktop("quick-restart", "Quickly restart recording", ["Ctrl", "Shift", "R"]), + desktop("draw", "Open the drawing tool", ["Ctrl", "Shift", "D"]), + desktop("confetti", "Trigger confetti", ["Ctrl", "Alt", "C"]), + desktop("record-window", "Record a window", undefined, ["Option", "Shift", "2"]), + desktop("record-custom-size", "Record a custom size", undefined, ["Option", "Shift", "3"]), + desktop("record-camera", "Record camera only", undefined, ["Option", "Shift", "4"]), + desktop("record-agent", "Record for Agent", undefined, ["Option", "Shift", "5"]), + desktop("record-control", "Start, pause, or resume recording", undefined, ["Control", "Option", "R"]), ], }, { diff --git a/lib/shortcuts/apps/macos-finder.ts b/lib/shortcuts/apps/macos-finder.ts index acfe7587..c1d846bc 100644 --- a/lib/shortcuts/apps/macos-finder.ts +++ b/lib/shortcuts/apps/macos-finder.ts @@ -9,10 +9,10 @@ export const macosFinder: AppShortcuts = { category: "File management", platforms: [...platforms], versionContext: - "Finder and system shortcuts from Apple's current Mac keyboard-shortcuts reference, checked September 1, 2026. A command can change with the current Finder selection or window state.", - sourceUrl: "https://support.apple.com/102650", + "Finder and system shortcuts from Apple's current Mac keyboard-shortcuts reference, checked November 1, 2026. A command can change with the current Finder selection or window state.", + sourceUrl: "https://support.apple.com/en-us/102650", sourceLabel: "Apple Mac keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "file-actions", diff --git a/lib/shortcuts/apps/macos.ts b/lib/shortcuts/apps/macos.ts index e3e02710..67409911 100644 --- a/lib/shortcuts/apps/macos.ts +++ b/lib/shortcuts/apps/macos.ts @@ -7,10 +7,10 @@ export const macos: AppShortcuts = { category: "Operating system", platforms: ["macos"], versionContext: - "From Apple's Mac keyboard shortcuts article published March 10, 2026, checked again in August 2026. Shortcuts apply to current macOS releases, though individual apps can override common bindings and some keys depend on the keyboard hardware.", + "Apple's current Mac keyboard shortcuts checked November 1, 2026. Shortcuts apply across macOS, though individual apps can override common bindings and some keys depend on the keyboard hardware.", sourceUrl: "https://support.apple.com/en-us/102650", sourceLabel: "official Apple shortcut list", - lastVerified: "2026-08-26", + lastVerified: "2026-11-01", groups: [ { id: "essentials", @@ -40,7 +40,7 @@ export const macos: AppShortcuts = { { id: "save", action: "Save the current document", keys: [{ platform: "macos", keys: ["Cmd", "S"] }] }, { id: "new-tab", action: "Open a new tab", keys: [{ platform: "macos", keys: ["Cmd", "T"] }] }, { id: "app-settings", action: "Open settings for the front app", keys: [{ platform: "macos", keys: ["Cmd", ","] }] }, - { id: "character-viewer", action: "Open the Character Viewer", keys: [{ platform: "macos", keys: ["Control", "Cmd", "Space"] }], note: "Fn+E is also documented." }, + { id: "character-viewer", action: "Open the Character Viewer", keys: [{ platform: "macos", keys: ["Control", "Cmd", "Space"], alternatives: [["Fn", "E"]] }] }, { id: "quick-note", action: "Create a Quick Note", keys: [{ platform: "macos", keys: ["Fn", "Q"] }] }, ], }, @@ -234,7 +234,7 @@ export const macos: AppShortcuts = { { id: "spelling", action: "Open the Spelling and Grammar window", keys: [{ platform: "macos", keys: ["Shift", "Cmd", ":"] }] }, { id: "find-misspelled", action: "Find misspelled words", keys: [{ platform: "macos", keys: ["Cmd", ";"] }] }, { id: "delete-left", action: "Delete the character to the left", keys: [{ platform: "macos", keys: ["Control", "H"] }] }, - { id: "delete-forward", action: "Delete the character to the right", keys: [{ platform: "macos", keys: ["Control", "D"] }], note: "Fn+Delete is also forward delete." }, + { id: "delete-forward", action: "Delete the character to the right", keys: [{ platform: "macos", keys: ["Control", "D"], alternatives: [["Fn", "Delete"]] }] }, { id: "cut-to-paragraph-end", action: "Cut to the end of the paragraph", keys: [{ platform: "macos", keys: ["Control", "K"] }], note: "Control+Y pastes this app-local cut buffer." }, { id: "center-cursor", action: "Center the cursor or selection in view", keys: [{ platform: "macos", keys: ["Control", "L"] }] }, { id: "control-up", action: "Move up one line", keys: [{ platform: "macos", keys: ["Control", "P"] }] }, diff --git a/lib/shortcuts/apps/mastodon.ts b/lib/shortcuts/apps/mastodon.ts index c99f35d6..d22718b6 100644 --- a/lib/shortcuts/apps/mastodon.ts +++ b/lib/shortcuts/apps/mastodon.ts @@ -23,10 +23,10 @@ export const mastodon: AppShortcuts = { category: "Social networking", platforms: [...platforms], versionContext: - "Web hotkeys from Mastodon's current main source, checked September 1, 2026. The source ignores shortcuts while focus is in an input, textarea, or select, and user instances can run different releases or customizations. Alt is shown as Option on macOS.", + "Web hotkeys from Mastodon's current main source, checked November 1, 2026. The source ignores shortcuts while focus is in an input, textarea, or select, and user instances can run different releases or customizations. Alt is shown as Option on macOS.", sourceUrl: "https://github.com/mastodon/mastodon/blob/main/app/javascript/mastodon/components/hotkeys/index.tsx", sourceLabel: "Mastodon web hotkeys source", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "post-actions", diff --git a/lib/shortcuts/apps/matlab.ts b/lib/shortcuts/apps/matlab.ts index f2ce245d..dbe124c4 100644 --- a/lib/shortcuts/apps/matlab.ts +++ b/lib/shortcuts/apps/matlab.ts @@ -30,10 +30,10 @@ export const matlab: AppShortcuts = { category: "Developer tools", platforms: [...platforms], versionContext: - "Current MATLAB Editor and desktop keyboard-shortcut tables checked September 1, 2026. The exact set depends on MATLAB release and operating-system keyboard settings; users can inspect or change bindings in Preferences.", + "Current MATLAB Editor and desktop keyboard-shortcut tables checked November 1, 2026. The exact set depends on MATLAB release and operating-system keyboard settings; users can inspect or change bindings in Preferences.", sourceUrl: "https://www.mathworks.com/help/matlab/matlab_prog/editor-keyboard-shortcuts.html", sourceLabel: "official MathWorks MATLAB Editor shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "editor-window", @@ -51,7 +51,7 @@ export const matlab: AppShortcuts = { pair("save-as", "Save the current file as", ["Ctrl", "Shift", "S"], ["Cmd", "Shift", "S"]), pair("print", "Print the current file", ["Ctrl", "P"], ["Cmd", "P"]), pair("close-file", "Close the current file", ["Ctrl", "W"], ["Cmd", "W"]), - pair("reopen-file", "Reopen the most recently closed file", ["Ctrl", "Shift", "Z"], ["Cmd", "Shift", "Z"]), + pair("reopen-file", "Reopen the most recently closed file", ["Ctrl", "Shift", "T"], ["Cmd", "Shift", "T"]), pair("undo", "Undo the last edit", ["Ctrl", "Z"], ["Cmd", "Z"]), pair("redo", "Redo the last edit", ["Ctrl", "Y"], ["Cmd", "Shift", "Z"]), ], @@ -65,20 +65,20 @@ export const matlab: AppShortcuts = { pair("toggle-bookmark", "Set or clear a bookmark", ["Ctrl", "F2"], ["Cmd", "F2"]), all("next-bookmark", "Go to the next bookmark", ["F2"]), all("previous-bookmark", "Go to the previous bookmark", ["Shift", "F2"]), - pair("previous-location", "Go to the previous location", ["Alt", "Left"], ["Ctrl", "Minus"]), - pair("next-location", "Go to the next location", ["Alt", "Right"], ["Ctrl", "Shift", "Minus"]), - pair("previous-paragraph", "Move to the previous paragraph", ["Alt", "Up"], ["Option", "Up"]), - pair("next-paragraph", "Move to the next paragraph", ["Alt", "Down"], ["Option", "Down"]), + pair("previous-location", "Go back", ["Alt", "Left"], ["Ctrl", "Minus"]), + pair("next-location", "Go forward", ["Alt", "Right"], ["Ctrl", "Shift", "Minus"]), + pair("previous-paragraph", "Go to the previous variable instance", ["Alt", "Up"], ["Option", "Up"]), + pair("next-paragraph", "Go to the next variable instance", ["Alt", "Down"], ["Option", "Down"]), pair("find", "Find text", ["Ctrl", "F"], ["Cmd", "F"]), pair("find-next", "Find the next match", ["F3"], ["Cmd", "G"]), pair("find-previous", "Find the previous match", ["Shift", "F3"], ["Cmd", "Shift", "G"]), - windowsLinuxOnly("find-files", "Find files", ["Alt", "M"]), - windowsLinuxOnly("find-functions", "Find functions", ["Alt", "W"]), - windowsLinuxOnly("find-symbols", "Find symbols", ["Alt", "X"]), - windowsLinuxOnly("find-sections", "Find sections", ["Alt", "S"]), - windowsLinuxOnly("find-references", "Find references", ["Alt", "R"]), - windowsLinuxOnly("find-actions", "Find actions", ["Alt", "A"]), + windowsLinuxOnly("match-case", "Toggle Match case in Find and Replace", ["Alt", "M"]), + windowsLinuxOnly("match-whole-word", "Toggle Match whole word in Find and Replace", ["Alt", "W"]), + windowsLinuxOnly("search-regex", "Toggle regular expression search in Find and Replace", ["Alt", "X"]), + windowsLinuxOnly("find-in-selection", "Search within the selection", ["Alt", "S"]), pair("replace", "Find and replace", ["Ctrl", "H"], ["Cmd", "Option", "F"]), + windowsLinuxOnly("replace-match", "Replace the current match", ["Alt", "R"]), + windowsLinuxOnly("replace-all", "Replace all matches", ["Alt", "A"]), all("escape-find", "Close the Find or Replace bar", ["Esc"]), pair("find-selection", "Find the selected text", ["Ctrl", "F3"], ["Cmd", "E"]), ], @@ -88,27 +88,27 @@ export const matlab: AppShortcuts = { title: "Editing and formatting", intro: "Format code, comment blocks, indent lines, and manage the Editor's live-code tools.", shortcuts: [ - pair("live-script-section", "Insert a live-code section", ["Alt", "Enter"], ["Option", "Enter"]), - pair("live-editor-settings", "Open live-editor settings", ["Ctrl", "Alt", "T"], ["Ctrl", "Option", "T"]), - all("format-section-1", "Apply live-code format 1", ["Ctrl", "Shift", "1"]), - all("format-section-2", "Apply live-code format 2", ["Ctrl", "Shift", "2"]), - all("format-section-3", "Apply live-code format 3", ["Ctrl", "Shift", "3"]), - pair("live-code-layout", "Open the live-code layout menu", ["Ctrl", "Alt", "L"], ["Ctrl", "Option", "L"]), + pair("live-script-section", "Insert a line of text", ["Alt", "Enter"], ["Option", "Enter"]), + pair("live-editor-settings", "Apply Normal text format", ["Ctrl", "Alt", "T"], ["Ctrl", "Option", "T"]), + all("format-section-1", "Apply Heading 1 text format", ["Ctrl", "Shift", "1"]), + all("format-section-2", "Apply Heading 2 text format", ["Ctrl", "Shift", "2"]), + all("format-section-3", "Apply Heading 3 text format", ["Ctrl", "Shift", "3"]), + pair("live-code-layout", "Apply Title text format", ["Ctrl", "Alt", "L"], ["Ctrl", "Option", "L"]), pair("bold", "Toggle bold text", ["Ctrl", "B"], ["Cmd", "B"]), pair("italic", "Toggle italic text", ["Ctrl", "I"], ["Cmd", "I"]), - all("underline", "Toggle underlined text", ["Ctrl", "U"]), - pair("comment-lines", "Comment or uncomment selected lines", ["Ctrl", "R"], ["Cmd", "/"]), - pair("block-comment", "Comment or uncomment a block", ["Ctrl", "Shift", "R"], ["Cmd", "Option", "/"]), - pair("join-lines", "Join selected lines", ["Ctrl", "J"], ["Cmd", "Shift", "W"]), - pair("toggle-indent", "Toggle automatic indentation", ["Ctrl", "I"], ["Cmd", "I"]), + pair("underline", "Toggle underlined text", ["Ctrl", "U"], ["Cmd", "U"]), + pair("comment-lines", "Comment selected lines", ["Ctrl", "R"], ["Cmd", "/"]), + pair("block-comment", "Uncomment code", ["Ctrl", "Shift", "R"], ["Cmd", "Option", "/"]), + pair("join-lines", "Wrap comments", ["Ctrl", "J"], ["Cmd", "Shift", "W"]), + pair("toggle-indent", "Apply smart indentation", ["Ctrl", "I"], ["Cmd", "I"]), pair("indent-right", "Indent the selected lines right", ["Ctrl", "]"], ["Cmd", "]"]), pair("indent-left", "Indent the selected lines left", ["Ctrl", "["], ["Cmd", "["]), - pair("clear-line", "Clear the current line", ["Ctrl", "L"], ["Cmd", "C"]), - all("delete-line", "Delete the current line", ["Ctrl", "X"]), - all("copy-line", "Copy the current line", ["Ctrl", "Shift", "C"]), - all("select-line", "Select the current line", ["Ctrl", "Shift", "A"]), - pair("fold-code", "Fold the current code section", ["Ctrl", "Shift", "O"], ["Cmd", "Option", "O"]), - pair("toggle-comment", "Toggle the current block", ["Ctrl", "M"], ["Cmd", "M"]), + pair("clear-line", "Copy the current line", ["Ctrl", "L"], ["Cmd", "C"]), + pair("delete-line", "Cut the current line", ["Ctrl", "X"], ["Cmd", "X"]), + pair("copy-line", "Duplicate the current line", ["Ctrl", "Shift", "C"], ["Cmd", "Shift", "C"]), + pair("select-line", "Change the case of the selection", ["Ctrl", "Shift", "A"], ["Cmd", "Shift", "A"]), + pair("fold-code", "Move focus between code and output", ["Ctrl", "Shift", "O"], ["Cmd", "Option", "O"]), + pair("toggle-comment", "Open or expand a Code Analyzer message", ["Ctrl", "M"], ["Cmd", "M"]), ], }, { @@ -119,13 +119,45 @@ export const matlab: AppShortcuts = { pair("insert-section", "Insert a new code section", ["Ctrl", "Alt", "Enter"], ["Cmd", "Option", "Enter"]), pair("run-section", "Run the current section", ["Ctrl", "Enter"], ["Cmd", "Enter"]), pair("run-and-advance", "Run the current section and advance", ["Ctrl", "Shift", "Enter"], ["Cmd", "Shift", "Enter"]), - all("run-file", "Run the current file", ["F5"]), - pair("run-file-and-clear", "Run the file and clear the Command Window", ["Ctrl", "F5"], ["Cmd", "F5"]), - pair("run-selection", "Run the selected code", ["F12"], ["Cmd", "\\"]), - all("run-next-line", "Run the next line", ["F10"]), - all("run-next-section", "Run the next section", ["F11"]), - all("run-and-stop", "Run and stop at the next breakpoint", ["Shift", "F11"]), - all("stop-running", "Stop running code", ["Shift", "F5"]), + { + id: "run-file", + action: "Run the current file", + keys: perPlatform(platforms, { + windows: ["F5"], + linux: ["F5"], + macos: ["F5"], + }).map((entry) => + entry.platform === "macos" ? { ...entry, alternatives: [["Cmd", "Option", "R"]] } : entry, + ), + }, + pair("pause-running-file", "Pause the running file", ["Ctrl", "F5"], ["Cmd", "F5"]), + pair("run-selection", "Set or clear a breakpoint at the current line", ["F12"], ["Cmd", "\\"]), + { + id: "run-next-line", + action: "Step", + keys: perPlatform(platforms, { windows: ["F10"], linux: ["F10"], macos: ["F10"] }).map((entry) => + entry.platform === "macos" ? { ...entry, alternatives: [["Cmd", "Shift", "O"]] } : entry, + ), + }, + { + id: "run-next-section", + action: "Step in", + keys: perPlatform(platforms, { windows: ["F11"], linux: ["F11"], macos: ["F11"] }).map((entry) => + entry.platform === "macos" ? { ...entry, alternatives: [["Cmd", "Shift", "I"]] } : entry, + ), + }, + { + id: "step-out", + action: "Step out", + keys: perPlatform(platforms, { + windows: ["Shift", "F11"], + linux: ["Shift", "F11"], + macos: ["Shift", "F11"], + }).map((entry) => + entry.platform === "macos" ? { ...entry, alternatives: [["Cmd", "Shift", "U"]] } : entry, + ), + }, + all("stop-running", "Quit execution", ["Shift", "F5"]), pair("section-up", "Move to the previous section", ["Ctrl", "Up"], ["Cmd", "Up"]), pair("section-down", "Move to the next section", ["Ctrl", "Down"], ["Cmd", "Down"]), ], @@ -136,20 +168,28 @@ export const matlab: AppShortcuts = { intro: "Change the Editor view, fold code, check spelling, and move focus around the MATLAB desktop.", shortcuts: [ pair("toggle-full-screen", "Toggle full-screen Editor mode", ["Ctrl", "F11"], ["Cmd", "F11"]), - all("fold-level-1", "Fold one level", ["Ctrl", "Shift", "."]), - all("fold-level-2", "Fold two levels", ["Ctrl", "Shift", ".", "Ctrl", "Shift", "."]), - all("unfold-one-level", "Unfold one level", ["Ctrl", "Shift", ","]), - all("unfold-all", "Unfold all levels", ["Ctrl", "Shift", ",", "Ctrl", "Shift", ","]), + all("expand-current-fold", "Expand the current fold", ["Ctrl", "Shift", "."]), + all("collapse-current-fold", "Collapse the current fold", ["Ctrl", "."]), + all("expand-all-folds", "Expand all folds", ["Ctrl", "Shift", ","]), + all("collapse-all-folds", "Collapse all folds", ["Ctrl", ","]), pair("zoom-reset", "Reset the Editor zoom", ["Ctrl", "Alt", "0"], ["Cmd", "Option", "0"]), - windowsLinuxOnly("spell-check", "Open the spelling suggestions", ["Alt", "F7"]), - pair("spell-next", "Find the next spelling error", ["Shift", "F7"], ["Option", "F7"]), + pair("spell-next", "Find the next spelling issue", ["Alt", "F7"], ["Option", "F7"]), + pair("spell-previous", "Find the previous spelling issue", ["Alt", "Shift", "F7"], ["Option", "Shift", "F7"]), pair("next-desktop-group", "Move to the next desktop group", ["Ctrl", "F6"], ["Cmd", "F6"]), pair("previous-desktop-group", "Move to the previous desktop group", ["Ctrl", "Shift", "F6"], ["Cmd", "Shift", "F6"]), all("next-control", "Move focus to the next control", ["Tab"]), pair("search-desktop", "Search the MATLAB desktop", ["Ctrl", "Shift", "Space"], ["Cmd", "Shift", "Space"]), pair("preferences", "Open MATLAB Preferences", ["Ctrl", ","], ["Cmd", ","]), - all("shortcut-list", "Open the keyboard shortcut list", ["Ctrl", "/"]), - windowsLinuxOnly("interrupt", "Interrupt execution", ["Ctrl", "C"], "Ctrl+Break is also documented on Windows."), + { + id: "interrupt", + action: "Interrupt execution", + keys: perPlatform(platforms, { + windows: ["Ctrl", "C"], + linux: ["Ctrl", "C"], + }).map((entry) => entry.platform === "windows" + ? { ...entry, alternatives: [["Ctrl", "Break"]] } + : entry), + }, { id: "interrupt-macos", action: "Interrupt execution", keys: [{ platform: "macos", keys: ["Cmd", "."] }] }, ], }, diff --git a/lib/shortcuts/apps/mattermost.ts b/lib/shortcuts/apps/mattermost.ts index d81b9355..792cc274 100644 --- a/lib/shortcuts/apps/mattermost.ts +++ b/lib/shortcuts/apps/mattermost.ts @@ -23,10 +23,10 @@ export const mattermost: AppShortcuts = { category: "Team chat", platforms: [...platforms], versionContext: - "Current Mattermost keyboard shortcuts checked September 1, 2026. The official table covers Windows, Linux, and macOS. Some call controls depend on Mattermost Calls being enabled, and desktop tab commands apply to the Mattermost desktop app.", + "Mattermost keyboard shortcuts checked November 1, 2026. The official table covers Windows, Linux, and macOS. Some call controls depend on Mattermost Calls being enabled, and desktop tab commands apply to Mattermost desktop app 6.x.", sourceUrl: "https://docs.mattermost.com/end-user-guide/collaborate/keyboard-shortcuts.html", sourceLabel: "official Mattermost keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "channels-and-search", @@ -39,7 +39,7 @@ export const mattermost: AppShortcuts = { desktop("next-unread-channel", "Go to the next unread channel", ["Alt", "Shift", "Down"], ["Option", "Shift", "Down"]), desktop("find-channels", "Find channels or open a selected link", ["Ctrl", "K"], ["Cmd", "K"]), desktop("find-direct-messages", "Find direct messages", ["Ctrl", "Shift", "K"], ["Cmd", "Shift", "K"]), - desktop("channel-settings", "Open channel settings", ["Ctrl", "Shift", "A"], ["Cmd", "Shift", "A"]), + desktop("channel-settings", "Open the Settings dialog", ["Ctrl", "Shift", "A"], ["Cmd", "Shift", "A"]), desktop("mentions", "Open mentions", ["Ctrl", "Shift", "M"], ["Cmd", "Shift", "M"]), desktop("focus-message-input", "Focus the message input", ["Ctrl", "Shift", "L"], ["Cmd", "Shift", "L"]), desktop("toggle-right-sidebar", "Toggle the right sidebar", ["Ctrl", "."], ["Cmd", "."]), @@ -79,7 +79,15 @@ export const mattermost: AppShortcuts = { desktop("toggle-mute", "Mute or unmute yourself", ["Ctrl", "Shift", "Space"], ["Cmd", "Shift", "Space"]), desktop("raise-hand", "Raise or lower your hand", ["Ctrl", "Shift", "Y"], ["Cmd", "Shift", "Y"]), desktop("screen-share", "Start or stop screen sharing", ["Ctrl", "Shift", "E"], ["Cmd", "Shift", "E"]), - desktop("participants", "Open the participants list", ["Alt", "P"], ["Option", "P"], "Ctrl+Shift+P is also documented for Windows and Linux."), + { + id: "participants", + action: "Open the participants list", + keys: platforms.map((platform) => ({ + platform, + keys: platform === "macos" ? ["Option", "P"] : ["Alt", "P"], + alternatives: [platform === "macos" ? ["Cmd", "Shift", "P"] : ["Ctrl", "Shift", "P"]], + })), + }, desktop("leave-call", "Leave the call", ["Ctrl", "Shift", "L"], ["Cmd", "Shift", "L"]), shared("push-to-talk", "Temporarily unmute while held", ["Space"]), ], @@ -91,8 +99,8 @@ export const mattermost: AppShortcuts = { shortcuts: [ desktop("new-window", "Open a new Mattermost window", ["Ctrl", "N"], ["Cmd", "N"]), desktop("new-tab", "Open a new tab", ["Ctrl", "T"], ["Cmd", "T"]), - desktop("next-tab", "Switch to the next tab", ["Ctrl", "Tab"], ["Cmd", "Option", "Right"]), - desktop("previous-tab", "Switch to the previous tab", ["Ctrl", "Shift", "Tab"], ["Cmd", "Option", "Left"]), + desktop("next-tab", "Switch to the next tab", ["Ctrl", "Tab"], ["Cmd", "Tab"]), + desktop("previous-tab", "Switch to the previous tab", ["Ctrl", "Shift", "Tab"], ["Cmd", "Shift", "Tab"]), desktop("tab-1", "Switch to tab 1", ["Ctrl", "1"], ["Cmd", "1"]), desktop("tab-2", "Switch to tab 2", ["Ctrl", "2"], ["Cmd", "2"]), desktop("tab-3", "Switch to tab 3", ["Ctrl", "3"], ["Cmd", "3"]), diff --git a/lib/shortcuts/apps/microsoft-edge.ts b/lib/shortcuts/apps/microsoft-edge.ts index 9505c41b..102fee22 100644 --- a/lib/shortcuts/apps/microsoft-edge.ts +++ b/lib/shortcuts/apps/microsoft-edge.ts @@ -23,10 +23,10 @@ export const microsoftEdge: AppShortcuts = { category: "Web browser", platforms: [...platforms], versionContext: - "Current Microsoft Edge desktop shortcuts checked September 1, 2026. The official table has separate Windows and Mac columns; some commands depend on the page type, such as PDF viewing, and extensions can add conflicts.", + "Current Microsoft Edge desktop shortcuts checked November 1, 2026. The official table has separate Windows and Mac columns; some commands depend on the page type, such as PDF viewing, and extensions can add conflicts.", sourceUrl: "https://support.microsoft.com/en-US/edge/keyboard-shortcuts-in-microsoft-edge", sourceLabel: "official Microsoft Edge keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "tabs-and-windows", @@ -94,7 +94,6 @@ export const microsoftEdge: AppShortcuts = { pair("developer-console", "Open the developer console", ["Ctrl", "Shift", "J"], ["Cmd", "Option", "J"]), windowsOnly("feedback", "Send feedback", ["Alt", "Shift", "I"]), pair("focus-toolbar", "Focus the first item in the toolbar", ["Alt", "Shift", "T"], ["Cmd", "Option", "T"]), - pair("mute-tab", "Mute or unmute the current tab", ["Ctrl", "M"], ["Ctrl", "M"]), pair("profile", "Open the profile menu", ["Ctrl", "Shift", "M"], ["Cmd", "Shift", "M"]), windowsOnly("collections", "Open Collections", ["Ctrl", "Shift", "Y"]), pair("clear-browsing-data", "Open the clear browsing data dialog", ["Ctrl", "Shift", "Delete"], ["Cmd", "Shift", "Delete"]), diff --git a/lib/shortcuts/apps/microsoft-onedrive.ts b/lib/shortcuts/apps/microsoft-onedrive.ts index 00d6056b..2b1b6e74 100644 --- a/lib/shortcuts/apps/microsoft-onedrive.ts +++ b/lib/shortcuts/apps/microsoft-onedrive.ts @@ -13,10 +13,10 @@ export const microsoftOneDrive: AppShortcuts = { category: "Cloud storage", platforms: ["windows"], versionContext: - "Official OneDrive for work or school shortcuts checked September 1, 2026. The source uses Windows keyboard names and documents several commands specifically for the OneDrive web interface.", + "Official OneDrive for work or school shortcuts checked November 1, 2026. The source uses Windows keyboard names and documents several commands specifically for the OneDrive web interface.", sourceUrl: "https://support.microsoft.com/en-US/accessibility/onedrive/keyboard-shortcuts-for-onedrive-for-work-or-school", sourceLabel: "official Microsoft OneDrive keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "files-and-selection", diff --git a/lib/shortcuts/apps/microsoft-onenote.ts b/lib/shortcuts/apps/microsoft-onenote.ts index 2caa71ba..fcc69dfd 100644 --- a/lib/shortcuts/apps/microsoft-onenote.ts +++ b/lib/shortcuts/apps/microsoft-onenote.ts @@ -30,10 +30,10 @@ export const microsoftOneNote: AppShortcuts = { category: "Notes and writing", platforms: [...platforms], versionContext: - "Current Microsoft OneNote desktop shortcut tables checked September 1, 2026. Windows, macOS, and web versions have different maps, so this record separates the Windows and Mac desktop commands and leaves web-only access keys out.", + "Current Microsoft OneNote desktop shortcut tables checked November 1, 2026. Windows, macOS, and web versions have different maps, so this record separates the Windows and Mac desktop commands and leaves web-only access keys out.", sourceUrl: "https://support.microsoft.com/en-us/accessibility/onenote/keyboard-shortcuts-in-onenote", sourceLabel: "official Microsoft OneNote keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "editing", @@ -61,9 +61,17 @@ export const microsoftOneNote: AppShortcuts = { intro: "Use these commands in OneNote for Windows to open notes, dock the window, sync, and lock the notebook.", shortcuts: [ windowsOnly("new-window", "Open a new OneNote window", ["Ctrl", "M"]), - windowsOnly("quick-note", "Open a new Quick Note", ["Ctrl", "Shift", "M"], "Alt+Windows logo key+N is also documented."), + { + id: "quick-note", + action: "Open a new Quick Note", + keys: [{ platform: "windows", keys: ["Ctrl", "Shift", "M"], alternatives: [["Alt", "Win", "N"]] }], + }, windowsOnly("dock-window", "Dock the OneNote window", ["Ctrl", "Alt", "D"]), - windowsOnly("sync-current-windows", "Synchronize the current notebook", ["Ctrl", "S"], "Shift+F9 is also documented."), + { + id: "sync-current-windows", + action: "Synchronize the current notebook", + keys: [{ platform: "windows", keys: ["Ctrl", "S"], alternatives: [["Shift", "F9"]] }], + }, windowsOnly("sync-all-windows", "Synchronize all notebooks", ["F9"]), windowsOnly("lock-notebooks", "Lock password-protected sections", ["Ctrl", "Alt", "L"]), ], diff --git a/lib/shortcuts/apps/microsoft-outlook.ts b/lib/shortcuts/apps/microsoft-outlook.ts index 25b59157..62719691 100644 --- a/lib/shortcuts/apps/microsoft-outlook.ts +++ b/lib/shortcuts/apps/microsoft-outlook.ts @@ -13,10 +13,10 @@ export const microsoftOutlook: AppShortcuts = { category: "Email", platforms: ["windows"], versionContext: - "Current Microsoft Outlook shortcut documentation checked September 1, 2026. This record covers Windows desktop and web-oriented Windows commands from Microsoft's New Outlook and classic Outlook tables. Outlook for Mac has a separate official table and is not mixed into this Windows record.", + "Current Microsoft Outlook shortcut documentation checked November 1, 2026. This record covers Windows desktop and web-oriented Windows commands from Microsoft's New Outlook and classic Outlook tables. Outlook for Mac has a separate official table and is not mixed into this Windows record.", sourceUrl: "https://support.microsoft.com/en-us/office/keyboard-shortcuts-for-outlook-3cdeb221-7ae5-4c1d-8c1d-9e63216c1efd", sourceLabel: "official Microsoft Outlook keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "new-outlook", @@ -43,7 +43,7 @@ export const microsoftOutlook: AppShortcuts = { windows("bold", "Bold selected text", ["Ctrl", "B"]), windows("italic", "Italicize selected text", ["Ctrl", "I"]), windows("underline", "Underline selected text", ["Ctrl", "U"]), - windows("folder", "Open the Move to folder menu", ["Ctrl", "Y"]), + windows("folder", "Open the Folder pane", ["Ctrl", "Y"]), windows("previous-item", "Move to the previous item", ["Up"]), windows("next-item", "Move to the next item", ["Down"]), ], @@ -60,7 +60,7 @@ export const microsoftOutlook: AppShortcuts = { windows("insert-file", "Insert a file", ["Alt", "then", "N", "then", "A", "then", "F"]), windows("new-task", "Create a new task", ["Ctrl", "Shift", "K"]), windows("delete-message", "Delete the selected message", ["Delete"]), - windows("search", "Search Outlook", ["Ctrl", "E"], "F3 is also documented for search."), + { id: "search", action: "Search Outlook", keys: [{ platform: "windows", keys: ["Ctrl", "E"], alternatives: [["F3"]] }] }, windows("reply-message", "Reply to a message", ["Ctrl", "R"]), windows("forward-message", "Forward a message", ["Ctrl", "F"]), windows("reply-all-message", "Reply to all recipients", ["Ctrl", "Shift", "R"]), @@ -68,7 +68,7 @@ export const microsoftOutlook: AppShortcuts = { windows("calendar-classic", "Open Calendar", ["Ctrl", "2"]), windows("new-appointment", "Create an appointment", ["Ctrl", "Shift", "A"]), windows("address-book", "Open the Address Book", ["Ctrl", "Shift", "B"]), - windows("check-mail", "Check for new messages", ["Ctrl", "M"], "F9 is also documented."), + { id: "check-mail", action: "Check for new messages", keys: [{ platform: "windows", keys: ["Ctrl", "M"], alternatives: [["F9"]] }] }, windows("inbox", "Open the Inbox", ["Ctrl", "Shift", "I"]), windows("outbox", "Open the Outbox", ["Ctrl", "Shift", "O"]), windows("resolve-name", "Resolve a recipient name", ["Ctrl", "K"]), diff --git a/lib/shortcuts/apps/microsoft-powerpoint.ts b/lib/shortcuts/apps/microsoft-powerpoint.ts index c1c2b005..55d4a70e 100644 --- a/lib/shortcuts/apps/microsoft-powerpoint.ts +++ b/lib/shortcuts/apps/microsoft-powerpoint.ts @@ -37,10 +37,10 @@ export const microsoftPowerPoint: AppShortcuts = { category: "Presentation software", platforms: [...platforms], versionContext: - "Current Microsoft PowerPoint creation and presentation shortcuts checked September 1, 2026. The official creation table is Windows-focused, while the delivery table documents Windows and Mac separately. Ribbon access keys and presentation commands can vary with the PowerPoint version and keyboard layout.", + "Current Microsoft PowerPoint creation and presentation shortcuts checked November 1, 2026. The official creation table is Windows-focused, while the delivery table documents Windows and Mac separately. Ribbon access keys and presentation commands can vary with the PowerPoint version and keyboard layout.", sourceUrl: "https://support.microsoft.com/en-us/office/use-keyboard-shortcuts-to-deliver-powerpoint-presentations-1524ffce-bd2a-45f4-9a7f-f18b992b93a0", sourceLabel: "official Microsoft PowerPoint presentation shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "windows-authoring", @@ -60,11 +60,11 @@ export const microsoftPowerPoint: AppShortcuts = { windowsOnly("view-tab", "Open the View tab", ["Alt", "then", "W"]), windowsOnly("record-tab", "Open the Record tab", ["Alt", "then", "E"]), windowsOnly("help-tab", "Open PowerPoint help", ["Alt", "then", "Y", "then", "2"]), - windowsOnly("ribbon-focus", "Show ribbon key tips", ["Alt"], "F10 is also documented."), + { id: "ribbon-focus", action: "Select the active ribbon tab and activate access keys", keys: [{ platform: "windows", keys: ["Alt"], alternatives: [["F10"]] }] }, windowsOnly("next-ribbon-control", "Move to the next ribbon control", ["Tab"]), windowsOnly("previous-ribbon-control", "Move to the previous ribbon control", ["Shift", "Tab"]), windowsOnly("ribbon-tooltip", "Show the tooltip for the focused ribbon control", ["Ctrl", "Shift", "F10"]), - windowsOnly("activate-control", "Activate the focused control", ["Space"], "Enter is also documented."), + { id: "activate-control", action: "Activate the focused control", keys: [{ platform: "windows", keys: ["Space"], alternatives: [["Enter"]] }] }, windowsOnly("open-list", "Open the selected list", ["Down"]), windowsOnly("open-menu", "Open the selected menu", ["Alt", "Down"]), windowsOnly("toggle-ribbon", "Expand or collapse the ribbon", ["Ctrl", "F1"]), @@ -99,21 +99,82 @@ export const microsoftPowerPoint: AppShortcuts = { macOnly("start-show-macos", "Start the slide show from the beginning", ["Cmd", "Shift", "Return"]), macOnly("start-current-slide-macos", "Start the slide show from the current slide", ["Cmd", "Return"]), macOnly("presenter-view-macos", "Start Presenter view", ["Option", "Return"]), - both("next-slide", "Advance to the next slide", ["N"], "Enter, Page Down, Right, Down, or Space also advances."), - both("previous-slide", "Return to the previous slide", ["P"], "Page Up, Left, Up, or Backspace also returns."), - both("first-slide", "Go to the first slide", ["Home"]), - both("last-slide", "Go to the last slide", ["End"]), - both("specific-slide", "Go to a specific slide", ["Number", "then", "Enter"]), - both("end-show", "End the slide show", ["Esc"], "The hyphen key also ends a show on Windows; Command+Period is documented on Mac."), + { + id: "next-slide", + action: "Advance to the next slide", + keys: platforms.map((platform) => ({ + platform, + keys: ["N"], + alternatives: [["Enter"], ["Page Down"], ["Right"], ["Down"], ["Space"]], + })), + }, + { + id: "previous-slide", + action: "Return to the previous slide", + keys: [ + { + platform: "windows", + keys: ["P"], + alternatives: [["Page Up"], ["Left"], ["Up"], ["Backspace"]], + }, + { + platform: "macos", + keys: ["P"], + alternatives: [["Page Up"], ["Left"], ["Up"], ["Delete"]], + }, + ], + }, + { + id: "first-slide", + action: "Go to the first slide", + keys: [ + { platform: "windows", keys: ["Home"] }, + { platform: "macos", keys: ["Fn", "Left"] }, + ], + }, + { + id: "last-slide", + action: "Go to the last slide", + keys: [ + { platform: "windows", keys: ["End"] }, + { platform: "macos", keys: ["Fn", "Right"] }, + ], + }, + { + id: "specific-slide", + action: "Go to a specific slide", + keys: [ + { platform: "windows", keys: ["Number", "then", "Enter"] }, + { platform: "macos", keys: ["Number", "then", "Return"] }, + ], + }, + { + id: "end-show", + action: "End the slide show", + keys: [ + { platform: "windows", keys: ["Esc"] }, + { platform: "macos", keys: ["Esc"], alternatives: [["-"], ["Cmd", "."]] }, + ], + }, windowsOnly("all-slides", "Show all slides", ["Ctrl", "S"]), - both("help-during-show", "Show slide show help", ["F1"]), - both("toggle-pointer", "Show or hide the pointer", ["Ctrl", "H"], "On Mac, use Command+I."), - pair("toggle-black-screen", "Show a black screen", ["B"], ["B"]), - pair("toggle-white-screen", "Show a white screen", ["W"], ["W"]), - macOnly("black-screen-alternative", "Show a black screen", ["Shift", "B"]), - macOnly("white-screen-alternative", "Show a white screen", ["Shift", "W"]), - macOnly("first-slide-macos", "Go to the first slide", ["Function", "Left"]), - macOnly("last-slide-macos", "Go to the last slide", ["Function", "Right"]), + windowsOnly("help-during-show", "Show slide show help", ["F1"]), + pair("toggle-pointer", "Hide the pointer", ["Ctrl", "H"], ["Cmd", "I"]), + { + id: "toggle-black-screen", + action: "Show or return from a black screen", + keys: [ + { platform: "windows", keys: ["B"], alternatives: [["."]] }, + { platform: "macos", keys: ["B"], alternatives: [["Shift", "B"], ["."]] }, + ], + }, + { + id: "toggle-white-screen", + action: "Show or return from a white screen", + keys: [ + { platform: "windows", keys: ["W"], alternatives: [[","]] }, + { platform: "macos", keys: ["W"], alternatives: [["Shift", "W"], [","]] }, + ], + }, both("show-hotspots", "Show clickable hotspots", ["Tab"]), ], }, diff --git a/lib/shortcuts/apps/microsoft-powertoys.ts b/lib/shortcuts/apps/microsoft-powertoys.ts index cc01ee4c..03c7f1bd 100644 --- a/lib/shortcuts/apps/microsoft-powertoys.ts +++ b/lib/shortcuts/apps/microsoft-powertoys.ts @@ -13,10 +13,10 @@ export const microsoftPowerToys: AppShortcuts = { category: "Windows utilities", platforms: ["windows"], versionContext: - "Current Microsoft PowerToys documentation checked September 1, 2026. These are the documented defaults, but every utility can be disabled or remapped in PowerToys settings and the set may change as utilities evolve.", + "Current Microsoft PowerToys documentation checked November 1, 2026. These are documented defaults, but utilities can be disabled or remapped in PowerToys settings.", sourceUrl: "https://learn.microsoft.com/en-us/windows/powertoys/shortcut-guide", sourceLabel: "official Microsoft PowerToys documentation", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "shortcut-guide", @@ -57,16 +57,13 @@ export const microsoftPowerToys: AppShortcuts = { intro: "Open the layout editor and move windows into zones using the default FancyZones bindings.", shortcuts: [ windows("open-zones-editor", "Open the FancyZones editor", ["Win", "Shift", "`"]), - windows("move-window-left", "Move the focused window to the zone on the left", ["Win", "Left"]), - windows("move-window-right", "Move the focused window to the zone on the right", ["Win", "Right"]), - windows("move-window-up", "Move the focused window to the zone above", ["Win", "Up"]), - windows("move-window-down", "Move the focused window to the zone below", ["Win", "Down"]), + windows("move-window-left", "Move the focused window to the zone on the left", ["Win", "Left"], "Requires the FancyZones Override Windows Snap hotkeys setting."), + windows("move-window-right", "Move the focused window to the zone on the right", ["Win", "Right"], "Requires the FancyZones Override Windows Snap hotkeys setting."), + windows("move-window-up", "Move the focused window to the zone above", ["Win", "Up"], "Requires Override Windows Snap hotkeys with Relative position selected."), + windows("move-window-down", "Move the focused window to the zone below", ["Win", "Down"], "Requires Override Windows Snap hotkeys with Relative position selected."), windows("switch-window-up", "Switch windows in the zone above", ["Win", "Page Up"]), windows("switch-window-down", "Switch windows in the zone below", ["Win", "Page Down"]), - windows("custom-layout", "Apply a custom layout", ["Win", "Ctrl", "Alt", "Number"]), - windows("next-zone", "Move to the next zone in the editor", ["Tab"]), - windows("divide-zone", "Divide a zone in the editor", ["S"]), - windows("merge-zones", "Merge selected zones in the editor", ["Delete"]), + windows("custom-layout", "Apply a custom layout", ["Win", "Ctrl", "Alt", "Number"], "Assign a number to the custom layout in the FancyZones editor first."), ], }, { diff --git a/lib/shortcuts/apps/microsoft-teams.ts b/lib/shortcuts/apps/microsoft-teams.ts index 7f757452..e8062388 100644 --- a/lib/shortcuts/apps/microsoft-teams.ts +++ b/lib/shortcuts/apps/microsoft-teams.ts @@ -39,17 +39,24 @@ export const microsoftTeams: AppShortcuts = { category: "Team chat", platforms: [...platforms], versionContext: - "Current Microsoft Teams desktop and web shortcut tables checked September 1, 2026. Several commands have separate desktop and web forms, and Teams can reserve or customize shortcuts depending on the client and account.", + "Current Microsoft Teams desktop and web shortcut tables checked November 1, 2026. Several commands have separate desktop and web forms, and Teams can reserve or customize shortcuts depending on the client and account.", sourceUrl: "https://support.microsoft.com/en-us/accessibility/teams/keyboard-shortcuts-for-microsoft-teams", sourceLabel: "official Microsoft Teams keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "navigation-and-search", title: "Navigation and search", intro: "Search Teams, open a chat or channel, filter results, and move focus through the application shell.", shortcuts: [ - pair("shortcut-help", "Open keyboard shortcut help", ["Ctrl", "."], ["Cmd", "."], "Alt+/ is also documented on Windows; Command+Period is the Mac desktop form."), + { + id: "shortcut-help", + action: "Open keyboard shortcut help", + keys: [ + { platform: "windows", keys: ["Ctrl", "."], alternatives: [["Alt", "/"]] }, + { platform: "macos", keys: ["Cmd", "."] }, + ], + }, desktopWeb("search", "Search Teams", ["Ctrl", "E"], ["Cmd", "E"], ["Ctrl", "Alt", "E"], ["Option", "Cmd", "E"]), pair("filter", "Filter the current view", ["Ctrl", "Shift", "F"], ["Cmd", "Shift", "F"]), desktopWeb("new-chat", "Start a new chat", ["Ctrl", "N"], ["Cmd", "N"], ["Alt", "N"], ["Ctrl", "Cmd", "N"]), @@ -90,9 +97,8 @@ export const microsoftTeams: AppShortcuts = { pair("mute", "Mute or unmute the microphone", ["Ctrl", "Shift", "M"], ["Cmd", "Shift", "M"]), pair("camera", "Turn the camera on or off", ["Ctrl", "Shift", "O"], ["Cmd", "Shift", "O"]), pair("raise-hand", "Raise or lower your hand", ["Ctrl", "Shift", "K"], ["Cmd", "Shift", "K"]), - pair("screen-share", "Start or stop screen sharing", ["Ctrl", "Shift", "E"], ["Cmd", "Shift", "E"]), - pair("background", "Open background effects", ["Ctrl", "Shift", "P"], ["Cmd", "Shift", "P"]), - pair("captions", "Turn live captions on or off", ["Ctrl", "Shift", "C"], ["Cmd", "Shift", "C"]), + pair("screen-share", "Toggle the share content tray", ["Ctrl", "Shift", "E"], ["Cmd", "Shift", "E"]), + pair("captions", "Turn live captions on or off", ["Ctrl", "Shift", "A"], ["Cmd", "Shift", "A"]), ], }, ], diff --git a/lib/shortcuts/apps/microsoft-word.ts b/lib/shortcuts/apps/microsoft-word.ts index 1ef894a7..80ec1f2a 100644 --- a/lib/shortcuts/apps/microsoft-word.ts +++ b/lib/shortcuts/apps/microsoft-word.ts @@ -30,10 +30,10 @@ export const microsoftWord: AppShortcuts = { category: "Word processing", platforms: [...platforms], versionContext: - "Current Microsoft Word shortcut tables checked September 1, 2026. The official article uses U.S. keyboard layouts and separate Windows and Mac sections. Web, mobile, add-in, and custom shortcut behavior is outside this desktop record.", + "Current Microsoft Word shortcut tables checked November 1, 2026. The official article uses U.S. keyboard layouts and separate Windows and Mac sections. Web, mobile, add-in, and custom shortcut behavior is outside this desktop record.", sourceUrl: "https://support.microsoft.com/en-us/accessibility/word/keyboard-shortcuts-in-word", sourceLabel: "official Microsoft Word keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "document-and-editing", @@ -56,7 +56,7 @@ export const microsoftWord: AppShortcuts = { pair("find", "Find text", ["Ctrl", "F"], ["Cmd", "F"]), pair("replace", "Find and replace text", ["Ctrl", "H"], ["Control", "H"]), windowsOnly("go-to", "Open the Go To dialog", ["Ctrl", "G"]), - macOnly("go-to-macos", "Open the Go To dialog", ["Cmd", "Option", "G"], "F5 is also documented."), + { id: "go-to-macos", action: "Open the Go To dialog", keys: [{ platform: "macos", keys: ["Cmd", "Option", "G"], alternatives: [["F5"]] }] }, pair("cancel", "Cancel a command", ["Esc"], ["Esc"]), pair("split-window", "Split the document window", ["Ctrl", "Alt", "S"], ["Cmd", "Option", "S"]), ], diff --git a/lib/shortcuts/apps/milanote.ts b/lib/shortcuts/apps/milanote.ts index 16c24c66..829acf22 100644 --- a/lib/shortcuts/apps/milanote.ts +++ b/lib/shortcuts/apps/milanote.ts @@ -10,16 +10,22 @@ const dual = (id: string, action: string, windows: string[], macos: string[], no ...(note ? { note } : {}), }); +const macOnly = (id: string, action: string, keys: string[]): Shortcut => ({ + id, + action, + keys: [{ platform: "macos", keys }], +}); + export const milanote: AppShortcuts = { slug: "milanote", name: "Milanote", category: "Notes and docs", platforms: [...platforms], versionContext: - "Current Milanote desktop and browser shortcuts checked September 1, 2026. Milanote also supports an in-app shortcut menu, and several actions depend on whether a note, board, table, or drawing is selected.", + "Current Milanote desktop and browser shortcuts checked November 1, 2026. Milanote also supports an in-app shortcut menu, and several actions depend on whether a note, board, table, or drawing is selected.", sourceUrl: "https://help.milanote.com/en/articles/111393-keyboard-shortcuts", sourceLabel: "official Milanote keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "basics", @@ -32,13 +38,13 @@ export const milanote: AppShortcuts = { dual("redo", "Redo", ["Ctrl", "Shift", "Z"], ["Cmd", "Shift", "Z"]), dual("select-all", "Select all", ["Ctrl", "A"], ["Cmd", "A"]), dual("stop-editing", "Deselect or stop editing", ["Escape"], ["Escape"]), - dual("delete", "Delete an object", ["Delete"], ["Delete"]), + macOnly("delete", "Delete an object", ["Delete"]), dual("duplicate", "Duplicate by dragging", ["Alt", "drag"], ["Option", "drag"]), dual("search", "Search or view recent boards", ["Ctrl", "F"], ["Cmd", "F"]), - dual("zoom", "Zoom", ["Z"], ["Z"]), - dual("pan", "Move around the board", ["Space", "drag"], ["Space", "drag"]), - dual("nudge", "Nudge an object", ["Arrow keys"], ["Arrow keys"]), - dual("nudge-more", "Move an object farther", ["Shift", "Arrow keys"], ["Shift", "Arrow keys"]), + macOnly("zoom", "Zoom", ["Z"]), + macOnly("pan", "Move around the board", ["Space", "drag"]), + macOnly("nudge", "Nudge an object", ["Arrow keys"]), + macOnly("nudge-more", "Move an object farther", ["Shift", "Arrow keys"]), ], }, { diff --git a/lib/shortcuts/apps/miro.ts b/lib/shortcuts/apps/miro.ts index 57f752ec..58ff0db9 100644 --- a/lib/shortcuts/apps/miro.ts +++ b/lib/shortcuts/apps/miro.ts @@ -37,17 +37,17 @@ export const miro: AppShortcuts = { category: "Design tool", platforms: [...platforms], versionContext: - "Current Miro browser and desktop shortcuts checked September 1, 2026. Miro lets users turn single-character shortcuts off from Accessibility, and the desktop app adds reload, tab, exit, and board-link commands.", + "Current Miro browser and desktop shortcuts checked November 1, 2026. Miro lets users turn single-character shortcuts off from Accessibility, and the desktop app adds reload, tab, exit, and board-link commands.", sourceUrl: "https://help.miro.com/hc/en-us/articles/360017731033-Shortcuts-and-hotkeys", sourceLabel: "official Miro shortcuts and hotkeys", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "tools", title: "Board tools", intro: "Single keys switch the active board tool. Miro documents these as defaults that can be disabled together for assistive technology use.", shortcuts: [ - dualAlt("select-hand", "Select tool or hand mode", ["V"], ["V"], [["/"], ["H"]], [["/"], ["H"]]), + dualAlt("select-hand", "Select tool or hand mode", ["V"], ["V"], [["H"]], [["H"]]), dual("text", "Text tool", ["T"], ["T"]), dual("sticky-notes", "Sticky notes", ["N"], ["N"]), dual("shapes", "Shapes", ["S"], ["S"]), @@ -123,7 +123,7 @@ export const miro: AppShortcuts = { dual("toolbar-next", "Move between toolbars", ["Tab"], ["Tab"]), dual("toolbar-previous", "Move back between toolbars", ["Shift", "Tab"], ["Shift", "Tab"]), dual("toolbar-item", "Move between toolbar items", ["Arrow keys"], ["Arrow keys"]), - dualAlt("activate", "Activate a menu item", ["Enter"], ["Enter"], [["/"], ["Space"]], [["/"], ["Space"]]), + dualAlt("activate", "Activate a menu item", ["Enter"], ["Enter"], [["Space"]], [["Space"]]), dual("bulk-sticky", "Create a sticky note in bulk mode", ["Enter"], ["Return"]), dual("exit-bulk", "Exit sticky-note bulk mode", ["Esc"], ["Esc"]), dual("card-panel", "Open the card panel", ["Ctrl", "Shift", "Enter"], ["Cmd", "Shift", "Enter"]), diff --git a/lib/shortcuts/apps/monday-com.ts b/lib/shortcuts/apps/monday-com.ts index 7e5ca45e..096dcb2c 100644 --- a/lib/shortcuts/apps/monday-com.ts +++ b/lib/shortcuts/apps/monday-com.ts @@ -16,10 +16,10 @@ export const mondayCom: AppShortcuts = { category: "Project management", platforms: [...platforms], versionContext: - "Current monday.com web shortcuts checked September 1, 2026. The shortcut list is available from the account menu or Shift + ?. Labels differ slightly between Mac and PC, but the actions are shared.", + "Current monday.com web shortcuts checked November 1, 2026. The shortcut list is available from the account menu or Shift + ?. Labels differ slightly between Mac and PC, but the actions are shared.", sourceUrl: "https://support.monday.com/hc/en-us/articles/115005339905-monday-com-Shortcuts", sourceLabel: "official monday.com shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "system-navigation", diff --git a/lib/shortcuts/apps/mozilla-thunderbird.ts b/lib/shortcuts/apps/mozilla-thunderbird.ts index 47ea0b66..c29cca08 100644 --- a/lib/shortcuts/apps/mozilla-thunderbird.ts +++ b/lib/shortcuts/apps/mozilla-thunderbird.ts @@ -21,17 +21,25 @@ export const mozillaThunderbird: AppShortcuts = { category: "Email", platforms: [...platforms], versionContext: - "Current Thunderbird desktop shortcuts checked September 1, 2026. The official table was updated April 2, 2026. Add-ons can customize or disable some shortcuts, and macOS laptops may need Fn for function keys.", + "Current Thunderbird desktop shortcuts checked November 1, 2026. The official table was updated April 2, 2026. Add-ons can customize or disable some shortcuts, and macOS laptops may need Fn for function keys.", sourceUrl: "https://support.mozilla.org/en-US/kb/keyboard-shortcuts-thunderbird", sourceLabel: "official Thunderbird keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "messages", title: "Messages", intro: "Use these commands to compose, reply, read, mark, move, and remove messages from the message list or an open message.", shortcuts: [ - dual("new-message", "Start a new message", ["Ctrl", "M"], ["Cmd", "N"]), + { + id: "new-message", + action: "Start a new message", + keys: [ + { platform: "windows", keys: ["Ctrl", "M"], alternatives: [["Ctrl", "N"]] }, + { platform: "linux", keys: ["Ctrl", "M"], alternatives: [["Ctrl", "N"]] }, + { platform: "macos", keys: ["Cmd", "N"] }, + ], + }, dual("reply", "Reply to the sender", ["Ctrl", "R"], ["Cmd", "R"]), dual("reply-all", "Reply to all recipients", ["Ctrl", "Shift", "R"], ["Cmd", "Shift", "R"]), dual("reply-list", "Reply to the list", ["Ctrl", "Shift", "L"], ["Cmd", "Shift", "L"]), @@ -62,8 +70,24 @@ export const mozillaThunderbird: AppShortcuts = { desktop("next-unread", "Go to the next unread message", ["N"]), desktop("previous-unread", "Go to the previous unread message", ["P"]), desktop("next-unread-thread", "Go to the next unread thread", ["T"]), - desktop("first-message", "Go to the first message", ["Home"]), - desktop("last-message", "Go to the last message", ["End"]), + { + id: "first-message", + action: "Go to the first message", + keys: [ + { platform: "windows", keys: ["Home"] }, + { platform: "linux", keys: ["Home"] }, + { platform: "macos", keys: ["Fn", "Left"] }, + ], + }, + { + id: "last-message", + action: "Go to the last message", + keys: [ + { platform: "windows", keys: ["End"] }, + { platform: "linux", keys: ["End"] }, + { platform: "macos", keys: ["Fn", "Right"] }, + ], + }, dual("next-tab", "Move to the next tab", ["Ctrl", "Tab"], ["Cmd", "Option", "Right"]), dual("previous-tab", "Move to the previous tab", ["Ctrl", "Shift", "Tab"], ["Cmd", "Option", "Left"]), dual("address-book", "Open the address book", ["Ctrl", "Shift", "B"], ["Cmd", "Shift", "B"]), diff --git a/lib/shortcuts/apps/neovim.ts b/lib/shortcuts/apps/neovim.ts index 2b56e011..b21afc48 100644 --- a/lib/shortcuts/apps/neovim.ts +++ b/lib/shortcuts/apps/neovim.ts @@ -122,7 +122,7 @@ export const neovim: AppShortcuts = { { id: "window-down", action: "Move to the window below", keys: combo(platforms, ["Ctrl", "w", "j"]) }, { id: "window-up", action: "Move to the window above", keys: combo(platforms, ["Ctrl", "w", "k"]) }, { id: "window-right", action: "Move to the window on the right", keys: combo(platforms, ["Ctrl", "w", "l"]) }, - { id: "open-terminal", action: "Open a terminal buffer", keys: combo(platforms, [":", "terminal"]), note: "Press Enter after the :terminal command." }, + { id: "open-terminal", action: "Open a terminal buffer", keys: combo(platforms, [":", "terminal", "then", "Enter"]) }, { id: "terminal-to-normal", action: "Leave terminal mode for normal mode", keys: combo(platforms, ["Ctrl", "\\", "Ctrl", "n"]) }, { id: "shell-command-prev", action: "Jump to the previous shell command", keys: combo(platforms, ["[", "["]), note: "Requires shell integration that emits OSC 133 markers." }, { id: "shell-command-next", action: "Jump to the next shell command", keys: combo(platforms, ["]", "]"]), note: "Requires shell integration that emits OSC 133 markers." }, diff --git a/lib/shortcuts/apps/netflix.ts b/lib/shortcuts/apps/netflix.ts index 2e43489c..aade93f6 100644 --- a/lib/shortcuts/apps/netflix.ts +++ b/lib/shortcuts/apps/netflix.ts @@ -16,10 +16,10 @@ export const netflix: AppShortcuts = { category: "Video streaming", platforms: [...platforms], versionContext: - "Netflix Help Center keyboard shortcuts checked September 1, 2026. The shortcuts apply while watching on a computer; Space and Escape also work in the Windows 10 and Windows 11 app where Netflix says so.", + "Netflix Help Center keyboard shortcuts checked November 1, 2026. The shortcuts apply while watching on a computer; Space and Escape also work in the Windows 10 and Windows 11 app where Netflix says so.", sourceUrl: "https://help.netflix.com/en/node/24855", sourceLabel: "official Netflix keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "player", diff --git a/lib/shortcuts/apps/notability.ts b/lib/shortcuts/apps/notability.ts index 6a7edcad..0fa3e72c 100644 --- a/lib/shortcuts/apps/notability.ts +++ b/lib/shortcuts/apps/notability.ts @@ -16,10 +16,10 @@ export const notability: AppShortcuts = { category: "Notes and docs", platforms: [...platforms], versionContext: - "Notability keyboard shortcuts checked September 1, 2026. This record uses the official Mac table and the toolbox, color, size, and content manager rows. The separate iPad table is outside this catalog's Windows, macOS, and Linux platform model.", + "Notability keyboard shortcuts checked November 1, 2026. This record uses the official Mac table and the toolbox, color, size, and content manager rows. The separate iPad table is outside this catalog's Windows, macOS, and Linux platform model.", sourceUrl: "https://support.gingerlabs.com/hc/en-us/articles/360021489291-Keyboard-shortcuts", sourceLabel: "official Notability keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "notes-and-navigation", diff --git a/lib/shortcuts/apps/notion.ts b/lib/shortcuts/apps/notion.ts index d9a2e260..2248112b 100644 --- a/lib/shortcuts/apps/notion.ts +++ b/lib/shortcuts/apps/notion.ts @@ -55,10 +55,10 @@ export const notion: AppShortcuts = { title: "Markdown and formatting", intro: "Use typed Markdown markers, selected-text formatting, and block-type chords to create structure without opening the toolbar.", shortcuts: [ - { id: "typed-bold", action: "Type bold Markdown", keys: combo(platforms, ["*", "*"]), note: "Type two asterisks on both sides of the text." }, - { id: "typed-italic", action: "Type italic Markdown", keys: combo(platforms, ["*"]), note: "Type one asterisk on both sides of the text." }, - { id: "typed-inline-code", action: "Type inline code Markdown", keys: combo(platforms, ["`"]), note: "Type a backtick on both sides of the text." }, - { id: "typed-strikethrough", action: "Type strikethrough Markdown", keys: combo(platforms, ["~"]), note: "Type a tilde on both sides of the text." }, + { id: "typed-bold", action: "Type bold Markdown", keys: combo(platforms, ["*", "*", "then", "{text}", "then", "*", "*"]) }, + { id: "typed-italic", action: "Type italic Markdown", keys: combo(platforms, ["*", "then", "{text}", "then", "*"]) }, + { id: "typed-inline-code", action: "Type inline code Markdown", keys: combo(platforms, ["`", "then", "{text}", "then", "`"]) }, + { id: "typed-strikethrough", action: "Type strikethrough Markdown", keys: combo(platforms, ["~", "then", "{text}", "then", "~"]) }, { id: "typed-bullet", action: "Start a bulleted list", keys: combo(platforms, ["*", "Space"]), note: "A hyphen or plus sign followed by Space works too." }, { id: "typed-todo", action: "Start a to-do checkbox", keys: combo(platforms, ["[", "]", "Space"]) }, { id: "typed-numbered", action: "Start a numbered list", keys: combo(platforms, ["1", ".", "Space"]), note: "a. and i. followed by Space also create numbered lists." }, diff --git a/lib/shortcuts/apps/obs-studio.ts b/lib/shortcuts/apps/obs-studio.ts index d8276635..9f6f249c 100644 --- a/lib/shortcuts/apps/obs-studio.ts +++ b/lib/shortcuts/apps/obs-studio.ts @@ -9,10 +9,10 @@ export const obsStudio: AppShortcuts = { category: "Streaming and recording", platforms: [...platforms], versionContext: - "The official OBS keyboard reference was last updated August 25, 2021 and was checked again September 1, 2026. OBS lets users assign or change hotkeys in Settings, so these entries describe the published defaults rather than a user's custom profile.", + "The official OBS keyboard reference was last updated August 25, 2021 and was checked again November 1, 2026. OBS lets users assign or change hotkeys in Settings, so these entries describe the published defaults rather than a user's custom profile.", sourceUrl: "https://obsproject.com/kb/keyboard-shortcuts", sourceLabel: "official OBS Studio keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "general", diff --git a/lib/shortcuts/apps/omnifocus.ts b/lib/shortcuts/apps/omnifocus.ts index 132a053d..4abb17cd 100644 --- a/lib/shortcuts/apps/omnifocus.ts +++ b/lib/shortcuts/apps/omnifocus.ts @@ -13,10 +13,10 @@ export const omnifocus: AppShortcuts = { category: "Task management", platforms: ["macos"], versionContext: - "OmniFocus 4.8.11 Mac shortcuts from the official reference manual, checked September 1, 2026. Some quick-entry and perspective shortcuts can be customized, and the manual includes separate bindings for iPad and the web app.", - sourceUrl: "https://support.omnigroup.com/documentation/omnifocus/universal/4.8.11/en/commands/", + "OmniFocus 4.9.3 Mac shortcuts from the official reference manual, checked November 1, 2026. Some Quick Entry and perspective shortcuts can be customized, and the manual includes separate bindings for iPad and the web app.", + sourceUrl: "https://support.omnigroup.com/documentation/omnifocus/universal/4.9.3/en/commands/", sourceLabel: "official OmniFocus 4 commands and shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "quick-entry-and-items", @@ -78,9 +78,9 @@ export const omnifocus: AppShortcuts = { mac("move-down", "Move the selection down", ["Shift", "Down"]), mac("flag", "Set or clear a flag", ["F"], [["Shift", "Cmd", "L"]]), mac("forecast-tag", "Apply or remove the Forecast tag", ["Control", "Cmd", "L"]), - mac("show-sidebar", "Show or hide the sidebar", ["Shift", "S"]), - mac("show-inspector", "Show or hide the inspector", ["Shift", "I"]), - mac("view-options", "Show or hide view options", ["V"]), + mac("show-sidebar", "Show or hide the sidebar", ["Option", "Cmd", "S"]), + mac("show-inspector", "Show or hide the inspector", ["Option", "Cmd", "I"]), + mac("view-options", "Show or hide view options", ["Shift", "Cmd", "V"]), ], }, { diff --git a/lib/shortcuts/apps/opencode.ts b/lib/shortcuts/apps/opencode.ts index 99d2f5bb..552971a4 100644 --- a/lib/shortcuts/apps/opencode.ts +++ b/lib/shortcuts/apps/opencode.ts @@ -17,10 +17,10 @@ export const opencode: AppShortcuts = { category: "AI coding agent", platforms: [...platforms], versionContext: - "Current default keybinds from opencode.ai, last updated August 25, 2026. The leader is Ctrl+X, leader chords are sequential, and every TUI binding can be remapped in tui.json.", + "Current default keybinds from opencode.ai checked November 1, 2026. The leader is Ctrl+X, leader chords are sequential, and every TUI binding can be remapped in tui.json.", sourceUrl: "https://opencode.ai/docs/keybinds", sourceLabel: "official OpenCode keybinds reference", - lastVerified: "2026-08-26", + lastVerified: "2026-11-01", groups: [ { id: "leader-and-app", @@ -124,7 +124,11 @@ export const opencode: AppShortcuts = { { id: "input-delete-start", action: "Delete to the start of the line", keys: combo(platforms, ["Ctrl", "U"]) }, { id: "input-backspace", action: "Delete the previous character", keys: bindings(["Backspace"], [["Shift", "Backspace"]]) }, { id: "input-delete", action: "Delete the next character", keys: bindings(["Ctrl", "D"], [["Delete"], ["Shift", "Delete"]]) }, - { id: "input-undo", action: "Undo prompt editing", keys: bindings(["Ctrl", "-"], [["Super", "Z"]]), note: "Super is the platform's Super or Command key." }, + { id: "input-undo", action: "Undo prompt editing", keys: platforms.map((platform) => ({ + platform, + keys: ["Ctrl", "-"], + alternatives: platform === "windows" ? [["Ctrl", "Z"], ["Super", "Z"]] : [["Super", "Z"]], + })), note: "Windows also adds Ctrl+Z by default. Super is the platform's Super or Command key." }, { id: "input-redo", action: "Redo prompt editing", keys: bindings(["Ctrl", "."], [["Super", "Shift", "Z"]]), note: "Super is the platform's Super or Command key." }, { id: "input-word-forward", action: "Move forward one word", keys: bindings(["Alt", "F"], [["Alt", "Right"], ["Ctrl", "Right"]]) }, { id: "input-word-backward", action: "Move backward one word", keys: bindings(["Alt", "B"], [["Alt", "Left"], ["Ctrl", "Left"]]) }, diff --git a/lib/shortcuts/apps/opera.ts b/lib/shortcuts/apps/opera.ts index 62fb1c0e..1b0fab17 100644 --- a/lib/shortcuts/apps/opera.ts +++ b/lib/shortcuts/apps/opera.ts @@ -1,13 +1,14 @@ -import { perPlatform } from "../build.ts"; -import type { AppShortcuts, Shortcut } from "../types.ts"; +import type { AppShortcuts, Shortcut, ShortcutKey } from "../types.ts"; const platforms = ["windows", "macos", "linux"] as const; -const browser = (id: string, action: string, windowsLinux: string[], mac: string[], note?: string): Shortcut => ({ - id, - action, - keys: perPlatform(platforms, { windows: windowsLinux, linux: windowsLinux, macos: mac }), - ...(note ? { note } : {}), -}); +const browser = (id: string, action: string, windowsLinux: string[], mac: string[] | undefined, note?: string): Shortcut => { + const keys: ShortcutKey[] = [ + { platform: "windows", keys: windowsLinux }, + { platform: "linux", keys: windowsLinux }, + ...(mac ? [{ platform: "macos" as const, keys: mac }] : []), + ]; + return { id, action, keys, ...(note ? { note } : {}) }; +}; export const opera: AppShortcuts = { slug: "opera", @@ -15,10 +16,10 @@ export const opera: AppShortcuts = { category: "Web browser", platforms: [...platforms], versionContext: - "Current Opera desktop shortcuts checked September 1, 2026. The official page covers macOS and Windows/Linux defaults and notes that shortcuts can be customized under Settings > Browser > Shortcuts.", + "Current Opera desktop shortcuts checked November 1, 2026. The official page covers macOS and Windows/Linux defaults and notes that shortcuts can be customized under Settings > Browser > Shortcuts.", sourceUrl: "https://help.opera.com/en/latest/shortcuts/", sourceLabel: "official Opera shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "commands", @@ -28,13 +29,13 @@ export const opera: AppShortcuts = { browser("downloads", "Open the Downloads page", ["Ctrl", "J"], ["Cmd", "J"]), browser("extensions", "Open the Extensions page", ["Ctrl", "Shift", "E"], ["Cmd", "Shift", "E"]), browser("history", "Open History", ["Ctrl", "H"], ["Cmd", "Shift", "H"]), - browser("open-file", "Open a local file", ["Ctrl", "O"], ["Cmd", "O"]), + browser("open-file", "Open a local file", ["Ctrl", "O"], undefined), browser("address-bar", "Focus the address bar", ["Ctrl", "L"], ["Cmd", "L"]), browser("settings", "Open Settings", ["Alt", "P"], ["Cmd", ","]), browser("print", "Print the current page", ["Ctrl", "P"], ["Cmd", "P"]), browser("save", "Save the page locally", ["Ctrl", "S"], ["Cmd", "S"]), browser("quit", "Quit Opera", ["Ctrl", "Shift", "X"], ["Cmd", "Q"]), - browser("help", "Open Opera help", ["F1"], ["F1"], "The official table lists F1 for Windows/Linux; macOS users can use the Help menu."), + browser("help", "Open Opera help", ["F1"], undefined), ], }, { diff --git a/lib/shortcuts/apps/oracle-virtualbox.ts b/lib/shortcuts/apps/oracle-virtualbox.ts index 125e5d4b..8f77f983 100644 --- a/lib/shortcuts/apps/oracle-virtualbox.ts +++ b/lib/shortcuts/apps/oracle-virtualbox.ts @@ -18,7 +18,7 @@ export const oracleVirtualBox: AppShortcuts = { "Oracle VirtualBox 7.2 User Guide keyboard and host-key behavior checked September 1, 2026. The default host key is Right Ctrl on Windows and Linux hosts and Left Command on a Mac host; users can change it in Preferences.", sourceUrl: "https://docs.oracle.com/en/virtualization/virtualbox/7.2/user/working-with-vms.html", sourceLabel: "Oracle VirtualBox User Guide host-key shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "special-combinations", diff --git a/lib/shortcuts/apps/otter-ai.ts b/lib/shortcuts/apps/otter-ai.ts index a1953bd7..7a8c89bb 100644 --- a/lib/shortcuts/apps/otter-ai.ts +++ b/lib/shortcuts/apps/otter-ai.ts @@ -26,7 +26,7 @@ export const otterAi: AppShortcuts = { "Otter Help Center keyboard shortcuts checked September 1, 2026. The source article was updated February 4, 2025 and labels the modifier differences for Mac and Windows.", sourceUrl: "https://help.otter.ai/hc/en-us/articles/29431724341399-Keyboard-Shortcuts", sourceLabel: "official Otter.ai keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "audio-playback", diff --git a/lib/shortcuts/apps/pages.ts b/lib/shortcuts/apps/pages.ts index 11df15b9..81fbf247 100644 --- a/lib/shortcuts/apps/pages.ts +++ b/lib/shortcuts/apps/pages.ts @@ -16,7 +16,7 @@ export const pages: AppShortcuts = { "Pages for Mac shortcuts from Apple's current Pages User Guide, checked September 1, 2026. Apple notes that portable Macs may require Fn and that paragraph, character, or list styles can also receive user-assigned shortcuts.", sourceUrl: "https://support.apple.com/guide/pages/keyboard-shortcuts-tanc0ffef022/mac", sourceLabel: "Apple Pages keyboard shortcuts for Mac", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "document", diff --git a/lib/shortcuts/apps/paint-net.ts b/lib/shortcuts/apps/paint-net.ts index 87f131e2..4837879e 100644 --- a/lib/shortcuts/apps/paint-net.ts +++ b/lib/shortcuts/apps/paint-net.ts @@ -16,7 +16,7 @@ export const paintNet: AppShortcuts = { "Paint.NET keyboard and mouse commands from the official documentation, checked September 1, 2026. This reference is for the Windows desktop application and keeps mouse gestures only where the official command is a keyboard-plus-mouse action.", sourceUrl: "https://www.getpaint.net/doc/latest/KeyboardMouseCommands.html", sourceLabel: "official Paint.NET keyboard and mouse commands", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "canvas", diff --git a/lib/shortcuts/apps/penpot.ts b/lib/shortcuts/apps/penpot.ts index 28922e58..e9e68890 100644 --- a/lib/shortcuts/apps/penpot.ts +++ b/lib/shortcuts/apps/penpot.ts @@ -18,7 +18,7 @@ export const penpot: AppShortcuts = { "Penpot workspace shortcuts from the official user guide, checked September 1, 2026. Penpot also publishes the current keymap in its source repository, and many bindings are available only when the workspace has focus.", sourceUrl: "https://help.penpot.dev/user-guide/first-steps/shortcuts/", sourceLabel: "official Penpot shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "alignment", diff --git a/lib/shortcuts/apps/power-bi.ts b/lib/shortcuts/apps/power-bi.ts index a00f9d1e..3263e560 100644 --- a/lib/shortcuts/apps/power-bi.ts +++ b/lib/shortcuts/apps/power-bi.ts @@ -16,7 +16,7 @@ export const powerBi: AppShortcuts = { "Power BI Desktop keyboard shortcuts from Microsoft Learn, checked September 1, 2026. The shortcut overlay opens with Shift+? and several bindings work only when a visual, pane, formula bar, or model object has focus.", sourceUrl: "https://learn.microsoft.com/en-us/power-bi/create-reports/desktop-accessibility-keyboard-shortcuts", sourceLabel: "Microsoft Power BI Desktop keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "general", diff --git a/lib/shortcuts/apps/powershell.ts b/lib/shortcuts/apps/powershell.ts index dbfec2f6..13e54cb7 100644 --- a/lib/shortcuts/apps/powershell.ts +++ b/lib/shortcuts/apps/powershell.ts @@ -20,7 +20,7 @@ export const powershell: AppShortcuts = { "Current PSReadLine functions and key handlers documented by Microsoft in August 2026. The active edit mode controls many defaults: Windows mode is common on Windows, while Emacs is the default on non-Windows. Check Get-PSReadLineKeyHandler for the exact local map.", sourceUrl: "https://learn.microsoft.com/en-us/powershell/module/psreadline/about/about_psreadline_functions?view=powershell-7.6", sourceLabel: "official Microsoft PSReadLine functions", - lastVerified: "2026-08-26", + lastVerified: "2026-11-01", groups: [ { id: "cursor-movement", diff --git a/lib/shortcuts/apps/presonus-studio-one.ts b/lib/shortcuts/apps/presonus-studio-one.ts index 1451763c..e5086783 100644 --- a/lib/shortcuts/apps/presonus-studio-one.ts +++ b/lib/shortcuts/apps/presonus-studio-one.ts @@ -3,10 +3,13 @@ import type { AppShortcuts, Shortcut } from "../types.ts"; const platforms = ["windows", "macos"] as const; -const dual = (id: string, action: string, pc: string[], mac: string[] = pc, note?: string): Shortcut => ({ +const dual = (id: string, action: string, pc: string[], mac?: string[], note?: string): Shortcut => ({ id, action, - keys: perPlatform(platforms, { windows: pc, macos: mac }), + keys: perPlatform(platforms, { + windows: pc, + macos: mac ?? pc.map((key) => key === "Ctrl" ? "Cmd" : key === "Alt" ? "Option" : key), + }), ...(note ? { note } : {}), }); @@ -21,7 +24,7 @@ export const presonusStudioOne: AppShortcuts = { "Studio One Pro 7 Key Command Sheet checked September 1, 2026. The sheet says its commands are for PC, with Ctrl becoming Cmd on Mac and Alt becoming Option or Alt on Mac. Studio One lets users change commands in Options or General or Keyboard Shortcuts.", sourceUrl: "https://pae-web.presonusmusic.com/downloads/products/pdf/Studio_One_Pro_7_Key_Command_Sheet.pdf", sourceLabel: "official Studio One Pro 7 key command sheet", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "audio-and-automation", diff --git a/lib/shortcuts/apps/pro-tools.ts b/lib/shortcuts/apps/pro-tools.ts index f1b5156e..1d872df1 100644 --- a/lib/shortcuts/apps/pro-tools.ts +++ b/lib/shortcuts/apps/pro-tools.ts @@ -21,7 +21,7 @@ export const proTools: AppShortcuts = { "Avid Pro Tools Shortcuts Guide 2025.10, created September 22, 2025, checked September 1, 2026. Pro Tools lets users customize most commands, and some rows require Pro Tools Ultimate, Studio, a numeric keypad, or a particular transport mode.", sourceUrl: "https://resources.avid.com/SupportFiles/PT/Pro_Tools_Shortcuts_2025.10.pdf", sourceLabel: "official Avid Pro Tools Shortcuts Guide", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "search-and-focus", diff --git a/lib/shortcuts/apps/protopie.ts b/lib/shortcuts/apps/protopie.ts index 01b121d2..580a5ab8 100644 --- a/lib/shortcuts/apps/protopie.ts +++ b/lib/shortcuts/apps/protopie.ts @@ -21,7 +21,7 @@ export const protopie: AppShortcuts = { "ProtoPie Studio shortcuts checked September 1, 2026. The official list says the shortcut modal opens from Help or Control+Shift+Question mark on macOS and Ctrl+Shift+Question mark on Windows. Some actions use a key with a pointer gesture and are left out of this keyboard-only record.", sourceUrl: "https://www.protopie.io/learn/docs/basic-features-shortcuts", sourceLabel: "official ProtoPie Studio shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "general", diff --git a/lib/shortcuts/apps/pycharm.ts b/lib/shortcuts/apps/pycharm.ts index 6a4a85c8..d3f3c579 100644 --- a/lib/shortcuts/apps/pycharm.ts +++ b/lib/shortcuts/apps/pycharm.ts @@ -12,7 +12,7 @@ export const pycharm: AppShortcuts = { "PyCharm 2026.2 keymap reference checked September 1, 2026. JetBrains lets users choose and customize a keymap, so these entries describe the published default reference actions and not every plugin shortcut.", sourceUrl: "https://www.jetbrains.com/help/pycharm/keymap-reference.html", sourceLabel: "official PyCharm keymap reference", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "search-and-navigation", diff --git a/lib/shortcuts/apps/quickbooks-online.ts b/lib/shortcuts/apps/quickbooks-online.ts index adb72a24..dc891c5b 100644 --- a/lib/shortcuts/apps/quickbooks-online.ts +++ b/lib/shortcuts/apps/quickbooks-online.ts @@ -21,7 +21,7 @@ export const quickbooksOnline: AppShortcuts = { "Intuit's QuickBooks Help article was updated August 28, 2026 and checked September 1, 2026. The article covers Internet Explorer, Firefox, and Chrome, with Mac substitutions where Intuit documents them. The in-product reference opens with Control+Option+Question mark on Mac.", sourceUrl: "https://quickbooks.intuit.com/learn-support/en-us/help-article/product-setup/keyboard-shortcuts-quickbooks-online/L49aUqVh2_US_en_US", sourceLabel: "official QuickBooks Online keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "browser-and-help", diff --git a/lib/shortcuts/apps/reddit.ts b/lib/shortcuts/apps/reddit.ts index d9d02ffc..e71c94ff 100644 --- a/lib/shortcuts/apps/reddit.ts +++ b/lib/shortcuts/apps/reddit.ts @@ -19,7 +19,7 @@ export const reddit: AppShortcuts = { "Current Reddit desktop web hotkeys checked September 1, 2026. Reddit says the feature is available on the newest reddit.com interface, not old Reddit. On Windows, the help article maps its CMD references to Alt.", sourceUrl: "https://support.reddithelp.com/hc/en-us/articles/38744650091412-How-to-use-keyboard-shortcuts-hotkeys", sourceLabel: "official Reddit keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "navigation", @@ -54,7 +54,10 @@ export const reddit: AppShortcuts = { title: "Media playback", intro: "Media keys work while a Reddit video is focused. The same letters used for post navigation can have a playback meaning in the media player.", shortcuts: [ - dual("play-pause", "Play or pause", ["Space", "or", "K"], ["Space", "or", "K"]), + { id: "play-pause", action: "Play or pause", keys: [ + { platform: "windows", keys: ["Space"], alternatives: [["K"]] }, + { platform: "macos", keys: ["Space"], alternatives: [["K"]] }, + ] }, dual("fullscreen", "Toggle fullscreen mode", ["F"], ["F"]), dual("rewind-five", "Rewind five seconds", ["Left Arrow"], ["Left Arrow"]), dual("forward-five", "Fast-forward five seconds", ["Right Arrow"], ["Right Arrow"]), diff --git a/lib/shortcuts/apps/rocket-chat.ts b/lib/shortcuts/apps/rocket-chat.ts index ec4da614..d348689d 100644 --- a/lib/shortcuts/apps/rocket-chat.ts +++ b/lib/shortcuts/apps/rocket-chat.ts @@ -1,15 +1,8 @@ -import { combo, perPlatform } from "../build.ts"; +import { combo } from "../build.ts"; import type { AppShortcuts, Shortcut } from "../types.ts"; const platforms = ["windows", "macos", "linux"] as const; -const desktop = (id: string, action: string, windowsLinux: string[], macos: string[], note?: string): Shortcut => ({ - id, - action, - keys: perPlatform(platforms, { windows: windowsLinux, linux: windowsLinux, macos }), - ...(note ? { note } : {}), -}); - const shared = (id: string, action: string, keys: string[], note?: string): Shortcut => ({ id, action, @@ -23,10 +16,10 @@ export const rocketChat: AppShortcuts = { category: "Team chat", platforms: [...platforms], versionContext: - "Rocket.Chat account and release-note shortcut references checked September 1, 2026. Rocket.Chat says macOS uses Command and Option where Windows and Linux use Control and Alt; server settings, browser extensions, and custom keymaps can change the active bindings.", + "Rocket.Chat keyboard shortcuts checked November 1, 2026. The current account guide publishes Ctrl and Alt notation for its Web/Desktop shortcuts without platform-specific variants; server settings and browser extensions can affect the active bindings.", sourceUrl: "https://docs.rocket.chat/docs/your-rocketchat-account", sourceLabel: "official Rocket.Chat keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "global", @@ -40,10 +33,10 @@ export const rocketChat: AppShortcuts = { keys: [ { platform: "windows", keys: ["Ctrl", "P"], alternatives: [["Ctrl", "K"]] }, { platform: "linux", keys: ["Ctrl", "P"], alternatives: [["Ctrl", "K"]] }, - { platform: "macos", keys: ["Cmd", "P"], alternatives: [["Cmd", "K"]] }, + { platform: "macos", keys: ["Ctrl", "P"], alternatives: [["Ctrl", "K"]] }, ], }, - desktop("mark-all-read", "Mark all messages as read", ["Ctrl", "Escape"], ["Cmd", "Escape"]), + shared("mark-all-read", "Mark all messages as read", ["Ctrl", "Escape"]), ], }, { @@ -58,7 +51,7 @@ export const rocketChat: AppShortcuts = { keys: [ { platform: "windows", keys: ["Alt", "Left Arrow"], alternatives: [["Alt", "Up Arrow"]] }, { platform: "linux", keys: ["Alt", "Left Arrow"], alternatives: [["Alt", "Up Arrow"]] }, - { platform: "macos", keys: ["Option", "Left Arrow"], alternatives: [["Option", "Up Arrow"]] }, + { platform: "macos", keys: ["Alt", "Left Arrow"], alternatives: [["Alt", "Up Arrow"]] }, ], }, { @@ -67,7 +60,7 @@ export const rocketChat: AppShortcuts = { keys: [ { platform: "windows", keys: ["Alt", "Right Arrow"], alternatives: [["Alt", "Down Arrow"]] }, { platform: "linux", keys: ["Alt", "Right Arrow"], alternatives: [["Alt", "Down Arrow"]] }, - { platform: "macos", keys: ["Option", "Right Arrow"], alternatives: [["Option", "Down Arrow"]] }, + { platform: "macos", keys: ["Alt", "Right Arrow"], alternatives: [["Alt", "Down Arrow"]] }, ], }, shared("new-line", "Insert a new line without sending", ["Shift", "Enter"]), diff --git a/lib/shortcuts/apps/rstudio.ts b/lib/shortcuts/apps/rstudio.ts index 13fb900b..d3c52d8d 100644 --- a/lib/shortcuts/apps/rstudio.ts +++ b/lib/shortcuts/apps/rstudio.ts @@ -28,7 +28,7 @@ export const rstudio: AppShortcuts = { "RStudio User Guide version 2026.08.2 checked September 1, 2026. The guide warns that Emacs, Vim, and Sublime Text keybindings change the defaults. This record uses the standard keybinding set and omits rows where the guide says no shortcut.", sourceUrl: "https://docs.posit.co/ide/user/ide/reference/shortcuts.html", sourceLabel: "official Posit RStudio keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "command-palette-and-accessibility", diff --git a/lib/shortcuts/apps/safari.ts b/lib/shortcuts/apps/safari.ts index bc9a5774..d67f5353 100644 --- a/lib/shortcuts/apps/safari.ts +++ b/lib/shortcuts/apps/safari.ts @@ -25,7 +25,7 @@ export const safari: AppShortcuts = { "Apple's Safari User Guide for Mac checked September 1, 2026. Apple notes that shortcuts can vary with language and keyboard layout, so the Safari menus remain the authority when a local layout changes a key.", sourceUrl: "https://support.apple.com/en-az/guide/safari/cpsh003/mac", sourceLabel: "official Safari keyboard shortcuts and gestures", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "scroll-and-page", diff --git a/lib/shortcuts/apps/signal.ts b/lib/shortcuts/apps/signal.ts index f8d672f8..7889f3b9 100644 --- a/lib/shortcuts/apps/signal.ts +++ b/lib/shortcuts/apps/signal.ts @@ -12,7 +12,7 @@ export const signal: AppShortcuts = { "Signal Desktop keyboard shortcuts checked September 1, 2026. The official table covers the Mac and Windows/Linux desktop apps; user settings and browser shortcuts are outside this record.", sourceUrl: "https://support.signal.org/hc/en-us/articles/360036517511-Signal-Desktop-Keyboard-Shortcuts", sourceLabel: "official Signal Desktop keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "display-and-navigation", diff --git a/lib/shortcuts/apps/simplenote.ts b/lib/shortcuts/apps/simplenote.ts index 2239d209..36df745a 100644 --- a/lib/shortcuts/apps/simplenote.ts +++ b/lib/shortcuts/apps/simplenote.ts @@ -2,6 +2,7 @@ import { combo, perPlatform } from "../build.ts"; import type { AppShortcuts } from "../types.ts"; const platforms = ["windows", "macos", "linux"] as const; +const electronPlatforms = ["windows", "linux"] as const; export const simplenote: AppShortcuts = { slug: "simplenote", @@ -9,10 +10,10 @@ export const simplenote: AppShortcuts = { category: "Notes and writing", platforms: [...platforms], versionContext: - "Simplenote desktop shortcuts checked September 1, 2026. The source separates Electron on Linux and Windows from the macOS app, and its full in-app list may include more commands than the common entries shown here.", + "Simplenote desktop shortcuts checked November 1, 2026. The source separates Electron on Linux and Windows from the macOS app.", sourceUrl: "https://simplenote.com/help/", sourceLabel: "official Simplenote help and keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "notes", @@ -30,10 +31,10 @@ export const simplenote: AppShortcuts = { title: "Editing and view", intro: "Editing and view commands add checklist items, change the writing view, show the shortcut list, and adjust the desktop font size.", shortcuts: [ - { id: "checklist", action: "Insert a checklist item", keys: combo(platforms, ["Ctrl", "Shift", "C"], ["Cmd", "Shift", "C"]) }, - { id: "focus-mode", action: "Toggle focus mode", keys: combo(platforms, ["Ctrl", "Shift", "F"], ["Cmd", "Shift", "F"]) }, - { id: "markdown-preview", action: "Toggle Markdown preview", keys: combo(platforms, ["Ctrl", "Shift", "P"], ["Cmd", "Shift", "P"]) }, - { id: "show-shortcuts", action: "Show keyboard shortcuts", keys: perPlatform(platforms, { windows: ["Ctrl", "/"], macos: ["Cmd", ","], linux: ["Ctrl", "/"] }) }, + { id: "checklist", action: "Insert a checklist item", keys: combo(electronPlatforms, ["Ctrl", "Shift", "C"]) }, + { id: "focus-mode", action: "Toggle focus mode", keys: combo(electronPlatforms, ["Ctrl", "Shift", "F"]) }, + { id: "markdown-preview", action: "Toggle Markdown preview", keys: combo(electronPlatforms, ["Ctrl", "Shift", "P"]) }, + { id: "show-shortcuts", action: "Show keyboard shortcuts", keys: perPlatform(electronPlatforms, { windows: ["Ctrl", "/"], linux: ["Ctrl", "/"] }) }, { id: "font-increase", action: "Increase font size", keys: combo(platforms, ["Ctrl", "+"], ["Cmd", "+"]) }, { id: "font-decrease", action: "Decrease font size", keys: combo(platforms, ["Ctrl", "-"], ["Cmd", "-"]) }, { id: "font-reset", action: "Reset font size", keys: combo(platforms, ["Ctrl", "0"], ["Cmd", "0"]) }, diff --git a/lib/shortcuts/apps/slack.ts b/lib/shortcuts/apps/slack.ts index 6f5323ad..1e7e3104 100644 --- a/lib/shortcuts/apps/slack.ts +++ b/lib/shortcuts/apps/slack.ts @@ -31,7 +31,7 @@ export const slack: AppShortcuts = { { id: "unsend-message", action: "Unsend a message", keys: perPlatform(platforms, { macos: ["Cmd", "Z"], windows: ["Ctrl", "Z"], linux: ["Ctrl", "Z"] }) }, { id: "set-status", action: "Set your status", keys: combo(platforms, ["Ctrl", "Shift", "Y"], ["Cmd", "Shift", "Y"]) }, { id: "preferences", action: "Open preferences", keys: combo(platforms, ["Ctrl", ","], ["Cmd", ","]), note: "Desktop app only." }, - { id: "resize-sidebar", action: "Resize the left sidebar", keys: combo(platforms, ["Left or Right"]), note: "Press F6 to move focus to the sidebar first." }, + { id: "resize-sidebar", action: "Resize the left sidebar", keys: combo(platforms, ["F6", "then", "Left or Right"]), note: "F6 moves focus to the sidebar." }, { id: "hide-right-sidebar", action: "Hide the right sidebar", keys: combo(platforms, ["Ctrl", "."], ["Cmd", "."]) }, { id: "new-canvas", action: "Create a new canvas", keys: combo(platforms, ["Ctrl", "Shift", "N"], ["Cmd", "Shift", "N"]) }, { id: "upload-file", action: "Upload a file", keys: combo(platforms, ["Ctrl", "O"], ["Cmd", "O"]), note: "Desktop app only." }, diff --git a/lib/shortcuts/apps/smartsheet.ts b/lib/shortcuts/apps/smartsheet.ts index 40e6bbf3..d4ace45b 100644 --- a/lib/shortcuts/apps/smartsheet.ts +++ b/lib/shortcuts/apps/smartsheet.ts @@ -12,7 +12,7 @@ export const smartsheet: AppShortcuts = { "Smartsheet keyboard shortcuts checked September 1, 2026. The official Learning Center table says Mac users should use Command instead of Ctrl for the paired shortcuts.", sourceUrl: "https://help.smartsheet.com/articles/522200-keyboard-shortcuts", sourceLabel: "official Smartsheet keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "editing", diff --git a/lib/shortcuts/apps/spark-mail.ts b/lib/shortcuts/apps/spark-mail.ts index bb952f4a..21c3e405 100644 --- a/lib/shortcuts/apps/spark-mail.ts +++ b/lib/shortcuts/apps/spark-mail.ts @@ -12,7 +12,7 @@ export const sparkMail: AppShortcuts = { "Spark Desktop shortcuts checked September 1, 2026. Spark supports configurable shortcut presets, so these entries describe the published default desktop bindings rather than a user's custom preset.", sourceUrl: "https://sparkmailapp.com/help/tips-tricks/use-keyboard-shortcuts", sourceLabel: "official Spark Mail desktop keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "email-triage", @@ -44,7 +44,7 @@ export const sparkMail: AppShortcuts = { { id: "inbox", action: "Open Inbox", keys: combo(platforms, ["Ctrl", "Shift", "1"], ["Cmd", "Shift", "1"]) }, { id: "archive-view", action: "Open Archive", keys: combo(platforms, ["Ctrl", "Shift", "2"], ["Cmd", "Shift", "2"]) }, { id: "search", action: "Focus search", keys: perPlatform(platforms, { windows: ["/"], macos: ["/"] }) }, - { id: "done-view", action: "Open Done", keys: combo(platforms, ["Ctrl", "E"], ["Cmd", "E"]) }, + { id: "done-view", action: "Show or hide Done emails", keys: combo(platforms, ["Ctrl", "E"], ["Cmd", "E"]) }, { id: "set-aside", action: "Set aside a message", keys: perPlatform(platforms, { windows: ["G"], macos: ["G"] }) }, { id: "pinned-view", action: "Open Pinned", keys: perPlatform(platforms, { windows: ["Ctrl", "Shift", "P"], macos: ["Cmd", "Shift", "P"] }) }, { id: "snoozed-view", action: "Open Snoozed", keys: perPlatform(platforms, { windows: ["Ctrl", "Shift", "S"], macos: ["Cmd", "Shift", "S"] }) }, diff --git a/lib/shortcuts/apps/spotify.ts b/lib/shortcuts/apps/spotify.ts index 61a2a57b..93774ded 100644 --- a/lib/shortcuts/apps/spotify.ts +++ b/lib/shortcuts/apps/spotify.ts @@ -12,14 +12,17 @@ export const spotify: AppShortcuts = { "Spotify desktop and web player shortcuts checked September 1, 2026. Spotify says the app can show the full in-app list, so this record contains the stable actions published in the support article.", sourceUrl: "https://support.spotify.com/na-en/article/keyboard-shortcuts/", sourceLabel: "official Spotify keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "search-and-navigation", title: "Search and navigation", intro: "Search and navigation shortcuts reach help, library sections, preferences, and the sidebars without opening the mouse menus.", shortcuts: [ - { id: "open-help", action: "Open Help", keys: combo(platforms, ["Ctrl", "/"], ["Cmd", "/"]) }, + { id: "open-help", action: "Open Help", keys: [ + { platform: "windows", keys: ["Ctrl", "/"], alternatives: [["Ctrl", "?"]] }, + { platform: "macos", keys: ["Cmd", "/"], alternatives: [["Cmd", "?"]] }, + ] }, { id: "open-search", action: "Open Search", keys: combo(platforms, ["Ctrl", "K"], ["Cmd", "K"]) }, { id: "select-all", action: "Select all", keys: combo(platforms, ["Ctrl", "A"], ["Cmd", "A"]) }, { id: "filter", action: "Filter", keys: combo(platforms, ["Ctrl", "F"], ["Cmd", "F"]) }, diff --git a/lib/shortcuts/apps/squarespace.ts b/lib/shortcuts/apps/squarespace.ts index c611f188..9a0668b6 100644 --- a/lib/shortcuts/apps/squarespace.ts +++ b/lib/shortcuts/apps/squarespace.ts @@ -9,10 +9,10 @@ export const squarespace: AppShortcuts = { category: "Website builder", platforms: [...platforms], versionContext: - "Squarespace keyboard shortcuts checked September 1, 2026. The official guide was updated November 5, 2025 and warns that some shortcuts do not work in products or panels outside direct site editing.", + "Squarespace keyboard shortcuts checked November 1, 2026. The official guide was updated November 5, 2025 and warns that some shortcuts do not work in products or panels outside direct site editing.", sourceUrl: "https://support.squarespace.com/hc/en-us/articles/214491097-Squarespace-keyboard-shortcuts-and-tips", sourceLabel: "official Squarespace keyboard shortcuts and tips", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "navigation", @@ -62,7 +62,10 @@ export const squarespace: AppShortcuts = { { id: "align-center", action: "Align center", keys: perPlatform(platforms, { windows: ["Shift", "Ctrl", "E"], macos: ["Shift", "Cmd", "E"] }) }, { id: "align-right", action: "Align right", keys: perPlatform(platforms, { windows: ["Shift", "Ctrl", "R"], macos: ["Shift", "Cmd", "R"] }) }, { id: "undo", action: "Undo", keys: combo(platforms, ["Ctrl", "Z"], ["Cmd", "Z"]) }, - { id: "redo", action: "Redo", keys: perPlatform(platforms, { windows: ["Ctrl", "Y"], macos: ["Cmd", "Y"] }) }, + { id: "redo", action: "Redo", keys: [ + { platform: "windows", keys: ["Ctrl", "Y"], alternatives: [["Ctrl", "Shift", "Z"]] }, + { platform: "macos", keys: ["Cmd", "Y"], alternatives: [["Shift", "Cmd", "Z"]] }, + ] }, { id: "paste-plain", action: "Paste as plain text", keys: combo(platforms, ["Ctrl", "Shift", "V"], ["Cmd", "Shift", "V"]) }, { id: "indent-more", action: "Increase text indent", keys: combo(platforms, ["Ctrl", "]"], ["Cmd", "]"]) }, { id: "indent-less", action: "Decrease text indent", keys: combo(platforms, ["Ctrl", "["], ["Cmd", "["]) }, diff --git a/lib/shortcuts/apps/sublime-text.ts b/lib/shortcuts/apps/sublime-text.ts index 94fd0b9c..5b0ac878 100644 --- a/lib/shortcuts/apps/sublime-text.ts +++ b/lib/shortcuts/apps/sublime-text.ts @@ -12,7 +12,7 @@ export const sublimeText: AppShortcuts = { "Sublime Text keyboard shortcut tables checked September 1, 2026. Windows and Linux share most bindings, while macOS changes the primary modifier and a few editing commands.", sourceUrl: "https://docs.sublimetext.io/reference/keyboard_shortcuts_win.html", sourceLabel: "official Sublime Text keyboard shortcut reference", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "editing", @@ -66,7 +66,7 @@ export const sublimeText: AppShortcuts = { intro: "Tabs and folding shortcuts reopen or close files, switch tabs, select groups, and collapse code by nesting level.", shortcuts: [ { id: "reopen-tab", action: "Open the last closed tab", keys: perPlatform(platforms, { windows: ["Ctrl", "Shift", "T"], linux: ["Ctrl", "Shift", "T"], macos: ["Shift", "Cmd", "T"] }) }, - { id: "recent-tab", action: "Cycle through recent tabs", keys: perPlatform(platforms, { windows: ["Ctrl", "Tab"], linux: ["Ctrl", "Tab"], macos: ["Ctrl", "Tab"] }) }, + { id: "recent-tab", action: "Cycle through recent tabs", keys: perPlatform(platforms, { windows: ["Ctrl", "Tab"], linux: ["Ctrl", "Tab"], macos: ["Ctrl", "Tab"] }), note: "To reverse the cycle, use Ctrl+Shift+Tab." }, { id: "close-tab", action: "Close the current tab", keys: combo(platforms, ["Ctrl", "W"], ["Cmd", "W"]) }, { id: "tab-number", action: "Switch to a tab by number", keys: perPlatform(platforms, { windows: ["Alt", "1"], linux: ["Alt", "1"], macos: ["Cmd", "1"] }) }, { id: "fold", action: "Fold", keys: perPlatform(platforms, { windows: ["Ctrl", "Shift", "["], linux: ["Ctrl", "Shift", "["], macos: ["Ctrl", "Shift", "["] }) }, diff --git a/lib/shortcuts/apps/tableau.ts b/lib/shortcuts/apps/tableau.ts index b38486a3..fc62a81c 100644 --- a/lib/shortcuts/apps/tableau.ts +++ b/lib/shortcuts/apps/tableau.ts @@ -9,10 +9,10 @@ export const tableau: AppShortcuts = { category: "Analytics", platforms: [...platforms], versionContext: - "Tableau Desktop keyboard shortcuts checked September 1, 2026. The official reference also documents Server, Cloud, and Prep shortcuts, but this record focuses on the Desktop authoring workflow.", + "Tableau Desktop keyboard shortcuts checked November 1, 2026. The official reference also documents Server, Cloud, and Prep shortcuts, but this record focuses on the Desktop authoring workflow.", sourceUrl: "https://help.tableau.com/current/pro/desktop/en-us/shortcut.htm", sourceLabel: "official Tableau Desktop keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "workbooks", @@ -26,9 +26,9 @@ export const tableau: AppShortcuts = { { id: "save-public", action: "Save a workbook to Tableau Public", keys: combo(platforms, ["Ctrl", "Shift", "S"], ["Cmd", "Shift", "S"]) }, { id: "close-workbook", action: "Close the workbook", keys: perPlatform(platforms, { windows: ["Alt", "F4"], macos: ["Cmd", "W"] }) }, { id: "print", action: "Print", keys: combo(platforms, ["Ctrl", "P"], ["Cmd", "P"]) }, - { id: "cycle-sheets", action: "Cycle through worksheets", keys: perPlatform(platforms, { windows: ["Ctrl", "Tab"], macos: ["Shift", "Cmd", "]"] }) }, + { id: "cycle-sheets", action: "Cycle through worksheets", keys: perPlatform(platforms, { windows: ["Ctrl", "Tab"], macos: ["Shift", "Cmd", "]"] }).map((entry) => entry.platform === "windows" ? { ...entry, alternatives: [["Ctrl", "F6"]] } : entry) }, { id: "previous-sheet", action: "Go to the previous worksheet", keys: perPlatform(platforms, { windows: ["Ctrl", "Shift", "Tab"], macos: ["Shift", "Cmd", "["] }) }, - { id: "presentation", action: "Enter presentation mode", keys: perPlatform(platforms, { windows: ["F7"], macos: ["Ctrl", "H"] }) }, + { id: "presentation", action: "Enter presentation mode", keys: perPlatform(platforms, { windows: ["F7"], macos: ["Option", "Return"] }).map((entry) => entry.platform === "windows" ? { ...entry, alternatives: [["Ctrl", "H"]] } : entry) }, { id: "fullscreen", action: "Toggle full screen", keys: perPlatform(platforms, { windows: ["Ctrl", "Shift", "F"], macos: ["Ctrl", "Cmd", "F"] }) }, { id: "help", action: "Open Help", keys: perPlatform(platforms, { windows: ["F1"], macos: ["Ctrl", "Cmd", "?"] }) }, ], @@ -38,12 +38,12 @@ export const tableau: AppShortcuts = { title: "Authoring and data", intro: "Authoring shortcuts find fields, refresh data, show recommendations, update views, and change the worksheet editing state.", shortcuts: [ - { id: "describe", action: "Describe the selected field", keys: combo(platforms, ["Ctrl", "E"], ["Cmd", "E"]) }, + { id: "describe", action: "Describe the sheet", keys: combo(platforms, ["Ctrl", "E"], ["Cmd", "E"]) }, { id: "find", action: "Find", keys: combo(platforms, ["Ctrl", "F"], ["Cmd", "F"]) }, { id: "refresh", action: "Refresh data", keys: perPlatform(platforms, { windows: ["F5"], macos: ["Cmd", "R"] }) }, { id: "run-updates", action: "Run automatic updates", keys: perPlatform(platforms, { windows: ["F9"], macos: ["Shift", "Cmd", "0"] }) }, { id: "automatic-updates", action: "Turn automatic updates on or off", keys: perPlatform(platforms, { windows: ["F10"], macos: ["Option", "Cmd", "0"] }) }, - { id: "show-me", action: "Open Show Me", keys: perPlatform(platforms, { windows: ["Ctrl", "1"], macos: ["Cmd", "1"] }) }, + { id: "show-me", action: "Open Show Me", keys: perPlatform(platforms, { windows: ["Ctrl", "1"], macos: ["Cmd", "1"] }).map((entry) => entry.platform === "windows" ? { ...entry, alternatives: [["Ctrl", "Shift", "1"]] } : entry) }, { id: "model", action: "Open the data model", keys: combo(platforms, ["Ctrl", "Shift", "I"], ["Cmd", "Shift", "I"]) }, { id: "swap", action: "Swap rows and columns", keys: perPlatform(platforms, { windows: ["Ctrl", "W"], macos: ["Ctrl", "Cmd", "W"] }) }, { id: "grid", action: "Show or hide the grid", keys: perPlatform(platforms, { windows: ["G"], macos: ["G"] }) }, @@ -60,12 +60,11 @@ export const tableau: AppShortcuts = { shortcuts: [ { id: "select-all", action: "Select all", keys: combo(platforms, ["Ctrl", "A"], ["Cmd", "A"]) }, { id: "escape", action: "Clear the current focus or selection", keys: perPlatform(platforms, { windows: ["Esc"], macos: ["Esc"] }) }, - { id: "tooltip", action: "Show or hide tooltips", keys: perPlatform(platforms, { windows: ["Ctrl", "H"], macos: ["Ctrl", "H"] }) }, - { id: "pages-next", action: "Move to the next page", keys: perPlatform(platforms, { windows: ["Ctrl", "Right"], macos: ["Cmd", "Right"] }) }, - { id: "pages-previous", action: "Move to the previous page", keys: perPlatform(platforms, { windows: ["Ctrl", "Left"], macos: ["Cmd", "Left"] }) }, - { id: "mark-label", action: "Add a mark label", keys: perPlatform(platforms, { windows: ["A"], macos: ["A"] }) }, - { id: "mark-detail", action: "Add a mark detail", keys: perPlatform(platforms, { windows: ["D"], macos: ["D"] }) }, - { id: "mark-size", action: "Change mark size", keys: perPlatform(platforms, { windows: ["S"], macos: ["S"] }) }, + { id: "pages-next", action: "Skip forward one page", keys: perPlatform(platforms, { windows: ["Ctrl", "."], macos: ["Cmd", "."] }) }, + { id: "pages-previous", action: "Skip backward one page", keys: perPlatform(platforms, { windows: ["Ctrl", ","], macos: ["Cmd", ","] }) }, + { id: "mark-label", action: "Use the rectangular selection tool", keys: perPlatform(platforms, { windows: ["A"], macos: ["A"] }) }, + { id: "mark-detail", action: "Use the lasso selection tool", keys: perPlatform(platforms, { windows: ["D"], macos: ["D"] }) }, + { id: "mark-size", action: "Use the radial selection tool", keys: perPlatform(platforms, { windows: ["S"], macos: ["S"] }) }, ], }, ], diff --git a/lib/shortcuts/apps/tableplus.ts b/lib/shortcuts/apps/tableplus.ts index 4cd13e19..bc84fafa 100644 --- a/lib/shortcuts/apps/tableplus.ts +++ b/lib/shortcuts/apps/tableplus.ts @@ -9,18 +9,18 @@ export const tableplus: AppShortcuts = { category: "Database tools", platforms: [...platforms], versionContext: - "TablePlus shortcut keys checked September 1, 2026. The documentation says to replace Command with Control on Windows and notes that key mappings can be customized in Preferences.", + "TablePlus shortcut keys checked November 1, 2026. The documentation says to replace Command with Control on Windows and notes that key mappings can be customized in Preferences.", sourceUrl: "https://docs.tableplus.com/utilities/shortcut-keys", sourceLabel: "official TablePlus shortcut keys", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "global-and-workspace", title: "Connections and workspace", intro: "Global and workspace shortcuts open connections, tabs, preferences, plugins, panes, and the database or connection switchers.", shortcuts: [ - { id: "new-connection", action: "Open a new connection", keys: combo(platforms, ["Ctrl", "N"], ["Cmd", "N"]) }, - { id: "preferences", action: "Open Preferences", keys: combo(platforms, ["Ctrl", ","], ["Cmd", ","]) }, + { id: "new-connection", action: "Open connection list (New Workspace)", keys: combo(platforms, ["Ctrl", "N"], ["Cmd", "N"]) }, + { id: "preferences", action: "Open Settings", keys: combo(platforms, ["Ctrl", ","], ["Cmd", ","]) }, { id: "quit", action: "Quit the app", keys: combo(platforms, ["Ctrl", "Q"], ["Cmd", "Q"]) }, { id: "new-sql-viewer", action: "Open a new SQL Viewer", keys: combo(platforms, ["Ctrl", "Shift", "O"], ["Cmd", "Shift", "O"]) }, { id: "close-tab", action: "Close the current tab or window", keys: combo(platforms, ["Ctrl", "W"], ["Cmd", "W"]) }, @@ -30,7 +30,7 @@ export const tableplus: AppShortcuts = { { id: "reload", action: "Reload the workspace", keys: combo(platforms, ["Ctrl", "R"], ["Cmd", "R"]) }, { id: "left-tab", action: "Navigate to the left tab", keys: combo(platforms, ["Ctrl", "["], ["Cmd", "["]) }, { id: "right-tab", action: "Navigate to the right tab", keys: combo(platforms, ["Ctrl", "]"], ["Cmd", "]"]) }, - { id: "split-pane", action: "Split the pane horizontally", keys: combo(platforms, ["Ctrl", "Shift", "D"], ["Cmd", "Shift", "D"]) }, + { id: "split-pane", action: "Split the pane to the right", keys: combo(platforms, ["Ctrl", "Shift", "D"], ["Cmd", "Shift", "D"]) }, { id: "switch-database", action: "Switch database", keys: combo(platforms, ["Ctrl", "K"], ["Cmd", "K"]) }, { id: "switch-connection", action: "Switch connection", keys: combo(platforms, ["Ctrl", "Shift", "K"], ["Cmd", "Shift", "K"]) }, ], @@ -42,14 +42,13 @@ export const tableplus: AppShortcuts = { shortcuts: [ { id: "execute", action: "Execute SQL code", keys: combo(platforms, ["Ctrl", "Enter"], ["Cmd", "Return"]) }, { id: "open-sql", action: "Open a SQL file", keys: combo(platforms, ["Ctrl", "O"], ["Cmd", "O"]) }, - { id: "indent", action: "Indent selected SQL code", keys: perPlatform(platforms, { windows: ["Ctrl", "I"], macos: ["Cmd", "I"] }) }, { id: "beautify", action: "Beautify selected SQL statements", keys: combo(platforms, ["Ctrl", "I"], ["Cmd", "I"]) }, { id: "uglify", action: "Uglify selected SQL statements", keys: combo(platforms, ["Ctrl", "Shift", "I"], ["Shift", "Cmd", "I"]) }, { id: "save-sql", action: "Save selected SQL statements as", keys: combo(platforms, ["Ctrl", "Shift", "S"], ["Shift", "Cmd", "S"]) }, { id: "comment", action: "Comment or uncomment SQL lines", keys: combo(platforms, ["Ctrl", "/"], ["Cmd", "/"]) }, { id: "next-cursor", action: "Add a cursor above the current line", keys: perPlatform(platforms, { windows: ["Ctrl", "Shift", "Up"], macos: ["Ctrl", "Shift", "Up"] }) }, { id: "next-cursor-below", action: "Add a cursor below the current line", keys: perPlatform(platforms, { windows: ["Ctrl", "Shift", "Down"], macos: ["Ctrl", "Shift", "Down"] }) }, - { id: "select-next", action: "Select the next instance of the current highlight", keys: combo(platforms, ["Ctrl", "D"], ["Cmd", "D"]) }, + { id: "select-next", action: "Select the next occurrence of the current word", keys: combo(platforms, ["Ctrl", "D"], ["Cmd", "D"]) }, { id: "select-all-instances", action: "Select all instances of the current highlight", keys: perPlatform(platforms, { windows: ["Ctrl", "Ctrl", "G"], macos: ["Cmd", "Ctrl", "G"] }) }, ], }, @@ -63,9 +62,9 @@ export const tableplus: AppShortcuts = { { id: "paste-rows", action: "Paste rows", keys: combo(platforms, ["Ctrl", "V"], ["Cmd", "V"]) }, { id: "duplicate-row", action: "Duplicate a row", keys: combo(platforms, ["Ctrl", "D"], ["Cmd", "D"]) }, { id: "insert-row", action: "Insert a new row", keys: combo(platforms, ["Ctrl", "I"], ["Cmd", "I"]) }, - { id: "row-filter", action: "Open the row filter", keys: combo(platforms, ["Ctrl", "F"], ["Cmd", "F"]) }, - { id: "column-filter", action: "Open the column filter", keys: perPlatform(platforms, { windows: ["Ctrl", "Alt", "F"], macos: ["Cmd", "Option", "F"] }) }, - { id: "focus-next", action: "Move focus while editing", keys: perPlatform(platforms, { windows: ["Tab"], macos: ["Tab"] }) }, + { id: "row-filter", action: "Show filters", keys: combo(platforms, ["Ctrl", "F"], ["Cmd", "F"]) }, + { id: "column-filter", action: "Choose columns to show", keys: perPlatform(platforms, { windows: ["Ctrl", "Alt", "F"], macos: ["Cmd", "Option", "F"] }) }, + { id: "focus-next", action: "Edit the selected cell", keys: perPlatform(platforms, { windows: ["Tab"], macos: ["Tab"] }).map((entry) => ({ ...entry, alternatives: [["Return"]] })) }, { id: "undo", action: "Undo", keys: combo(platforms, ["Ctrl", "Z"], ["Cmd", "Z"]) }, { id: "redo", action: "Redo", keys: combo(platforms, ["Ctrl", "Shift", "Z"], ["Cmd", "Shift", "Z"]) }, { id: "open-anything", action: "Open anything", keys: combo(platforms, ["Ctrl", "P"], ["Cmd", "P"]) }, diff --git a/lib/shortcuts/apps/tally-forms.ts b/lib/shortcuts/apps/tally-forms.ts index 14e2725a..5c1837ca 100644 --- a/lib/shortcuts/apps/tally-forms.ts +++ b/lib/shortcuts/apps/tally-forms.ts @@ -1,7 +1,7 @@ -import { combo } from "../build.ts"; +import { combo, perPlatform } from "../build.ts"; import type { AppShortcuts } from "../types.ts"; -const platforms = ["windows", "macos", "linux"] as const; +const platforms = ["windows", "macos"] as const; export const tallyForms: AppShortcuts = { slug: "tally-forms", @@ -9,10 +9,10 @@ export const tallyForms: AppShortcuts = { category: "Forms", platforms: [...platforms], versionContext: - "Tally keyboard shortcuts checked September 1, 2026. The source describes shortcuts for the Tally editor and uses Cmd on macOS and Ctrl on Windows or Linux.", + "Tally keyboard shortcuts checked November 1, 2026. The source describes shortcuts for the Tally editor and uses Cmd on macOS and Ctrl on Windows.", sourceUrl: "https://tally.so/help/keyboard-shortcuts", sourceLabel: "official Tally keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "editing", @@ -20,7 +20,7 @@ export const tallyForms: AppShortcuts = { intro: "Editing shortcuts select, open, move, duplicate, copy, paste, and delete blocks while a form is being built.", shortcuts: [ { id: "escape", action: "Select the current block", keys: combo(platforms, ["Esc"], ["Esc"]) }, - { id: "edit-block", action: "Edit the selected block", keys: combo(platforms, ["Enter"], ["Return"]) }, + { id: "edit-block", action: "Edit the selected block", keys: combo(platforms, ["Enter"]) }, { id: "select-up", action: "Select the block above", keys: combo(platforms, ["Up"], ["Up"]) }, { id: "select-down", action: "Select the block below", keys: combo(platforms, ["Down"], ["Down"]) }, { id: "move-up", action: "Move a block up", keys: combo(platforms, ["Ctrl", "Shift", "Up"], ["Cmd", "Shift", "Up"]) }, @@ -30,7 +30,7 @@ export const tallyForms: AppShortcuts = { { id: "paste", action: "Paste a block", keys: combo(platforms, ["Ctrl", "V"], ["Cmd", "V"]) }, { id: "duplicate", action: "Duplicate a block", keys: combo(platforms, ["Ctrl", "D"], ["Cmd", "D"]) }, { id: "undo", action: "Undo", keys: combo(platforms, ["Ctrl", "Z"], ["Cmd", "Z"]) }, - { id: "redo", action: "Redo", keys: combo(platforms, ["Ctrl", "Shift", "Z"], ["Cmd", "Shift", "Z"]) }, + { id: "redo", action: "Redo", keys: perPlatform(platforms, { windows: ["Ctrl", "Y"], macos: ["Cmd", "Shift", "Z"] }) }, ], }, { @@ -38,16 +38,16 @@ export const tallyForms: AppShortcuts = { title: "Text and commands", intro: "Text shortcuts insert lines and formatting, open the block menu or command menu, and apply common Markdown-style constructs.", shortcuts: [ - { id: "new-line", action: "Insert a line break", keys: combo(platforms, ["Shift", "Enter"], ["Shift", "Return"]) }, + { id: "new-line", action: "Insert a line break", keys: combo(platforms, ["Shift", "Enter"]) }, { id: "bold", action: "Bold selected text", keys: combo(platforms, ["Ctrl", "B"], ["Cmd", "B"]) }, { id: "italic", action: "Italicize selected text", keys: combo(platforms, ["Ctrl", "I"], ["Cmd", "I"]) }, { id: "underline", action: "Underline selected text", keys: combo(platforms, ["Ctrl", "U"], ["Cmd", "U"]) }, { id: "link", action: "Add or edit a link", keys: combo(platforms, ["Ctrl", "K"], ["Cmd", "K"]) }, { id: "block-menu", action: "Open the block menu", keys: combo(platforms, ["/"], ["/"]) }, { id: "command-menu", action: "Open the command menu", keys: combo(platforms, ["Ctrl", "K"], ["Cmd", "K"]) }, - { id: "heading-1", action: "Create a heading 1 with Markdown input", keys: combo(platforms, ["#", "Space"], ["#", "Space"]) }, - { id: "heading-2", action: "Create a heading 2 with Markdown input", keys: combo(platforms, ["#", "#", "Space"], ["#", "#", "Space"]) }, - { id: "checklist", action: "Create a checklist with Markdown input", keys: combo(platforms, ["[", "]", "Space"], ["[", "]", "Space"]) }, + { id: "heading-1", action: "Create a heading 1 with Markdown input", keys: combo(platforms, ["#", "then", "Space"]) }, + { id: "heading-2", action: "Create a heading 2 with Markdown input", keys: combo(platforms, ["#", "then", "#", "then", "Space"]) }, + { id: "checklist", action: "Create a checklist with Markdown input", keys: combo(platforms, ["[", "then", "]"]) }, { id: "mention", action: "Open the mention picker", keys: combo(platforms, ["@"], ["@"]) }, ], }, diff --git a/lib/shortcuts/apps/telegram.ts b/lib/shortcuts/apps/telegram.ts index 18c02353..e4b01948 100644 --- a/lib/shortcuts/apps/telegram.ts +++ b/lib/shortcuts/apps/telegram.ts @@ -9,10 +9,10 @@ export const telegram: AppShortcuts = { category: "Messaging", platforms: [...platforms], versionContext: - "Telegram Desktop shortcuts checked September 1, 2026 against the current desktop source and official translation strings. Telegram lets users customize and reset these bindings in Chat Settings > Keyboard shortcuts.", + "Telegram Desktop shortcuts checked November 1, 2026 against the current desktop source and official translation strings. Telegram lets users customize and reset these bindings in Chat Settings > Keyboard shortcuts.", sourceUrl: "https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/core/shortcuts.cpp", sourceLabel: "official Telegram Desktop shortcut definitions", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "application", @@ -53,10 +53,7 @@ export const telegram: AppShortcuts = { intro: "Chat actions mark a chat read and expose the desktop chat menu and message controls shown by Telegram's translation settings.", shortcuts: [ { id: "mark-read", action: "Mark the current chat as read", keys: combo(platforms, ["Ctrl", "R"], ["Cmd", "R"]) }, - { id: "chat-menu", action: "Show the chat menu", keys: combo(platforms, ["Ctrl", "Shift", "M"], ["Cmd", "Shift", "M"]) }, - { id: "send", action: "Send a message", keys: perPlatform(platforms, { windows: ["Enter"], macos: ["Return"], linux: ["Enter"] }) }, - { id: "send-silent", action: "Send silently", keys: combo(platforms, ["Ctrl", "Enter"], ["Cmd", "Return"]) }, - { id: "scheduled", action: "Send or schedule a message", keys: combo(platforms, ["Ctrl", "Shift", "Enter"], ["Cmd", "Shift", "Return"]) }, + { id: "chat-menu", action: "Show the chat menu", keys: perPlatform(platforms, { windows: ["Ctrl", "\\"], macos: ["Cmd", "\\"], linux: ["Ctrl", "\\"] }) }, { id: "next-message", action: "Go to the next message", keys: perPlatform(platforms, { windows: ["Down"], macos: ["Down"], linux: ["Down"] }) }, { id: "previous-message", action: "Go to the previous message", keys: perPlatform(platforms, { windows: ["Up"], macos: ["Up"], linux: ["Up"] }) }, ], diff --git a/lib/shortcuts/apps/things-3.ts b/lib/shortcuts/apps/things-3.ts index 05975600..b9bef612 100644 --- a/lib/shortcuts/apps/things-3.ts +++ b/lib/shortcuts/apps/things-3.ts @@ -8,10 +8,10 @@ export const things3: AppShortcuts = { category: "Task management", platforms: ["macos"], versionContext: - "Things for Mac keyboard shortcuts checked October 10, 2026 against Cultured Code's current support article. The Quick Entry hotkeys can be changed in Things settings.", + "Things for Mac keyboard shortcuts checked November 1, 2026 against Cultured Code's current support article. The Quick Entry hotkeys can be changed in Things settings.", sourceUrl: "https://culturedcode.com/things/support/articles/2785159/", sourceLabel: "official Things for Mac keyboard shortcuts", - lastVerified: "2026-10-10", + lastVerified: "2026-11-01", groups: [ { id: "create-and-edit", diff --git a/lib/shortcuts/apps/todoist.ts b/lib/shortcuts/apps/todoist.ts index c5220279..f11fc159 100644 --- a/lib/shortcuts/apps/todoist.ts +++ b/lib/shortcuts/apps/todoist.ts @@ -23,10 +23,10 @@ export const todoist: AppShortcuts = { category: "Task management", platforms: [...platforms], versionContext: - "Todoist Help keyboard shortcut reference last updated July 2, 2026 and checked September 1, 2026. Todoist documents QWERTY and QWERTZ layouts, and global Quick Add or Ramble shortcuts do not work on Linux; this record covers Windows 10/11 and macOS.", + "Todoist Help keyboard shortcut reference last updated July 2, 2026 and checked November 1, 2026. Todoist documents QWERTY and QWERTZ layouts, and global Quick Add or Ramble shortcuts do not work on Linux; this record covers Windows 10/11 and macOS.", sourceUrl: "https://todoist.com/help/articles/use-keyboard-shortcuts-in-todoist-Wyovn2", sourceLabel: "official Todoist keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "general-and-navigation", diff --git a/lib/shortcuts/apps/typora.ts b/lib/shortcuts/apps/typora.ts index 6e62cf97..f4572d57 100644 --- a/lib/shortcuts/apps/typora.ts +++ b/lib/shortcuts/apps/typora.ts @@ -23,10 +23,10 @@ export const typora: AppShortcuts = { category: "Writing tool", platforms: [...platforms], versionContext: - "Typora's Shortcut Keys reference updated July 19, 2026 and checked September 1, 2026. Typora allows menu bindings to be changed in macOS App Shortcuts or Windows/Linux advanced settings, so this record describes the published defaults.", + "Typora's Shortcut Keys reference updated October 31, 2026 and checked November 1, 2026. Typora allows menu bindings to be changed in macOS App Shortcuts or Windows/Linux advanced settings, so this record describes the published defaults.", sourceUrl: "https://support.typora.io/Shortcut-Keys/", sourceLabel: "official Typora shortcut keys", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "files", diff --git a/lib/shortcuts/apps/ulysses.ts b/lib/shortcuts/apps/ulysses.ts index d5d5af34..4ec8d5dc 100644 --- a/lib/shortcuts/apps/ulysses.ts +++ b/lib/shortcuts/apps/ulysses.ts @@ -16,10 +16,10 @@ export const ulysses: AppShortcuts = { category: "Writing tool", platforms: [...platforms], versionContext: - "Ulysses Keyboard Shortcuts page checked September 1, 2026. The source publishes the Mac map separately from iPad and iPhone shortcuts; this record uses the Mac defaults and leaves custom macOS App Shortcuts outside the map.", + "Ulysses Keyboard Shortcuts page checked November 1, 2026. The source publishes the Mac map separately from iPad and iPhone shortcuts; this record uses the Mac defaults and leaves custom macOS App Shortcuts outside the map.", sourceUrl: "https://help.ulysses.app/keyboard-shortcuts-mac-ipad?kb_language=en_US", sourceLabel: "official Ulysses Mac keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "sheets-and-navigation", diff --git a/lib/shortcuts/apps/vim.ts b/lib/shortcuts/apps/vim.ts index 60374c6a..569bb3ed 100644 --- a/lib/shortcuts/apps/vim.ts +++ b/lib/shortcuts/apps/vim.ts @@ -39,7 +39,7 @@ export const vim: AppShortcuts = { { id: "file-end", action: "Move to the end of the file", keys: combo(platforms, ["G"]) }, { id: "matching-pair", action: "Jump to the matching bracket or parenthesis", keys: combo(platforms, ["%"]) }, { id: "previous-position", action: "Jump to the older cursor position", keys: combo(platforms, ["Ctrl", "o"]), note: "Ctrl+i jumps forward through the jump list." }, - { id: "find-character", action: "Find a character forward on the line", keys: combo(platforms, ["f"]), note: "Type f then the character. t lands just before it; F and T search backward." }, + { id: "find-character", action: "Find a character forward on the line", keys: combo(platforms, ["f", "then", "{char}"]), note: "t lands just before the character; F and T search backward." }, ], }, { @@ -53,10 +53,10 @@ export const vim: AppShortcuts = { { id: "change-word", action: "Change the word under the cursor", keys: combo(platforms, ["c", "i", "w"]), note: "The ci text-object pattern also works with parentheses, quotes, and other objects." }, { id: "change-line", action: "Change the whole line", keys: combo(platforms, ["c", "c"]) }, { id: "change-to-line-end", action: "Change from the cursor to line end", keys: combo(platforms, ["C"]) }, - { id: "replace-character", action: "Replace one character", keys: combo(platforms, ["r"]), note: "Type r then the replacement character." }, + { id: "replace-character", action: "Replace one character", keys: combo(platforms, ["r", "then", "{char}"]) }, { id: "join-lines", action: "Join the current line with the next", keys: combo(platforms, ["J"]) }, { id: "yank-line", action: "Copy (yank) the current line", keys: combo(platforms, ["y", "y"]) }, - { id: "yank-motion", action: "Copy text covered by a motion", keys: combo(platforms, ["y"]), note: "Press y then a motion, such as yw or y$." }, + { id: "yank-motion", action: "Copy text covered by a motion", keys: combo(platforms, ["y", "then", "{motion}"]), note: "For example yw or y$." }, { id: "paste-after", action: "Paste after the cursor", keys: combo(platforms, ["p"]), note: "P pastes before the cursor." }, { id: "undo", action: "Undo the last change", keys: combo(platforms, ["u"]) }, { id: "redo", action: "Redo an undone change", keys: combo(platforms, ["Ctrl", "r"]) }, @@ -74,7 +74,7 @@ export const vim: AppShortcuts = { title: "Search, replace, and folds", intro: "Search forward or backward, move through matches, replace text, and reveal or hide folded code.", shortcuts: [ - { id: "search-forward", action: "Search forward", keys: combo(platforms, ["/"]), note: "Type a pattern and press Enter. ? searches backward." }, + { id: "search-forward", action: "Search forward", keys: combo(platforms, ["/", "then", "{pattern}", "then", "Enter"]), note: "? searches backward." }, { id: "search-next", action: "Go to the next search match", keys: combo(platforms, ["n"]), note: "N goes to the previous match." }, { id: "search-word-forward", action: "Search forward for the word under the cursor", keys: combo(platforms, ["*"]), note: "# searches backward." }, { id: "clear-search-highlight", action: "Clear search highlighting", keys: combo(platforms, [":", "nohlsearch"]) }, @@ -105,7 +105,7 @@ export const vim: AppShortcuts = { { id: "quit", action: "Quit Vim", keys: combo(platforms, [":", "q"]), note: ":wq or :x writes and quits; :q! quits without saving." }, { id: "write-quit", action: "Write and quit", keys: combo(platforms, [":", "w", "q"]) }, { id: "quit-without-saving", action: "Quit without saving", keys: combo(platforms, [":", "q", "!"]) }, - { id: "edit-file", action: "Edit a file", keys: combo(platforms, [":", "e"]), note: "Type :e followed by a path, then press Enter." }, + { id: "edit-file", action: "Edit a file", keys: combo(platforms, [":", "e", "then", "Space", "then", "{path}", "then", "Enter"]) }, { id: "next-buffer", action: "Edit the next buffer", keys: combo(platforms, [":", "bn"]) }, { id: "previous-buffer", action: "Edit the previous buffer", keys: combo(platforms, [":", "bp"]) }, { id: "command-line", action: "Open the command-line prompt", keys: combo(platforms, [":"]) }, diff --git a/lib/shortcuts/apps/visual-studio.ts b/lib/shortcuts/apps/visual-studio.ts index cd28468d..7656233f 100644 --- a/lib/shortcuts/apps/visual-studio.ts +++ b/lib/shortcuts/apps/visual-studio.ts @@ -16,10 +16,10 @@ export const visualStudio: AppShortcuts = { category: "Code editor", platforms: [...platforms], versionContext: - "Microsoft Learn's Visual Studio default keyboard shortcut reference checked September 1, 2026. The map uses the General profile; Visual C#, Visual Basic, keyboard layout, language settings, extensions, and user changes can assign different bindings.", + "Microsoft Learn's Visual Studio default keyboard shortcut reference checked November 1, 2026. The map uses the General profile; Visual C#, Visual Basic, keyboard layout, language settings, extensions, and user changes can assign different bindings.", sourceUrl: "https://learn.microsoft.com/en-us/visualstudio/ide/default-keyboard-shortcuts-in-visual-studio?view=visualstudio", sourceLabel: "official Visual Studio default keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "build-and-debug", diff --git a/lib/shortcuts/apps/webstorm.ts b/lib/shortcuts/apps/webstorm.ts index b8ac93b3..4bb0c07c 100644 --- a/lib/shortcuts/apps/webstorm.ts +++ b/lib/shortcuts/apps/webstorm.ts @@ -16,19 +16,19 @@ export const webstorm: AppShortcuts = { category: "Code editor", platforms: [...platforms], versionContext: - "JetBrains WebStorm documentation and default reference cards checked September 1, 2026. The current WebStorm 2026.2 keymap is configurable, can be replaced by a plugin or another predefined map, and may vary with keyboard layout or operating-system conflicts.", + "JetBrains WebStorm documentation and default reference cards checked November 1, 2026. The current WebStorm 2026.2 keymap is configurable, can be replaced by a plugin or another predefined map, and may vary with keyboard layout or operating-system conflicts.", sourceUrl: "https://www.jetbrains.com/help/webstorm/mastering-keyboard-shortcuts.html", sourceLabel: "official WebStorm keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "search-and-navigation", title: "Search and navigation", intro: "Find files, actions, symbols, usages, recent documents, errors, and definitions throughout a WebStorm project.", shortcuts: [ - desktop("search-everywhere", "Search Everywhere", ["Shift", "Shift"], ["Shift", "Shift"], "Press Shift twice."), + desktop("search-everywhere", "Search Everywhere", ["Shift", "then", "Shift"], ["Shift", "then", "Shift"]), desktop("find-action", "Find and run an action", ["Ctrl", "Shift", "A"], ["Cmd", "Shift", "A"]), - desktop("run-anything", "Open Run Anything", ["Ctrl", "Ctrl"], ["Ctrl", "Ctrl"], "Press Ctrl twice."), + desktop("run-anything", "Open Run Anything", ["Ctrl", "then", "Ctrl"], ["Ctrl", "then", "Ctrl"]), desktop("recent-files", "Open recent files", ["Ctrl", "E"], ["Cmd", "E"]), desktop("find", "Find in the current file", ["Ctrl", "F"], ["Cmd", "F"]), desktop("replace", "Replace in the current file", ["Ctrl", "R"], ["Cmd", "R"]), diff --git a/lib/shortcuts/apps/whimsical.ts b/lib/shortcuts/apps/whimsical.ts index e2c43453..e0e3574c 100644 --- a/lib/shortcuts/apps/whimsical.ts +++ b/lib/shortcuts/apps/whimsical.ts @@ -23,16 +23,34 @@ const shared = (id: string, action: string, keys: string[], note?: string): Shor ...(note ? { note } : {}), }); +const dualWithAlternatives = ( + id: string, + action: string, + windows: string[], + windowsAlternative: string[], + macos: string[], + macosAlternative: string[], + note?: string, +): Shortcut => ({ + id, + action, + keys: [ + { platform: "windows", keys: windows, alternatives: [windowsAlternative] }, + { platform: "macos", keys: macos, alternatives: [macosAlternative] }, + ], + ...(note ? { note } : {}), +}); + export const whimsical: AppShortcuts = { slug: "whimsical", name: "Whimsical", category: "Visual collaboration", platforms: [...platforms], versionContext: - "Whimsical shortcut tables checked September 1, 2026. The source lists the Windows and Mac modifiers separately, and some shortcuts depend on the active board or editor.", + "Whimsical shortcut tables checked November 1, 2026. The source lists the Windows and Mac modifiers separately, and some shortcuts depend on the active board or editor.", sourceUrl: "https://whimsical.com/learn/shortcuts", sourceLabel: "official Whimsical keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "workspace", @@ -43,15 +61,22 @@ export const whimsical: AppShortcuts = { dual("select-all", "Select all", ["Ctrl", "A"], ["Cmd", "A"]), shared("deselect", "Deselect", ["Escape"]), dual("file-search", "Search files", ["Ctrl", "F"], ["Cmd", "F"]), - dual("workspace-search", "Search the workspace", ["Ctrl", "J"], ["Cmd", "J"], "Ctrl+Shift+F on Windows and Cmd+Shift+F on Mac also open workspace search."), + dualWithAlternatives("workspace-search", "Search the workspace", ["Ctrl", "J"], ["Ctrl", "Shift", "F"], ["Cmd", "J"], ["Cmd", "Shift", "F"]), dual("toggle-sidebar", "Show or hide the sidebar", ["Ctrl", "E"], ["Cmd", "E"]), - dual("share", "Share the current file", ["Ctrl", "Shift", "S"], ["Cmd", "Shift", "S"]), + dualWithAlternatives("share", "Share the current file", ["Ctrl", "Shift", "S"], ["Ctrl", "Alt", "S"], ["Cmd", "Shift", "S"], ["Cmd", "Option", "S"]), dual("export", "Export the current file", ["Ctrl", "Shift", "E"], ["Cmd", "Shift", "E"]), dual("print", "Print the current file", ["Ctrl", "P"], ["Cmd", "P"]), dual("new-file", "Create a new file or folder", ["Ctrl", "Alt", "N"], ["Cmd", "Option", "N"]), shared("list-view", "Switch to list view", ["L"]), shared("grid-view", "Switch to grid view", ["G"]), - dual("delete-file", "Delete the selected file or folder", ["Delete"], ["Delete"], "Backspace is also documented on both platforms."), + { + id: "delete-file", + action: "Delete the selected file or folder", + keys: [ + { platform: "windows", keys: ["Delete"], alternatives: [["Backspace"]] }, + { platform: "macos", keys: ["Delete"], alternatives: [["Backspace"]] }, + ], + }, ], }, { @@ -116,8 +141,8 @@ export const whimsical: AppShortcuts = { dual("mind-map-parent", "Add a parent node", ["Alt", "Enter"], ["Option", "Enter"]), dual("mind-map-collapse", "Collapse or expand a node", ["Ctrl", "/"], ["Cmd", "/"]), dual("mind-map-line-break", "Insert a line break in a node", ["Shift", "Enter"], ["Shift", "Enter"]), - dual("mind-map-indent", "Indent a node", ["Ctrl", "Alt", "["], ["Cmd", "Option", "["]), - dual("mind-map-outdent", "Outdent a node", ["Ctrl", "Alt", "]"], ["Cmd", "Option", "]"]), + dual("mind-map-indent", "Decrease text indent", ["Ctrl", "Alt", "["], ["Cmd", "Ctrl", "["]), + dual("mind-map-outdent", "Increase text indent", ["Ctrl", "Alt", "]"], ["Cmd", "Ctrl", "]"]), ], }, { @@ -129,8 +154,8 @@ export const whimsical: AppShortcuts = { dual("sticky-size-up", "Increase sticky-note size", ["Ctrl", "Alt", "="], ["Cmd", "Option", "="]), dual("sticky-size-down", "Decrease sticky-note size", ["Ctrl", "Alt", "-"], ["Cmd", "Option", "-"]), dual("paragraph", "Change paragraph alignment", ["Ctrl", "\\"], ["Cmd", "\\"]), - dual("bulleted-list", "Toggle a bulleted list", ["Ctrl", "Shift", "8"], ["Cmd", "Shift", "8"]), - dual("numbered-list", "Toggle a numbered list", ["Ctrl", "Shift", "7"], ["Cmd", "Shift", "7"]), + dualWithAlternatives("bulleted-list", "Start or toggle a bulleted list", ["*", "then", "Space"], ["Ctrl", "Shift", "8"], ["*", "then", "Space"], ["Cmd", "Shift", "8"], "Typing - then Space also starts a bulleted list."), + dualWithAlternatives("numbered-list", "Start or toggle a numbered list", ["1", "then", ".", "then", "Space"], ["Ctrl", "Shift", "7"], ["1", "then", ".", "then", "Space"], ["Cmd", "Shift", "7"]), dual("external-link", "Insert an external link", ["Ctrl", "Shift", "U"], ["Cmd", "Shift", "U"]), ], }, diff --git a/lib/shortcuts/apps/windows-file-explorer.ts b/lib/shortcuts/apps/windows-file-explorer.ts index 28f3408f..34c2769e 100644 --- a/lib/shortcuts/apps/windows-file-explorer.ts +++ b/lib/shortcuts/apps/windows-file-explorer.ts @@ -16,10 +16,10 @@ export const windowsFileExplorer: AppShortcuts = { category: "File management", platforms: [...platforms], versionContext: - "Microsoft Windows keyboard shortcuts checked September 1, 2026. The source applies to Windows 10 and Windows 11, and some commands depend on the focused File Explorer pane.", + "Microsoft Windows keyboard shortcuts checked November 1, 2026. The source applies to Windows 10 and Windows 11, and some commands depend on the focused File Explorer pane.", sourceUrl: "https://support.microsoft.com/en-us/windows/keyboard-shortcuts-in-windows-dcc61a57-8ff0-cffe-9796-cb9706c75eec", sourceLabel: "official Microsoft Windows keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "file-explorer", diff --git a/lib/shortcuts/apps/windows-snipping-tool.ts b/lib/shortcuts/apps/windows-snipping-tool.ts index c25ae7cd..b83272c7 100644 --- a/lib/shortcuts/apps/windows-snipping-tool.ts +++ b/lib/shortcuts/apps/windows-snipping-tool.ts @@ -16,10 +16,10 @@ export const windowsSnippingTool: AppShortcuts = { category: "Screen capture", platforms: [...platforms], versionContext: - "Microsoft Snipping Tool help checked September 1, 2026. The capture overlay supports screenshots and screen recordings on current Windows.", + "Microsoft Snipping Tool help checked November 1, 2026. The capture overlay supports screenshots and screen recordings on current Windows.", sourceUrl: "https://support.microsoft.com/en-US/Windows/Apps/use-snipping-tool-to-capture-screenshots", sourceLabel: "official Microsoft Snipping Tool shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "capture", diff --git a/lib/shortcuts/apps/windows-terminal.ts b/lib/shortcuts/apps/windows-terminal.ts index 54b4d886..603c86f0 100644 --- a/lib/shortcuts/apps/windows-terminal.ts +++ b/lib/shortcuts/apps/windows-terminal.ts @@ -7,10 +7,10 @@ export const windowsTerminal: AppShortcuts = { category: "Terminal emulator", platforms: ["windows"], versionContext: - "Current Windows Terminal documentation as of August 2026; most bindings listed are defaults from the official Actions reference and can be changed in settings.", + "Current Windows Terminal documentation checked November 1, 2026; most bindings listed are defaults from the official Actions reference and can be changed in settings.", sourceUrl: "https://learn.microsoft.com/en-us/windows/terminal/customize-settings/actions", sourceLabel: "official Windows Terminal actions", - lastVerified: "2026-08-26", + lastVerified: "2026-11-01", groups: [ { id: "tabs", diff --git a/lib/shortcuts/apps/windows.ts b/lib/shortcuts/apps/windows.ts index a78a2935..941ed905 100644 --- a/lib/shortcuts/apps/windows.ts +++ b/lib/shortcuts/apps/windows.ts @@ -8,10 +8,10 @@ export const windows: AppShortcuts = { category: "Operating system", platforms: ["windows"], versionContext: - "Default Windows 10 and Windows 11 bindings from Microsoft Support checked in August 2026. Individual apps may override general bindings, and some keys depend on a feature, keyboard layout, taskbar focus, or an enabled accessibility setting.", + "Default Windows 10 and Windows 11 bindings from Microsoft Support checked November 1, 2026. Individual apps may override general bindings, and some keys depend on a feature, keyboard layout, taskbar focus, or an enabled accessibility setting.", sourceUrl: "https://support.microsoft.com/en-us/windows/keyboard-shortcuts-in-windows-dcc61a57-8ff0-cffe-9796-cb9706c75eec", sourceLabel: "official Microsoft Windows shortcut reference", - lastVerified: "2026-08-26", + lastVerified: "2026-11-01", groups: [ { id: "text-editing", diff --git a/lib/shortcuts/apps/winmerge.ts b/lib/shortcuts/apps/winmerge.ts index 578a63c2..590e6d4a 100644 --- a/lib/shortcuts/apps/winmerge.ts +++ b/lib/shortcuts/apps/winmerge.ts @@ -16,10 +16,10 @@ export const winmerge: AppShortcuts = { category: "File comparison", platforms: [...platforms], versionContext: - "WinMerge manual shortcut page checked September 1, 2026. Some commands depend on whether a folder comparison, file comparison, or merge view has focus.", + "WinMerge manual shortcut page checked November 1, 2026. Some commands depend on whether a folder comparison, file comparison, or merge view has focus.", sourceUrl: "https://manual.winmerge.org/en/Shortcut_keys.html", sourceLabel: "official WinMerge shortcut keys", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "comparison", diff --git a/lib/shortcuts/apps/wireshark.ts b/lib/shortcuts/apps/wireshark.ts index dea5c3c3..452021f7 100644 --- a/lib/shortcuts/apps/wireshark.ts +++ b/lib/shortcuts/apps/wireshark.ts @@ -22,10 +22,10 @@ export const wireshark: AppShortcuts = { category: "Network analysis", platforms: [...platforms], versionContext: - "Current Wireshark User's Guide 4.7.3 checked October 10, 2026. The guide uses the same menu shortcuts across desktop platforms, with Option replacing Alt for packet-selection history on macOS.", + "Current Wireshark User's Guide 5.1.0 checked November 1, 2026. The guide uses the same menu shortcuts across Windows, macOS, and Linux, with Option replacing Alt for packet-selection history on macOS.", sourceUrl: "https://www.wireshark.org/docs/wsug_html/", sourceLabel: "official Wireshark User's Guide", - lastVerified: "2026-10-10", + lastVerified: "2026-11-01", groups: [ { id: "packet-navigation", diff --git a/lib/shortcuts/apps/wix.ts b/lib/shortcuts/apps/wix.ts index 37648f65..eeacebf6 100644 --- a/lib/shortcuts/apps/wix.ts +++ b/lib/shortcuts/apps/wix.ts @@ -29,10 +29,10 @@ export const wix: AppShortcuts = { category: "Website builder", platforms: [...platforms], versionContext: - "Wix Editor shortcut article checked September 1, 2026. The source lists Windows and Mac combinations and notes that some commands depend on the editor focus.", + "Wix Editor shortcut article checked November 1, 2026. The source lists Windows and Mac combinations and notes that some commands depend on the editor focus.", sourceUrl: "https://support.wix.com/en/article/wix-editor-keyboard-shortcuts-in-the-editor", sourceLabel: "official Wix Editor keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "editing", diff --git a/lib/shortcuts/apps/wondershare-filmora.ts b/lib/shortcuts/apps/wondershare-filmora.ts index 35bd29c8..4a47bc78 100644 --- a/lib/shortcuts/apps/wondershare-filmora.ts +++ b/lib/shortcuts/apps/wondershare-filmora.ts @@ -29,10 +29,10 @@ export const wondershareFilmora: AppShortcuts = { category: "Video editing", platforms: [...platforms], versionContext: - "Filmora V15 release notes and current Windows and Mac shortcut lists checked September 1, 2026. Filmora lets users edit or switch shortcut presets.", + "Filmora V15 release notes and current Windows and Mac shortcut lists checked November 1, 2026. Filmora lets users edit or switch shortcut presets.", sourceUrl: "https://filmora.wondershare.com/guide/keyboard-shortcuts-on-windows.html", sourceLabel: "official Wondershare Filmora keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "file-and-edit", @@ -49,7 +49,7 @@ export const wondershareFilmora: AppShortcuts = { dual("cut", "Cut", ["Ctrl", "X"], ["Cmd", "X"]), dual("copy", "Copy", ["Ctrl", "C"], ["Cmd", "C"]), dual("paste", "Paste", ["Ctrl", "V"], ["Cmd", "V"]), - shared("delete", "Delete the selected clip", ["Delete"], "Filmora also documents separate ripple-delete commands."), + { id: "delete", action: "Delete the selected clip", keys: perPlatform(platforms, { windows: ["Delete"], macos: ["Backspace"] }), note: "Filmora also documents separate ripple-delete commands." }, shared("ripple-delete", "Ripple-delete the selected clip", ["Shift", "Delete"]), dual("select-all", "Select all", ["Ctrl", "A"], ["Cmd", "A"]), dual("duplicate", "Duplicate the selected clip", ["Ctrl", "D"], ["Cmd", "D"]), @@ -67,7 +67,7 @@ export const wondershareFilmora: AppShortcuts = { dual("trim-end", "Trim the clip end to the playhead", ["Alt", "]"], ["Option", "]"]), dual("move-clip-left", "Move the selected clip left", ["Alt", "Left Arrow"], ["Option", "Left Arrow"]), dual("move-clip-right", "Move the selected clip right", ["Alt", "Right Arrow"], ["Option", "Right Arrow"]), - dual("audio-mixer", "Open the audio mixer", ["Alt", "E"], ["Option", "E"]), + { id: "audio-mixer", action: "Open the audio mixer", keys: perPlatform(platforms, { windows: ["Alt", "E"] }) }, dual("crop", "Open crop and zoom", ["Alt", "C"], ["Option", "C"]), dual("group", "Group selected clips", ["Ctrl", "G"], ["Cmd", "G"]), dual("ungroup", "Ungroup selected clips", ["Ctrl", "Alt", "G"], ["Cmd", "Option", "G"]), @@ -75,7 +75,7 @@ export const wondershareFilmora: AppShortcuts = { dual("rename", "Rename the selected item", ["F2"], ["F2"]), dual("reveal", "Reveal the selected item in the media panel", ["Ctrl", "Shift", "R"], ["Cmd", "Shift", "R"]), dual("favorite", "Add the selected item to favorites", ["Shift", "F"], ["Shift", "F"]), - dual("shortcut-panel", "Open the keyboard shortcuts panel", ["Ctrl", "Alt", "K"], ["Cmd", "Option", "K"]), + dual("shortcut-panel", "Open the keyboard shortcuts panel", ["Ctrl", "Alt", "K"], ["Ctrl", "Cmd", "K"]), ], }, { @@ -84,8 +84,8 @@ export const wondershareFilmora: AppShortcuts = { intro: "Use these controls to play the timeline, move the playhead, and change the timeline zoom.", shortcuts: [ shared("play-pause", "Play or pause", ["Space"]), - dual("stop", "Stop playback", ["Ctrl", "/"], ["Cmd", "/"]), - dual("fullscreen", "Enter or leave full screen", ["Alt", "Enter"], ["Cmd", "Option", "Enter"]), + dual("stop", "Stop playback", ["Ctrl", "/"], ["Ctrl", "/"]), + dual("fullscreen", "Play the preview full screen", ["Alt", "Enter"], ["Option", "Enter"]), shared("previous-frame", "Move to the previous frame", ["Left Arrow"]), shared("next-frame", "Move to the next frame", ["Right Arrow"]), shared("previous-second", "Move backward by one second", ["Shift", "Left Arrow"]), diff --git a/lib/shortcuts/apps/wordpress-admin.ts b/lib/shortcuts/apps/wordpress-admin.ts index f07a20d0..bdaab124 100644 --- a/lib/shortcuts/apps/wordpress-admin.ts +++ b/lib/shortcuts/apps/wordpress-admin.ts @@ -29,10 +29,10 @@ export const wordpressAdmin: AppShortcuts = { category: "Content management", platforms: [...platforms], versionContext: - "WordPress Block Editor, Classic Editor, and Site Editor references checked September 1, 2026. The Block Editor table and Classic Editor table publish Windows or Linux and Mac variants, while the Site Editor documents its command palette separately.", + "WordPress Block Editor, Classic Editor, and Site Editor references checked November 1, 2026. The Block Editor table and Classic Editor table publish Windows or Linux and Mac variants, while the Site Editor documents its command palette separately.", sourceUrl: "https://wordpress.org/documentation/article/keyboard-shortcuts-block-editor/", sourceLabel: "official WordPress editor keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "block-editor", diff --git a/lib/shortcuts/apps/wrike.ts b/lib/shortcuts/apps/wrike.ts index a6055ffd..b739971c 100644 --- a/lib/shortcuts/apps/wrike.ts +++ b/lib/shortcuts/apps/wrike.ts @@ -36,10 +36,10 @@ export const wrike: AppShortcuts = { category: "Project management", platforms: [...platforms], versionContext: - "Wrike keyboard shortcut and Gantt chart shortcut pages checked September 1, 2026. Some commands depend on the current view, browser, or focus inside a task description.", + "Wrike keyboard shortcut and Gantt chart shortcut pages checked November 1, 2026. Some commands depend on the current view, browser, or focus inside a task description.", sourceUrl: "https://help.wrike.com/hc/en-us/articles/210322725-Keyboard-Shortcuts", sourceLabel: "official Wrike keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "global", diff --git a/lib/shortcuts/apps/x-twitter.ts b/lib/shortcuts/apps/x-twitter.ts index b7a6bcb7..595dcbf3 100644 --- a/lib/shortcuts/apps/x-twitter.ts +++ b/lib/shortcuts/apps/x-twitter.ts @@ -29,10 +29,10 @@ export const xTwitter: AppShortcuts = { category: "Social media", platforms: [...platforms], versionContext: - "X Help Center keyboard shortcut list checked October 10, 2026. These shortcuts apply to the desktop web experience on x.com. The Help Center still lists a Moments destination that is no longer included here.", + "X Help Center keyboard shortcut list checked November 1, 2026. These shortcuts apply to the desktop web experience on x.com. The Help Center still lists a Moments destination that is no longer included here.", sourceUrl: "https://help.x.com/en/using-x/how-to-post", sourceLabel: "official X Help Center keyboard shortcuts", - lastVerified: "2026-10-10", + lastVerified: "2026-11-01", groups: [ { id: "posts", @@ -67,7 +67,7 @@ export const xTwitter: AppShortcuts = { { id: "go-to", title: "Go to areas", - intro: "Press G followed by the listed key to open a common X area.", + intro: "Press the keys in order to jump to Home, Notifications, Messages, and other X areas.", shortcuts: [ shared("home", "Go to Home", ["G", "then", "H"]), shared("notifications", "Go to Notifications", ["G", "then", "N"]), diff --git a/lib/shortcuts/apps/youtrack.ts b/lib/shortcuts/apps/youtrack.ts index f3e9239f..55c218d1 100644 --- a/lib/shortcuts/apps/youtrack.ts +++ b/lib/shortcuts/apps/youtrack.ts @@ -29,10 +29,10 @@ export const youtrack: AppShortcuts = { category: "Project management", platforms: [...platforms], versionContext: - "YouTrack Cloud 2026.2 keyboard shortcuts checked September 1, 2026. The source gives Windows or Linux and macOS variants, and many commands depend on the focused issue list or editor.", + "YouTrack Cloud 2026.2 keyboard shortcuts checked November 1, 2026. The source gives Windows or Linux and macOS variants, and many commands depend on the focused issue list or editor.", sourceUrl: "https://www.jetbrains.com/help/youtrack/cloud/keyboard-shortcuts.html", sourceLabel: "official YouTrack keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "issue-list", diff --git a/lib/shortcuts/apps/zbrush.ts b/lib/shortcuts/apps/zbrush.ts index 6c1df200..80c94f14 100644 --- a/lib/shortcuts/apps/zbrush.ts +++ b/lib/shortcuts/apps/zbrush.ts @@ -29,10 +29,10 @@ export const zbrush: AppShortcuts = { category: "3D sculpting", platforms: [...platforms], versionContext: - "Maxon ZBrush keyboard shortcut guide and default hotkeys reference checked September 1, 2026. Maxon documents Ctrl to Cmd and Alt to Option equivalents for Mac, while some popup selections are PC-only.", + "Maxon ZBrush keyboard shortcut guide and default hotkeys reference checked November 1, 2026. Maxon documents Ctrl to Cmd and Alt to Option equivalents for Mac, while some popup selections are PC-only.", sourceUrl: "https://help.maxon.net/zbr/en-us/Content/html/user-guide/keyboard-shortcuts/keyboard-shortcuts.html", sourceLabel: "official Maxon ZBrush keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "project-and-interface", diff --git a/lib/shortcuts/apps/zen-browser.ts b/lib/shortcuts/apps/zen-browser.ts index 12ffe19c..c29a31b2 100644 --- a/lib/shortcuts/apps/zen-browser.ts +++ b/lib/shortcuts/apps/zen-browser.ts @@ -16,10 +16,10 @@ export const zenBrowser: AppShortcuts = { category: "Web browser", platforms: [...platforms], versionContext: - "Zen Browser shortcut reference checked October 10, 2026. The published table uses Ctrl and Alt notation without operating-system variants, so this record keeps the published notation for Windows, macOS, and Linux.", + "Zen Browser shortcut reference checked November 1, 2026. The published table uses Ctrl and Alt notation without operating-system variants, so this record keeps the published notation for Windows, macOS, and Linux.", sourceUrl: "https://docs.zen-browser.app/user-manual/shortcuts", sourceLabel: "official Zen Browser shortcuts", - lastVerified: "2026-10-10", + lastVerified: "2026-11-01", groups: [ { id: "zen-layout", diff --git a/lib/shortcuts/apps/zendesk.ts b/lib/shortcuts/apps/zendesk.ts index 8639d11f..f0123ed6 100644 --- a/lib/shortcuts/apps/zendesk.ts +++ b/lib/shortcuts/apps/zendesk.ts @@ -22,10 +22,10 @@ export const zendesk: AppShortcuts = { category: "Customer support", platforms: [...platforms], versionContext: - "Zendesk Agent Workspace keyboard shortcut cheat sheet and dashboard shortcut article checked September 1, 2026. The cheat sheet uses Ctrl or Cmd notation, and some commands depend on the active ticket or editor.", + "Zendesk Agent Workspace keyboard shortcut cheat sheet and dashboard shortcut article checked November 1, 2026. The cheat sheet uses Ctrl or Cmd notation, and some commands depend on the active ticket or editor.", sourceUrl: "https://static.zdassets.com/agent/assets/keyboard-shortcut-cheatsheet-9bad3e7d4d6f5c2affb9d23e2f23ce22.pdf", sourceLabel: "official Zendesk Agent Workspace shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "navigation", @@ -78,10 +78,10 @@ export const zendesk: AppShortcuts = { dual("bold", "Bold text", ["Ctrl", "B"], ["Cmd", "B"]), dual("italic", "Italicize text", ["Ctrl", "I"], ["Cmd", "I"]), dual("code", "Format inline code", ["Ctrl", "Shift", "5"], ["Cmd", "Shift", "5"]), - dual("code-block", "Format a code block", ["Shift", "6"], ["Shift", "6"]), - dual("numbered-list", "Toggle a numbered list", ["Shift", "7"], ["Shift", "7"]), - dual("bulleted-list", "Toggle a bulleted list", ["Shift", "8"], ["Shift", "8"]), - dual("quote", "Format a quote", ["Shift", "9"], ["Shift", "9"]), + dual("code-block", "Format a code block", ["Ctrl", "Shift", "6"], ["Cmd", "Shift", "6"]), + dual("numbered-list", "Toggle a numbered list", ["Ctrl", "Shift", "7"], ["Cmd", "Shift", "7"]), + dual("bulleted-list", "Toggle a bulleted list", ["Ctrl", "Shift", "8"], ["Cmd", "Shift", "8"]), + dual("quote", "Format a quote", ["Ctrl", "Shift", "9"], ["Cmd", "Shift", "9"]), ], }, ], diff --git a/lib/shortcuts/apps/zeplin.ts b/lib/shortcuts/apps/zeplin.ts index 82a5f309..861d100e 100644 --- a/lib/shortcuts/apps/zeplin.ts +++ b/lib/shortcuts/apps/zeplin.ts @@ -29,10 +29,10 @@ export const zeplin: AppShortcuts = { category: "Design handoff", platforms: [...platforms], versionContext: - "Zeplin keyboard shortcut article checked September 1, 2026. The article also lists a Web app table, while this record uses the published Mac and Windows app maps.", + "Zeplin keyboard shortcut article checked November 1, 2026. The article lists Mac and Windows app maps; this record uses those desktop bindings.", sourceUrl: "https://support.zeplin.io/en/articles/9685993-keyboard-shortcuts", sourceLabel: "official Zeplin keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "navigation", @@ -41,7 +41,7 @@ export const zeplin: AppShortcuts = { shortcuts: [ dual("search", "Open global search", ["Ctrl", "K"], ["Cmd", "K"]), dual("new-project", "Create a new project or styleguide", ["Ctrl", "N"], ["Cmd", "N"]), - dual("workspace", "Open workspace switcher", ["Ctrl", "Shift", "A"], ["Cmd", "Shift", "A"]), + dual("workspace", "Go to the workspace", ["Ctrl", "Shift", "A"], ["Cmd", "Shift", "A"]), dual("dashboard", "Open the dashboard", ["Alt", "A"], ["Option", "A"]), dual("styleguide", "Open the styleguide", ["Alt", "S"], ["Option", "S"]), dual("flows", "Open flows", ["Alt", "L"], ["Option", "L"]), @@ -75,11 +75,18 @@ export const zeplin: AppShortcuts = { title: "Zoom and layout", intro: "Change the canvas zoom or show the grid for the selected screen.", shortcuts: [ - dual("zoom-in", "Zoom in", ["Ctrl", "+"], ["Cmd", ":"]), + { + id: "zoom-in", + action: "Zoom in", + keys: [ + { platform: "windows", keys: ["Ctrl", "+"] }, + { platform: "macos", keys: ["Cmd", ":"], alternatives: [["Cmd", "*"], ["Cmd", "="]] }, + ], + }, dual("zoom-out", "Zoom out", ["Ctrl", "-"], ["Cmd", "-"]), dual("zoom-actual", "Set zoom to actual size", ["Ctrl", "0"], ["Cmd", "0"]), dual("zoom-fit", "Fit the screen to the window", ["Ctrl", "1"], ["Cmd", "1"]), - dual("zoom-selection", "Zoom to the selected layer", ["Ctrl", "2"], ["Cmd", "2"]), + dual("zoom-selection", "Zoom to the selected layer", ["Ctrl", "2"], ["Cmd", "2"], "Select a layer first."), dual("grid", "Show or hide the grid", ["Ctrl", "Alt", "G"], ["Cmd", "Option", "G"]), ], }, diff --git a/lib/shortcuts/apps/zoom.ts b/lib/shortcuts/apps/zoom.ts index 9d841a83..8de21531 100644 --- a/lib/shortcuts/apps/zoom.ts +++ b/lib/shortcuts/apps/zoom.ts @@ -23,10 +23,10 @@ export const zoom: AppShortcuts = { category: "Video conferencing", platforms: [...platforms], versionContext: - "Zoom desktop keyboard shortcut reference checked September 1, 2026. Zoom notes that some shortcuts require a higher app version than the global minimum and that shortcuts can be edited in settings.", + "Zoom desktop keyboard shortcut reference checked November 1, 2026. Zoom notes that some shortcuts require a higher app version than the global minimum and that shortcuts can be edited in settings.", sourceUrl: "https://support.zoom.com/hc/en/article?id=zm_kb&sysparm_article=KB0067050", sourceLabel: "official Zoom desktop keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "meeting", diff --git a/lib/shortcuts/apps/zulip.ts b/lib/shortcuts/apps/zulip.ts index 6b90eda4..5a157ab1 100644 --- a/lib/shortcuts/apps/zulip.ts +++ b/lib/shortcuts/apps/zulip.ts @@ -16,10 +16,10 @@ export const zulip: AppShortcuts = { category: "Team chat", platforms: [...platforms], versionContext: - "Zulip keyboard shortcut reference checked September 1, 2026. The official Web and Desktop table uses Ctrl notation and includes the same app-level map for the listed desktop platforms.", + "Zulip keyboard shortcut reference checked November 1, 2026. The official Web and Desktop table uses Ctrl notation and includes the same app-level map for the listed desktop platforms.", sourceUrl: "https://zulip.com/help/keyboard-shortcuts", sourceLabel: "official Zulip keyboard shortcuts", - lastVerified: "2026-09-01", + lastVerified: "2026-11-01", groups: [ { id: "basics", @@ -74,13 +74,12 @@ export const zulip: AppShortcuts = { { id: "compose", title: "Compose", - intro: "Write replies, topics, and direct messages with formatting and preview controls.", + intro: "Write replies, topics, and direct messages with formatting and preview controls. To send, press Tab then Enter, or use the configured Enter or Ctrl+Enter binding.", shortcuts: [ shared("quote", "Quote the selected message", [">"]), shared("forward", "Forward the selected message", ["<"]), shared("direct-reply", "Reply directly to the selected message", ["Shift", "R"]), shared("mention", "Mention a user", ["@"]), - shared("send", "Send the message", ["Tab", "then", "Enter"], "Enter or Ctrl+Enter may send based on the compose settings."), shared("new-line", "Insert a new line", ["Shift", "Enter"]), shared("bold", "Bold text", ["Ctrl", "B"]), shared("italic", "Italicize text", ["Ctrl", "I"]), diff --git a/lib/shortcuts/catalog.generated.ts b/lib/shortcuts/catalog.generated.ts index 766c454d..be51ff15 100644 --- a/lib/shortcuts/catalog.generated.ts +++ b/lib/shortcuts/catalog.generated.ts @@ -168,7 +168,7 @@ export const shortcutAppCatalog = [ "windows", "macos" ], - "shortcutCount": 106 + "shortcutCount": 105 }, { "slug": "aider", @@ -206,8 +206,7 @@ export const shortcutAppCatalog = [ "category": "Product analytics", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 12 }, @@ -306,8 +305,7 @@ export const shortcutAppCatalog = [ "category": "3D modeling and animation", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 137 }, @@ -375,8 +373,7 @@ export const shortcutAppCatalog = [ "category": "Code hosting", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 15 }, @@ -417,8 +414,7 @@ export const shortcutAppCatalog = [ "category": "Social media management", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 16 }, @@ -479,8 +475,7 @@ export const shortcutAppCatalog = [ "category": "Time tracking", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 12 }, @@ -813,8 +808,7 @@ export const shortcutAppCatalog = [ "category": "Code hosting", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 52 }, @@ -824,8 +818,7 @@ export const shortcutAppCatalog = [ "category": "Email", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 85 }, @@ -858,7 +851,7 @@ export const shortcutAppCatalog = [ "macos", "linux" ], - "shortcutCount": 34 + "shortcutCount": 33 }, { "slug": "goodnotes", @@ -959,7 +952,7 @@ export const shortcutAppCatalog = [ "windows", "macos" ], - "shortcutCount": 7 + "shortcutCount": 6 }, { "slug": "harvest", @@ -1046,8 +1039,7 @@ export const shortcutAppCatalog = [ "category": "Issue tracking", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 10 }, @@ -1068,8 +1060,7 @@ export const shortcutAppCatalog = [ "category": "Developer tools", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 6 }, @@ -1129,8 +1120,7 @@ export const shortcutAppCatalog = [ "category": "Social networking", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 18 }, @@ -1172,7 +1162,7 @@ export const shortcutAppCatalog = [ "windows", "macos" ], - "shortcutCount": 20 + "shortcutCount": 25 }, { "slug": "macos", @@ -1221,7 +1211,7 @@ export const shortcutAppCatalog = [ "macos", "linux" ], - "shortcutCount": 83 + "shortcutCount": 82 }, { "slug": "mattermost", @@ -1251,7 +1241,7 @@ export const shortcutAppCatalog = [ "windows", "macos" ], - "shortcutCount": 63 + "shortcutCount": 62 }, { "slug": "excel", @@ -1299,7 +1289,7 @@ export const shortcutAppCatalog = [ "windows", "macos" ], - "shortcutCount": 61 + "shortcutCount": 57 }, { "slug": "microsoft-powertoys", @@ -1308,7 +1298,7 @@ export const shortcutAppCatalog = [ "platforms": [ "windows" ], - "shortcutCount": 24 + "shortcutCount": 21 }, { "slug": "microsoft-teams", @@ -1318,7 +1308,7 @@ export const shortcutAppCatalog = [ "windows", "macos" ], - "shortcutCount": 30 + "shortcutCount": 29 }, { "slug": "microsoft-word", @@ -1848,7 +1838,7 @@ export const shortcutAppCatalog = [ "windows", "macos" ], - "shortcutCount": 33 + "shortcutCount": 32 }, { "slug": "tableplus", @@ -1858,7 +1848,7 @@ export const shortcutAppCatalog = [ "windows", "macos" ], - "shortcutCount": 37 + "shortcutCount": 36 }, { "slug": "tally-forms", @@ -1866,8 +1856,7 @@ export const shortcutAppCatalog = [ "category": "Forms", "platforms": [ "windows", - "macos", - "linux" + "macos" ], "shortcutCount": 26 }, @@ -1880,7 +1869,7 @@ export const shortcutAppCatalog = [ "macos", "linux" ], - "shortcutCount": 26 + "shortcutCount": 23 }, { "slug": "things-3", @@ -2224,6 +2213,6 @@ export const shortcutAppCatalog = [ "macos", "linux" ], - "shortcutCount": 65 + "shortcutCount": 64 } ] as const satisfies readonly ShortcutAppSummary[]; diff --git a/proxy.ts b/proxy.ts index 7257bc68..235658aa 100644 --- a/proxy.ts +++ b/proxy.ts @@ -5,6 +5,7 @@ import { getPublishedToolPage } from "@/lib/tool-pages"; import { getPublishedDirectoryPage } from "@/lib/directory-pages"; import { sitePageAvailable } from "@/lib/page-source"; import { absoluteUrl } from "@/lib/seo"; +import { queryNeedsNoindex } from "@/lib/seo/query-indexing.mjs"; export async function proxy(request: NextRequest) { const headers = new Headers(request.headers); @@ -13,7 +14,9 @@ export async function proxy(request: NextRequest) { userQuery.delete("_rsc"); const withQueryIndexing = (response: NextResponse) => { if (request.nextUrl.search) { - response.headers.set("X-Robots-Tag", "noindex, follow"); + if (queryNeedsNoindex(request.nextUrl.search)) { + response.headers.set("X-Robots-Tag", "noindex, follow"); + } response.headers.append("Link", `<${absoluteUrl(pathname)}>; rel="canonical"`); } return response; diff --git a/scripts/platform-fees/data/directory.json b/scripts/platform-fees/data/directory.json index ffad1a5f..ec604e63 100644 --- a/scripts/platform-fees/data/directory.json +++ b/scripts/platform-fees/data/directory.json @@ -12310,38 +12310,38 @@ "facts": [ { "key": "fact-1", - "value": "Free; Creator $10/mo ($100/yr); Plus $30/mo ($300/yr). Max: Beacons' pricing page shows $100/mo or $900/yr ($83.3/mo billed yearly); its Max plan article says $90/mo or $900/yr.", - "state": "variable", + "value": "Free; Creator $10/mo ($100/yr); Plus $30/mo ($300/yr); Max $100/mo ($1,000/yr).", + "state": "verified", "source_url": "https://beacons.ai/i/pricing", - "checked_at": "2026-09-30" + "checked_at": "2026-11-01" }, { "key": "fact-2", "value": "9% on Free and Creator; 0% on Plus and Max", "state": "verified", "source_url": "https://beacons.ai/i/pricing", - "checked_at": "2026-09-30" + "checked_at": "2026-11-01" }, { "key": "fact-3", "value": "Stripe: 2.9% + $0.30; PayPal: 3.49% + currency-fixed fee", "state": "verified", "source_url": "https://help.beacons.ai/en/articles/4700289", - "checked_at": "2026-09-30" + "checked_at": "2026-11-01" }, { "key": "fact-4", "value": "Affiliate commission is creator-set; the plan’s Beacons fee still applies", "state": "verified", "source_url": "https://help.beacons.ai/en/articles/4701953", - "checked_at": "2026-09-30" + "checked_at": "2026-11-01" }, { "key": "fact-5", "value": "Public terms do not say who calculates, collects or remits buyer sales tax", "state": "not_public", "source_url": "https://shop.beacons.ai/i/terms-and-conditions", - "checked_at": "2026-09-30" + "checked_at": "2026-11-01" } ], "sources": [ @@ -12366,15 +12366,15 @@ "label": "Plan pricing article" } ], - "checked_at": "2026-09-30", - "comparison_basis": "USD plan pricing. Beacons' Max plan article says $90/mo or $900/yr; its pricing page shows $100/mo, or $900/yr ($83.3/mo).", + "checked_at": "2026-11-01", + "comparison_basis": "USD plan pricing. Max is $100/mo or $1,000/yr ($83.3/mo billed yearly). Card processing is separate.", "fee_percent": null, "fee_basis": null, "sort_order": 9, "card": { "owner": "Beacons", "tag": "Link-in-bio store", - "note": "Beacons' pricing pages list different prices for the Max plan.", + "note": "Plus and Max take no Beacons fee. Card fees still apply.", "take": { "value": "9%", "percent": 9, @@ -12390,7 +12390,7 @@ "lines": [ { "label": "Plans", - "value": "$0 to $30 a month", + "value": "$0 to $100 a month", "fact_key": "fact-1" }, { @@ -12419,7 +12419,7 @@ "value": "0%", "percent": 0, "label": "Plus and Max", - "note": "Plus is $30 a month", + "note": "Plus is $30 a month, Max $100", "fact_key": "fact-2" } ] diff --git a/scripts/self-host-apps/batches/2026-11-01-forge-error-tracking-estimates.json b/scripts/self-host-apps/batches/2026-11-01-forge-error-tracking-estimates.json new file mode 100644 index 00000000..a97bb4c0 --- /dev/null +++ b/scripts/self-host-apps/batches/2026-11-01-forge-error-tracking-estimates.json @@ -0,0 +1,134 @@ +{ + "estimates": [ + { + "slug": "forgejo", + "resource_kind": "ram", + "unit": "mb", + "estimate_min": 512, + "estimate_max": 2048, + "estimate_kind": "estimated", + "confidence": "medium", + "basis": "Small team Git service; indexing, packages, Actions, and concurrent use increase memory needs.", + "source_urls": [ + "https://forgejo.org/docs/latest/admin/installation/docker/", + "https://forgejo.org/docs/latest/admin/installation/binary/" + ], + "review": { + "method": "app_estimate" + } + }, + { + "slug": "forgejo", + "resource_kind": "storage", + "unit": "gb", + "estimate_min": 5, + "estimate_max": 100, + "estimate_kind": "estimated", + "confidence": "medium", + "basis": "Git repositories, LFS objects, packages, and attachments grow under the persistent /data path.", + "source_urls": [ + "https://forgejo.org/docs/latest/admin/installation/docker/" + ], + "review": { + "method": "app_estimate" + } + }, + { + "slug": "gitlab", + "resource_kind": "ram", + "unit": "mb", + "estimate_min": 8192, + "estimate_max": 16384, + "estimate_kind": "estimated", + "confidence": "high", + "basis": "GitLab lists 16 GB as the single-node baseline and at least 8 GB for memory-constrained setups.", + "source_urls": [ + "https://docs.gitlab.com/install/requirements/" + ], + "review": { + "method": "app_estimate" + } + }, + { + "slug": "gitlab", + "resource_kind": "storage", + "unit": "gb", + "estimate_min": 50, + "estimate_max": 200, + "estimate_kind": "estimated", + "confidence": "medium", + "basis": "Application nodes need at least 40 GB plus PostgreSQL (5 to 12 GB) and all repository data.", + "source_urls": [ + "https://docs.gitlab.com/install/requirements/" + ], + "review": { + "method": "app_estimate" + } + }, + { + "slug": "onedev", + "resource_kind": "ram", + "unit": "mb", + "estimate_min": 2048, + "estimate_max": 4096, + "estimate_kind": "estimated", + "confidence": "medium", + "basis": "The Docker guide says OneDev runs well on a 2 core, 2 GB box; CI jobs and code indexing add load.", + "source_urls": [ + "https://docs.onedev.io/installation-guide/run-as-docker-container" + ], + "review": { + "method": "app_estimate" + } + }, + { + "slug": "onedev", + "resource_kind": "storage", + "unit": "gb", + "estimate_min": 5, + "estimate_max": 100, + "estimate_kind": "estimated", + "confidence": "medium", + "basis": "Repositories, build artifacts, and packages grow under the persistent /opt/onedev path.", + "source_urls": [ + "https://docs.onedev.io/installation-guide/run-as-docker-container" + ], + "review": { + "method": "app_estimate" + } + }, + { + "slug": "glitchtip", + "resource_kind": "ram", + "unit": "mb", + "estimate_min": 512, + "estimate_max": 2048, + "estimate_kind": "estimated", + "confidence": "medium", + "basis": "GlitchTip recommends 512 MB for the app; PostgreSQL, Valkey, and a separate worker add memory.", + "source_urls": [ + "https://glitchtip.com/documentation/install", + "https://glitchtip.com/assets/compose.sample.yml" + ], + "review": { + "method": "app_estimate" + } + }, + { + "slug": "glitchtip", + "resource_kind": "storage", + "unit": "gb", + "estimate_min": 10, + "estimate_max": 50, + "estimate_kind": "estimated", + "confidence": "medium", + "basis": "The guide cites about 30 GB for roughly 1 million events per month; retention and volume change this.", + "source_urls": [ + "https://glitchtip.com/documentation/install" + ], + "review": { + "method": "app_estimate" + } + } + ] +} diff --git a/scripts/self-host-apps/batches/2026-11-01-forge-error-tracking-reviewed.json b/scripts/self-host-apps/batches/2026-11-01-forge-error-tracking-reviewed.json new file mode 100644 index 00000000..311588cf --- /dev/null +++ b/scripts/self-host-apps/batches/2026-11-01-forge-error-tracking-reviewed.json @@ -0,0 +1,1034 @@ +{ + "records": [ + { + "candidate_id": "06e70463-e572-4fa1-92dd-27d58ff16fb7", + "app_id": "2481c783-96b7-4537-a2b0-0ebc5a897465", + "decision": "publish", + "app": { + "slug": "forgejo", + "name": "Forgejo", + "summary": "Git hosting with code review, issues, wikis, a package registry, and built-in CI through Forgejo Actions.", + "icon_url": "https://tulgfrqamtcshdtmhvpx.supabase.co/storage/v1/object/public/stack127-media/directories/self-host-apps/icons/forgejo-780b54bfd1e8a93b.png", + "icon_source_url": null, + "website_url": "https://forgejo.org", + "source_code_url": "https://codeberg.org/forgejo/forgejo", + "source_code_host": "codeberg", + "docs_url": "https://forgejo.org/docs/latest/", + "install_docs_url": "https://forgejo.org/docs/latest/admin/installation/docker/", + "docker_url": "https://forgejo.org/docs/latest/admin/installation/docker/", + "license": "GPL-3.0-or-later", + "license_type": "copyleft", + "license_spdx_id": "GPL-3.0-or-later", + "source_model": "open_source", + "publisher_name": "Forgejo", + "publisher_type": "community", + "publisher_url": "https://forgejo.org", + "pricing_model": "free", + "published": true, + "self_hosting_status": "self_hostable", + "maintenance_status": "active", + "docker_support": "official", + "deployment_methods": [ + "docker", + "compose", + "binary", + "truenas_scale", + "umbrel", + "yunohost" + ], + "docker_image": "codeberg.org/forgejo/forgejo:17", + "compose_support": true, + "compose_url": "https://forgejo.org/docs/latest/admin/installation/docker/", + "requires_database": true, + "database_types": [ + "sqlite", + "mysql", + "mariadb", + "postgres" + ], + "ram_min_mb": null, + "ram_recommended_mb": null, + "storage_min_gb": null, + "resource_requirement_kind": "unknown", + "storage_profile": "depends", + "persistent_storage": true, + "surfaces": [ + "web" + ], + "categories": [ + "developer-tools", + "project-task-management" + ], + "summary_source": "curated", + "summary_confidence": "high", + "technical_facts_checked_at": "2026-11-01", + "data_checked_at": "2026-11-01", + "data_confidence": "high", + "data_source_urls": [ + "https://forgejo.org", + "https://forgejo.org/download/", + "https://forgejo.org/faq/", + "https://forgejo.org/docs/latest/admin/installation/docker/", + "https://forgejo.org/docs/latest/admin/installation/binary/", + "https://forgejo.org/docs/latest/admin/installation/database-preparation/", + "https://codeberg.org/forgejo/forgejo", + "https://codeberg.org/forgejo/forgejo/raw/branch/forgejo/LICENSE", + "https://codeberg.org/forgejo/forgejo/raw/branch/forgejo/README.md" + ], + "technical_fact_status": { + "license": { + "status": "published", + "note": "Forgejo v9.0 and later use GPL v3 or any later version; v8.0 and earlier used MIT.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://forgejo.org/faq/", + "https://codeberg.org/forgejo/forgejo/raw/branch/forgejo/README.md" + ] + }, + "source": { + "status": "published", + "note": "The official repository is forgejo/forgejo on Codeberg.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://forgejo.org", + "https://codeberg.org/forgejo/forgejo" + ] + }, + "publisher": { + "status": "published", + "note": "Forgejo is a community project; its domains and trademarks are held by Codeberg e.V.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://forgejo.org/faq/" + ] + }, + "docker": { + "status": "published", + "note": "Forgejo publishes official container images, including a rootless variant.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://forgejo.org/docs/latest/admin/installation/docker/" + ] + }, + "docker_image": { + "status": "published", + "note": "The Docker guide uses codeberg.org/forgejo/forgejo:17; the 17 tag follows 17.0.x patches.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://forgejo.org/docs/latest/admin/installation/docker/" + ] + }, + "compose": { + "status": "published", + "note": "The Docker guide includes Docker Compose examples.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://forgejo.org/docs/latest/admin/installation/docker/" + ] + }, + "database": { + "status": "published", + "note": "Forgejo supports SQLite, MySQL 8.4+, MariaDB 10.6+, and PostgreSQL 14+; SQLite is the default.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://forgejo.org/docs/latest/admin/installation/database-preparation/" + ] + }, + "persistent_storage": { + "status": "published", + "note": "The Docker guide mounts Forgejo data at /data.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://forgejo.org/docs/latest/admin/installation/docker/" + ] + }, + "release": { + "status": "published", + "note": "The download page lists 17.0.1 as the latest release.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://forgejo.org/download/" + ] + }, + "ram": { + "status": "not_published", + "note": "The install docs state no RAM requirement; the directory range estimates a small team Git service.", + "checked_at": "2026-11-01", + "confidence": "medium", + "source_urls": [ + "https://forgejo.org/docs/latest/admin/installation/docker/", + "https://forgejo.org/docs/latest/admin/installation/binary/" + ] + }, + "storage_capacity": { + "status": "not_published", + "note": "No disk requirement is published; repositories, LFS objects, and packages drive growth.", + "checked_at": "2026-11-01", + "confidence": "medium", + "source_urls": [ + "https://forgejo.org/docs/latest/admin/installation/docker/" + ] + } + } + }, + "install_options": [ + { + "method": "docker", + "label": "Docker image", + "url": "https://forgejo.org/docs/latest/admin/installation/docker/", + "image": "codeberg.org/forgejo/forgejo:17", + "support_level": "official", + "source_url": "https://forgejo.org/docs/latest/admin/installation/docker/", + "note": null, + "sort_order": 10 + }, + { + "method": "compose", + "label": "Docker Compose", + "url": "https://forgejo.org/docs/latest/admin/installation/docker/", + "image": null, + "support_level": "official", + "source_url": "https://forgejo.org/docs/latest/admin/installation/docker/", + "note": "Official Compose setup.", + "sort_order": 20 + }, + { + "method": "binary", + "label": "Binary download", + "url": "https://forgejo.org/docs/latest/admin/installation/binary/", + "image": null, + "support_level": "official", + "source_url": "https://forgejo.org/download/", + "note": null, + "sort_order": 25 + }, + { + "method": "truenas_scale", + "label": "TrueNAS SCALE catalog", + "url": "https://apps.truenas.com/catalog/forgejo_community/", + "image": null, + "support_level": "community", + "source_url": "https://apps.truenas.com/catalog/forgejo_community/", + "note": "Community catalog package; publisher support is not implied.", + "sort_order": 30 + }, + { + "method": "yunohost", + "label": "YunoHost catalog", + "url": "https://apps.yunohost.org/app/forgejo", + "image": null, + "support_level": "community", + "source_url": "https://apps.yunohost.org/app/forgejo", + "note": "Community catalog package; publisher support is not implied.", + "sort_order": 50 + }, + { + "method": "umbrel", + "label": "Umbrel catalog", + "url": "https://github.com/getumbrel/umbrel-apps/tree/master/forgejo", + "image": null, + "support_level": "community", + "source_url": "https://github.com/getumbrel/umbrel-apps/tree/master/forgejo", + "note": "Community catalog package; publisher support is not implied.", + "sort_order": 70 + } + ], + "sources": [ + { + "source": "official_product_site", + "source_url": "https://forgejo.org", + "confidence": "high", + "note": "Product purpose and publisher." + }, + { + "source": "official_docs", + "source_url": "https://forgejo.org/docs/latest/admin/installation/docker/", + "confidence": "high", + "note": "Docker image, Compose, and data path." + }, + { + "source": "official_docs", + "source_url": "https://forgejo.org/docs/latest/admin/installation/database-preparation/", + "confidence": "high", + "note": "Supported databases." + }, + { + "source": "official_repository", + "source_url": "https://codeberg.org/forgejo/forgejo/raw/branch/forgejo/LICENSE", + "confidence": "high", + "note": "GPL-3.0 license text." + }, + { + "source": "official_docs", + "source_url": "https://forgejo.org/faq/", + "confidence": "high", + "note": "GPL v3+ since v9.0." + } + ], + "data_source_urls": [], + "data_confidence": "high" + }, + { + "candidate_id": "54ca4fc9-3554-45df-b6c1-de64e9f1e53a", + "app_id": "cfd4f72a-713d-4918-bf1b-2eb5ae529f0e", + "decision": "publish", + "app": { + "slug": "gitlab", + "name": "GitLab Community Edition", + "summary": "Git hosting with merge requests, issue boards, wikis, a container registry, and built-in CI/CD pipelines.", + "icon_url": "https://tulgfrqamtcshdtmhvpx.supabase.co/storage/v1/object/public/stack127-media/directories/self-host-apps/icons/gitlab-85e85e66a46a1df4.png", + "icon_source_url": null, + "website_url": "https://about.gitlab.com/gitlab-community-edition/", + "source_code_url": "https://gitlab.com/gitlab-org/gitlab-foss", + "source_code_host": "gitlab", + "docs_url": "https://docs.gitlab.com/", + "install_docs_url": "https://docs.gitlab.com/install/docker/installation/", + "docker_url": "https://hub.docker.com/r/gitlab/gitlab-ce", + "license": "MIT", + "license_type": "permissive", + "license_spdx_id": "MIT", + "source_model": "open_source", + "publisher_name": "GitLab Inc.", + "publisher_type": "company", + "publisher_url": "https://about.gitlab.com", + "pricing_model": "free", + "published": true, + "self_hosting_status": "self_hostable", + "maintenance_status": "active", + "docker_support": "official", + "deployment_methods": [ + "docker", + "compose", + "package", + "helm", + "umbrel", + "yunohost" + ], + "docker_image": "gitlab/gitlab-ce", + "compose_support": true, + "compose_url": "https://docs.gitlab.com/install/docker/installation/", + "requires_database": true, + "database_types": [ + "postgres", + "redis" + ], + "ram_min_mb": 8192, + "ram_recommended_mb": 16384, + "storage_min_gb": 40, + "resource_requirement_kind": "official_minimum", + "storage_profile": "high", + "persistent_storage": true, + "surfaces": [ + "web" + ], + "categories": [ + "developer-tools", + "project-task-management" + ], + "summary_source": "curated", + "summary_confidence": "high", + "technical_facts_checked_at": "2026-11-01", + "data_checked_at": "2026-11-01", + "data_confidence": "high", + "data_source_urls": [ + "https://about.gitlab.com/gitlab-community-edition/", + "https://about.gitlab.com/install/", + "https://docs.gitlab.com/install/requirements/", + "https://docs.gitlab.com/install/docker/installation/", + "https://docs.gitlab.com/install/package/", + "https://docs.gitlab.com/charts/", + "https://hub.docker.com/r/gitlab/gitlab-ce", + "https://gitlab.com/gitlab-org/gitlab-foss", + "https://gitlab.com/gitlab-org/gitlab-foss/-/raw/master/LICENSE" + ], + "technical_fact_status": { + "license": { + "status": "published", + "note": "Community Edition code is MIT (Expat); docs are CC BY-SA 4.0. Enterprise code under ee/ is excluded from GitLab FOSS.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://gitlab.com/gitlab-org/gitlab-foss/-/raw/master/LICENSE", + "https://gitlab.com/gitlab-org/gitlab/-/raw/master/LICENSE" + ] + }, + "source": { + "status": "published", + "note": "GitLab FOSS is GitLab's read-only mirror of the codebase with proprietary code removed; development happens in gitlab-org/gitlab.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://gitlab.com/gitlab-org/gitlab-foss", + "https://about.gitlab.com/gitlab-community-edition/" + ] + }, + "publisher": { + "status": "published", + "note": "GitLab Inc. holds the copyright and publishes CE packages and images.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://gitlab.com/gitlab-org/gitlab-foss/-/raw/master/LICENSE" + ] + }, + "docker": { + "status": "published", + "note": "GitLab publishes the gitlab/gitlab-ce image for Community Edition.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.gitlab.com/install/docker/installation/", + "https://hub.docker.com/r/gitlab/gitlab-ce" + ] + }, + "docker_image": { + "status": "published", + "note": "The Docker guide names gitlab/gitlab-ce for CE, tagged -ce.0.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.gitlab.com/install/docker/installation/" + ] + }, + "compose": { + "status": "published", + "note": "The Docker guide includes docker-compose.yml examples.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.gitlab.com/install/docker/installation/" + ] + }, + "database": { + "status": "published", + "note": "PostgreSQL is the only supported database; Redis or Valkey stores sessions and background jobs. Both are bundled in the Linux package and image.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.gitlab.com/install/requirements/" + ] + }, + "persistent_storage": { + "status": "published", + "note": "The Docker guide mounts /etc/gitlab, /var/log/gitlab, and /var/opt/gitlab.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.gitlab.com/install/docker/installation/" + ] + }, + "ram": { + "status": "published", + "note": "Requirements list 16 GB as the single-node baseline and at least 8 GB for memory-constrained setups.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.gitlab.com/install/requirements/" + ] + }, + "storage_capacity": { + "status": "published", + "note": "Application nodes need at least 40 GB plus repository and PostgreSQL storage.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.gitlab.com/install/requirements/" + ] + }, + "edition": { + "status": "published", + "note": "GitLab recommends EE packages for new installs, even on Free; CE stays available as the MIT-licensed package.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://about.gitlab.com/gitlab-community-edition/" + ] + } + } + }, + "install_options": [ + { + "method": "docker", + "label": "Docker image", + "url": "https://hub.docker.com/r/gitlab/gitlab-ce", + "image": "gitlab/gitlab-ce", + "support_level": "official", + "source_url": "https://docs.gitlab.com/install/docker/installation/", + "note": null, + "sort_order": 10 + }, + { + "method": "compose", + "label": "Docker Compose", + "url": "https://docs.gitlab.com/install/docker/installation/", + "image": null, + "support_level": "official", + "source_url": "https://docs.gitlab.com/install/docker/installation/", + "note": "Official Compose setup.", + "sort_order": 20 + }, + { + "method": "package", + "label": "Linux package", + "url": "https://docs.gitlab.com/install/package/", + "image": null, + "support_level": "official", + "source_url": "https://docs.gitlab.com/install/package/", + "note": "Install the gitlab-ce package for Community Edition.", + "sort_order": 25 + }, + { + "method": "helm", + "label": "Helm chart", + "url": "https://docs.gitlab.com/charts/", + "image": null, + "support_level": "official", + "source_url": "https://about.gitlab.com/install/", + "note": null, + "sort_order": 28 + }, + { + "method": "yunohost", + "label": "YunoHost catalog", + "url": "https://apps.yunohost.org/app/gitlab", + "image": null, + "support_level": "community", + "source_url": "https://apps.yunohost.org/app/gitlab", + "note": "Community catalog package; publisher support is not implied.", + "sort_order": 50 + }, + { + "method": "umbrel", + "label": "Umbrel catalog", + "url": "https://github.com/getumbrel/umbrel-apps/tree/master/gitlab", + "image": null, + "support_level": "community", + "source_url": "https://github.com/getumbrel/umbrel-apps/tree/master/gitlab", + "note": "Community catalog package; publisher support is not implied.", + "sort_order": 70 + } + ], + "sources": [ + { + "source": "official_product_site", + "source_url": "https://about.gitlab.com/gitlab-community-edition/", + "confidence": "high", + "note": "Community Edition identity and FOSS mirror." + }, + { + "source": "official_docs", + "source_url": "https://docs.gitlab.com/install/requirements/", + "confidence": "high", + "note": "Hardware, PostgreSQL, and Redis requirements." + }, + { + "source": "official_docs", + "source_url": "https://docs.gitlab.com/install/docker/installation/", + "confidence": "high", + "note": "CE Docker image, Compose, and volumes." + }, + { + "source": "official_repository", + "source_url": "https://gitlab.com/gitlab-org/gitlab-foss/-/raw/master/LICENSE", + "confidence": "high", + "note": "MIT license for CE code." + } + ], + "data_source_urls": [], + "data_confidence": "high" + }, + { + "candidate_id": "0348f97a-3fb7-47b5-860b-c5df74dcf066", + "app_id": "dc968e17-070c-4a6b-bfc5-84ef68e5e40c", + "decision": "publish", + "app": { + "slug": "onedev", + "name": "OneDev", + "summary": "Git server with code search, pull requests, issue boards, CI/CD jobs, and a package registry in one app.", + "icon_url": "https://tulgfrqamtcshdtmhvpx.supabase.co/storage/v1/object/public/stack127-media/directories/self-host-apps/icons/onedev-6fb05a197448f52e.png", + "icon_source_url": null, + "website_url": "https://onedev.io", + "source_code_url": "https://github.com/theonedev/onedev", + "source_code_host": "github", + "docs_url": "https://docs.onedev.io/", + "install_docs_url": "https://docs.onedev.io/installation-guide/run-as-docker-container", + "docker_url": "https://hub.docker.com/r/1dev/server", + "license": "MIT", + "license_type": "permissive", + "license_spdx_id": "MIT", + "source_model": "open_source", + "publisher_name": "OneDev Pty", + "publisher_type": "company", + "publisher_url": "https://onedev.io", + "pricing_model": "freemium", + "published": true, + "self_hosting_status": "self_hostable", + "maintenance_status": "active", + "docker_support": "official", + "deployment_methods": [ + "docker", + "native", + "umbrel" + ], + "docker_image": "1dev/server", + "compose_support": null, + "compose_url": null, + "requires_database": true, + "database_types": [ + "mysql", + "mariadb", + "postgres" + ], + "ram_min_mb": null, + "ram_recommended_mb": 2048, + "storage_min_gb": null, + "resource_requirement_kind": "official_recommended", + "storage_profile": "depends", + "persistent_storage": true, + "surfaces": [ + "web" + ], + "categories": [ + "developer-tools", + "project-task-management" + ], + "summary_source": "curated", + "summary_confidence": "high", + "technical_facts_checked_at": "2026-11-01", + "data_checked_at": "2026-11-01", + "data_confidence": "high", + "data_source_urls": [ + "https://onedev.io", + "https://onedev.io/pricing", + "https://docs.onedev.io/", + "https://docs.onedev.io/installation-guide/run-as-docker-container", + "https://docs.onedev.io/installation-guide/run-on-bare-metal", + "https://hub.docker.com/r/1dev/server", + "https://github.com/theonedev/onedev", + "https://github.com/theonedev/onedev/blob/main/license.txt", + "https://code.onedev.io/onedev/server" + ], + "technical_fact_status": { + "license": { + "status": "published", + "note": "The repository license is MIT, copyright OneDev Pty.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://github.com/theonedev/onedev/blob/main/license.txt" + ] + }, + "source": { + "status": "published", + "note": "OneDev develops at code.onedev.io and mirrors to the official theonedev/onedev GitHub repository.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://github.com/theonedev/onedev", + "https://code.onedev.io/onedev/server" + ] + }, + "publisher": { + "status": "published", + "note": "OneDev Pty publishes OneDev and sells an Enterprise Edition; Community Edition is free.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://onedev.io/pricing" + ] + }, + "docker": { + "status": "published", + "note": "The install guide runs the official 1dev/server image.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.onedev.io/installation-guide/run-as-docker-container", + "https://hub.docker.com/r/1dev/server" + ] + }, + "docker_image": { + "status": "published", + "note": "The guide uses 1dev/server and mounts the host Docker socket for CI jobs.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.onedev.io/installation-guide/run-as-docker-container" + ] + }, + "compose": { + "status": "not_published", + "note": "The Docker guide shows docker run commands, not a Compose file.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.onedev.io/installation-guide/run-as-docker-container" + ] + }, + "database": { + "status": "published", + "note": "OneDev uses an embedded database by default and supports external MySQL, MariaDB, and PostgreSQL.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.onedev.io/installation-guide/run-as-docker-container" + ] + }, + "persistent_storage": { + "status": "published", + "note": "The Docker guide mounts OneDev data at /opt/onedev.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.onedev.io/installation-guide/run-as-docker-container" + ] + }, + "ram": { + "status": "published", + "note": "The Docker guide says OneDev runs happily on a 2 core, 2 GB box.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://docs.onedev.io/installation-guide/run-as-docker-container" + ] + }, + "storage_capacity": { + "status": "not_published", + "note": "No disk requirement is published; repositories, build artifacts, and packages drive growth.", + "checked_at": "2026-11-01", + "confidence": "medium", + "source_urls": [ + "https://docs.onedev.io/installation-guide/run-as-docker-container" + ] + } + } + }, + "install_options": [ + { + "method": "docker", + "label": "Docker image", + "url": "https://hub.docker.com/r/1dev/server", + "image": "1dev/server", + "support_level": "official", + "source_url": "https://docs.onedev.io/installation-guide/run-as-docker-container", + "note": null, + "sort_order": 10 + }, + { + "method": "native", + "label": "Bare metal install", + "url": "https://docs.onedev.io/installation-guide/run-on-bare-metal", + "image": null, + "support_level": "official", + "source_url": "https://docs.onedev.io/installation-guide/run-on-bare-metal", + "note": null, + "sort_order": 30 + }, + { + "method": "umbrel", + "label": "Umbrel catalog", + "url": "https://github.com/getumbrel/umbrel-apps/tree/master/onedev", + "image": null, + "support_level": "community", + "source_url": "https://github.com/getumbrel/umbrel-apps/tree/master/onedev", + "note": "Community catalog package; publisher support is not implied.", + "sort_order": 70 + } + ], + "sources": [ + { + "source": "official_product_site", + "source_url": "https://onedev.io", + "confidence": "high", + "note": "Product purpose." + }, + { + "source": "official_product_site", + "source_url": "https://onedev.io/pricing", + "confidence": "high", + "note": "Free Community Edition and paid Enterprise Edition; publisher OneDev Pty." + }, + { + "source": "official_docs", + "source_url": "https://docs.onedev.io/installation-guide/run-as-docker-container", + "confidence": "high", + "note": "Docker image, databases, data path, and 2 GB guidance." + }, + { + "source": "official_repository", + "source_url": "https://github.com/theonedev/onedev/blob/main/license.txt", + "confidence": "high", + "note": "MIT license." + } + ], + "data_source_urls": [], + "data_confidence": "high" + }, + { + "candidate_id": "60949483-8c14-43f3-8662-88d317ac4859", + "app_id": "2891e119-352e-4e56-8aa0-944ff95bc7f2", + "decision": "publish", + "app": { + "slug": "glitchtip", + "name": "GlitchTip", + "summary": "Error tracking that collects crash reports from Sentry SDKs, with uptime checks and performance monitoring.", + "icon_url": "https://tulgfrqamtcshdtmhvpx.supabase.co/storage/v1/object/public/stack127-media/directories/self-host-apps/icons/glitchtip-b8fee567f0ec3c56.png", + "icon_source_url": null, + "website_url": "https://glitchtip.com", + "source_code_url": "https://gitlab.com/glitchtip/glitchtip-backend", + "source_code_host": "gitlab", + "docs_url": "https://glitchtip.com/documentation", + "install_docs_url": "https://glitchtip.com/documentation/install", + "docker_url": "https://hub.docker.com/r/glitchtip/glitchtip", + "license": "MIT", + "license_type": "permissive", + "license_spdx_id": "MIT", + "source_model": "open_source", + "publisher_name": "Burke Software and Consulting", + "publisher_type": "company", + "publisher_url": "https://glitchtip.com", + "pricing_model": "free", + "published": true, + "self_hosting_status": "self_hostable", + "maintenance_status": "active", + "docker_support": "official", + "deployment_methods": [ + "docker", + "compose", + "helm" + ], + "docker_image": "glitchtip/glitchtip:6", + "compose_support": true, + "compose_url": "https://glitchtip.com/assets/compose.sample.yml", + "requires_database": true, + "database_types": [ + "postgres", + "valkey" + ], + "ram_min_mb": 256, + "ram_recommended_mb": 512, + "storage_min_gb": null, + "resource_requirement_kind": "official_minimum", + "storage_profile": "medium", + "persistent_storage": true, + "surfaces": [ + "web" + ], + "categories": [ + "monitoring-status", + "developer-tools" + ], + "summary_source": "curated", + "summary_confidence": "high", + "technical_facts_checked_at": "2026-11-01", + "data_checked_at": "2026-11-01", + "data_confidence": "high", + "data_source_urls": [ + "https://glitchtip.com", + "https://glitchtip.com/documentation", + "https://glitchtip.com/documentation/install", + "https://glitchtip.com/assets/compose.sample.yml", + "https://hub.docker.com/r/glitchtip/glitchtip", + "https://gitlab.com/glitchtip/glitchtip-backend", + "https://gitlab.com/glitchtip/glitchtip-backend/-/raw/master/LICENSE", + "https://gitlab.com/glitchtip/glitchtip" + ], + "technical_fact_status": { + "license": { + "status": "published", + "note": "The backend repository is MIT licensed.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://gitlab.com/glitchtip/glitchtip-backend/-/raw/master/LICENSE" + ] + }, + "source": { + "status": "published", + "note": "The Django backend lives in glitchtip/glitchtip-backend; glitchtip/glitchtip is the meta and deploy repo.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://gitlab.com/glitchtip/glitchtip-backend", + "https://gitlab.com/glitchtip/glitchtip" + ] + }, + "publisher": { + "status": "published", + "note": "The site footer names Burke Software and Consulting.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://glitchtip.com" + ] + }, + "docker": { + "status": "published", + "note": "The install guide runs the official GlitchTip image with Docker Compose.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://glitchtip.com/documentation/install", + "https://hub.docker.com/r/glitchtip/glitchtip" + ] + }, + "docker_image": { + "status": "published", + "note": "The sample Compose file uses glitchtip/glitchtip:6.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://glitchtip.com/assets/compose.sample.yml" + ] + }, + "compose": { + "status": "published", + "note": "The guide provides a sample compose.yml.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://glitchtip.com/documentation/install", + "https://glitchtip.com/assets/compose.sample.yml" + ] + }, + "database": { + "status": "published", + "note": "PostgreSQL 14+ is required; Valkey or Redis 7+ is optional and recommended in the sample.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://glitchtip.com/documentation/install", + "https://glitchtip.com/assets/compose.sample.yml" + ] + }, + "persistent_storage": { + "status": "published", + "note": "The sample Compose file keeps PostgreSQL data and uploads in volumes.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://glitchtip.com/assets/compose.sample.yml" + ] + }, + "ram": { + "status": "published", + "note": "The guide recommends 512 MB RAM and lists 256 MB as the all-in-one minimum.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://glitchtip.com/documentation/install" + ] + }, + "storage_capacity": { + "status": "published", + "note": "The guide cites about 30 GB for roughly 1 million events per month; use varies.", + "checked_at": "2026-11-01", + "confidence": "high", + "source_urls": [ + "https://glitchtip.com/documentation/install" + ] + } + } + }, + "install_options": [ + { + "method": "docker", + "label": "Docker image", + "url": "https://hub.docker.com/r/glitchtip/glitchtip", + "image": "glitchtip/glitchtip:6", + "support_level": "official", + "source_url": "https://glitchtip.com/documentation/install", + "note": null, + "sort_order": 10 + }, + { + "method": "compose", + "label": "Docker Compose", + "url": "https://glitchtip.com/documentation/install", + "image": null, + "support_level": "official", + "source_url": "https://glitchtip.com/assets/compose.sample.yml", + "note": "Official sample Compose file.", + "sort_order": 20 + }, + { + "method": "helm", + "label": "Helm chart", + "url": "https://glitchtip.com/documentation/install#helm", + "image": null, + "support_level": "official", + "source_url": "https://glitchtip.com/documentation/install", + "note": null, + "sort_order": 28 + } + ], + "sources": [ + { + "source": "official_product_site", + "source_url": "https://glitchtip.com", + "confidence": "high", + "note": "Product purpose and publisher." + }, + { + "source": "official_docs", + "source_url": "https://glitchtip.com/documentation/install", + "confidence": "high", + "note": "Install methods, PostgreSQL and Valkey, RAM and disk guidance." + }, + { + "source": "official_docs", + "source_url": "https://glitchtip.com/assets/compose.sample.yml", + "confidence": "high", + "note": "Official image and Compose services." + }, + { + "source": "official_repository", + "source_url": "https://gitlab.com/glitchtip/glitchtip-backend/-/raw/master/LICENSE", + "confidence": "high", + "note": "MIT license." + } + ], + "data_source_urls": [], + "data_confidence": "high" + }, + { + "candidate_id": "2dbc7493-6fd1-4743-8f29-7c8d68430ffc", + "decision": "duplicate", + "app_id": "2481c783-96b7-4537-a2b0-0ebc5a897465" + }, + { + "candidate_id": "5ecdaa5a-cb3a-428c-9d31-9dbe4ef3087a", + "decision": "duplicate", + "app_id": "cfd4f72a-713d-4918-bf1b-2eb5ae529f0e" + }, + { + "candidate_id": "a8309237-ae19-4474-8648-7d12104198c0", + "decision": "duplicate", + "app_id": "cfd4f72a-713d-4918-bf1b-2eb5ae529f0e" + }, + { + "candidate_id": "f9e3f5b4-1f8f-40eb-a594-9a33914793b0", + "decision": "duplicate", + "app_id": "cfd4f72a-713d-4918-bf1b-2eb5ae529f0e" + }, + { + "candidate_id": "2627a441-081f-46ba-975a-bfb49d886838", + "decision": "duplicate", + "app_id": "2891e119-352e-4e56-8aa0-944ff95bc7f2" + }, + { + "candidate_id": "2dd8ddee-8388-4c4a-a714-a558a27d42f0", + "decision": "duplicate", + "app_id": "dc968e17-070c-4a6b-bfc5-84ef68e5e40c" + } + ] +} diff --git a/scripts/stack127/frontend/cloudflare.mjs b/scripts/stack127/frontend/cloudflare.mjs index ebdfcb49..0920e530 100644 --- a/scripts/stack127/frontend/cloudflare.mjs +++ b/scripts/stack127/frontend/cloudflare.mjs @@ -1,3 +1,5 @@ +import { queryNoindexExpression } from "../../../lib/seo/query-indexing.mjs"; + export async function cloudflare(path, { method = "GET", body, environment = process.env, fetchImplementation = fetch } = {}) { if (!environment.CLOUDFLARE_API_TOKEN) throw new Error("Cloudflare API token is required."); const response = await fetchImplementation(`https://api.cloudflare.com/client/v4${path}`, { @@ -34,7 +36,8 @@ export async function activateFrontend({ environment = process.env, api = cloudf const headers = sets.find(set => set.phase === "http_response_headers_transform" && set.kind === "zone"); const definition = { action: "rewrite", description: "Stack127 query pages: noindex", - expression: '(http.host eq "stack127.com" and http.request.uri.query ne "" and not starts_with(http.request.uri.path, "/api/") and not starts_with(http.request.uri.path, "/_next/") and not starts_with(http.request.uri.path, "/vitals/"))', + expression: queryNoindexExpression(), + enabled: true, action_parameters: { headers: { "X-Robots-Tag": { operation: "set", value: "noindex, follow" } } }, }; if (headers) { diff --git a/stack127_docs/caching.md b/stack127_docs/caching.md index 7fb39f1a..cd98ef16 100644 --- a/stack127_docs/caching.md +++ b/stack127_docs/caching.md @@ -19,7 +19,7 @@ Shared links prefetch a page only when a reader shows intent, such as hovering o ## Indexing -- A URL with a query string (`?…`) is never indexed. This includes search, filters, sort, and `?page=`. The proxy sends `X-Robots-Tag: noindex, follow` for these, and the canonical link points to the clean URL. On the static frontend, a Cloudflare response header rule sends the same header. +- A URL with a query string (`?…`) is never indexed. This includes search, filters, sort, tool state, and `?page=`. The proxy sends `X-Robots-Tag: noindex, follow` for these, and the canonical link points to the clean URL. On the static frontend, a Cloudflare response header rule sends the same header. The one exception is a query made only of tracking parameters, such as `utm_*`, `gclid`, or `ref`. It gets no noindex and relies on the canonical. `lib/seo/query-indexing.mjs` holds the list. - A page we want indexed gets its own path, such as `/platform-fees/creators-and-communities` or later `/self-host-apps/categories/`. Its registry row must have both `is_published` and `is_indexable` set to `true`. - A published row with `is_indexable = false` stays public, sends noindex metadata, omits JSON-LD, and stays out of the sitemap. Use this for thin child pages and pages still being improved. - See [seo.md](seo.md) for the registry and sitemap rules. diff --git a/stack127_docs/cloudflare.md b/stack127_docs/cloudflare.md index 009c4a90..de4d389e 100644 --- a/stack127_docs/cloudflare.md +++ b/stack127_docs/cloudflare.md @@ -113,7 +113,7 @@ Browser revalidation also applies to successful documents with cookies and to 30 New client navigations reuse a page for at most 30 seconds instead of five minutes. Browser Back and Forward keep their normal history behavior. -Query pages keep their canonical base URL and `noindex, follow`. `_rsc` on its own is navigation data. It doesn't make a page a filtered search. Navigation cache keys keep the full query string. +Query pages keep their canonical base URL. Tracking-only queries rely on that canonical; queries containing non-tracking names get `noindex, follow`. `_rsc` on its own is navigation data. It doesn't make a page a filtered search. Navigation cache keys keep the full query string. The domain registry (RDAP) cache stays at 24 hours for registered domains and 1 hour for unregistered domains. @@ -340,7 +340,36 @@ Before upload, `assets.mjs` checks the Cloudflare Free limits: at most 20,000 fi | `/404` | `no-store` and `noindex` | | `/frontend-release.json` | `noindex` | -`proxy.ts` doesn't run on static hosting. A free zone response header transform rule, `Stack127 query pages: noindex`, sets `X-Robots-Tag: noindex, follow` on any query URL outside `/api/`, `/_next/`, and `/vitals/`. Canonicals, robots meta tags, and JSON-LD still come from the registry, inside the exported HTML. Indexable pages send no robots meta tag, so a query URL, which gets the same file, carries only the `noindex` header. +`proxy.ts` doesn't run on static hosting. The free zone response header transform rule, `Stack127 query pages: noindex`, sets `X-Robots-Tag: noindex, follow` only when a query contains a non-tracking name. It excludes `/api/`, `/_next/`, and `/vitals/`. Tracking-only links rely on the clean HTML canonical. Canonicals, robots meta tags, and JSON-LD still come from the registry in the exported HTML. A tracking query never overrides registry noindex metadata. + +`lib/seo/query-indexing.mjs` supplies the origin classifier and the deploy expression. It accepts the `utm_` prefix and the explicit tracking names listed in [seo.md](seo.md#query-parameter-policy). Unknown names, state, filters, search, pagination, and tracking mixed with any of those still get noindex. + +The generated expression is: + +```text +(http.host eq "stack127.com" and http.request.uri.query ne "" and (len(http.request.uri.args.names) eq 0 or any(not starts_with(http.request.uri.args.names[*], "utm_") and not http.request.uri.args.names[*] in {"gclid" "dclid" "gbraid" "wbraid" "fbclid" "msclkid" "ref" "mc_cid" "mc_eid"})) and not starts_with(http.request.uri.path, "/api/") and not starts_with(http.request.uri.path, "/_next/") and not starts_with(http.request.uri.path, "/vitals/")) +``` + +This is a zone `rewrite` rule in `http_response_headers_transform`, with a static header value. It uses no regular expressions or paid regex features. The two tests inside `any()` apply to the same argument name, so a mix such as `utm_source=x&gclid=123` is exempt, while `utm_source=x&q=test` is not. A non-empty query with no argument names is kept noindex. Names preserve case and encoding at both the edge and origin. + +Cloudflare documents [URI argument names](https://developers.cloudflare.com/ruleset-engine/rules-language/fields/reference/http.request.uri.args.names/), [array expressions](https://developers.cloudflare.com/ruleset-engine/rules-language/values/), [functions](https://developers.cloudflare.com/ruleset-engine/rules-language/functions/), and [response transform fields](https://developers.cloudflare.com/rules/transform/response-header-modification/reference/fields-functions/). The exact 452-character expression passed `GET /client/v4/filters/validate-expr?expression=…` on 1 November 2026 (HTTP 200, `success: true`, no errors). That read-only check validates syntax; it does not install the rule or test live response headers. The release must still verify those headers. + +`deploy.mjs --activate` finds the existing zone response-header ruleset and patches the rule with this description by its existing ID, enabling it. It creates the rule only if missing. Other rules remain untouched. The rule is installed before frontend routes change, and repeated activation updates the same rule. The normal production release passes `--activate`; a content-only refresh without that flag does not update edge rules. + +After release, run: + +```bash +curl -sS -D - -o /dev/null 'https://stack127.com/dockerignore-tester?utm_source=x' +curl -sS -D - -o /dev/null 'https://stack127.com/dockerignore-tester?utm_source=x&gclid=123&ref=newsletter' +curl -sS -D - -o /dev/null 'https://stack127.com/dockerignore-tester?state=test' +curl -sS -D - -o /dev/null 'https://stack127.com/dockerignore-tester?utm_source=x&state=test' +curl -sS -D - -o /dev/null 'https://stack127.com/self-host-apps?q=backup' +curl -sS -D - -o /dev/null 'https://stack127.com/self-host-apps?category=storage' +curl -sS -D - -o /dev/null 'https://stack127.com/self-host-apps?page=2' +curl -sS -D - -o /dev/null 'https://stack127.com/dockerignore-tester?unknown=x' +``` + +All eight should return 200. The first two should have no `X-Robots-Tag` header on this indexable page. The other six must send `X-Robots-Tag: noindex, follow`. Check the HTML canonical on all four Dockerignore URLs: it must remain `https://stack127.com/dockerignore-tester`. After an origin rollback, repeat these checks; origin responses also retain their clean canonical `Link` header. Static files are public whatever cookies the request carries, so analytics cookies don't lower the hit rate. The JSON routes on OVH keep their cookie bypass. To cache JSON requests that carry analytics cookies, add a strict allowlist of those cookie names. Don't remove the cookie checks. @@ -400,7 +429,7 @@ The repository is private, so its GitHub builds consume the account's included m 1. Build the static export from the merge commit and check it on the preview service, before the backend changes. 2. Deploy the backend image and confirm its health SHA. -3. Deploy the static frontend. The first release also writes the routes and the header rule. +3. Deploy the static frontend. Every release with `--activate` updates the routes and the existing header rule in place. 4. Check that `/`, `/robots.txt`, `/sitemap.xml`, and `/frontend-release.json` send the release SHA, and that a missing path returns 404. The release is complete only when: @@ -417,7 +446,7 @@ To send pages back to OVH: 2. Set both GitHub variable scopes and the Supabase hosting secret back to legacy. 3. Check `/api/health?scope=deploy`, the homepage, and a recently edited page. -The API exclusion routes and the header rule can stay. They match what `proxy.ts` does. +The API exclusion routes and the header rule stay. The header rule and the updated `proxy.ts` use the same query policy. Deploy this updated origin before relying on tracking-only behavior after rollback: an older origin image can still add the former blanket noindex header. Rolling back Worker assets does not roll back the zone header rule. Cloudflare can also roll `stack127-frontend` back to an earlier version. That version serves exactly its own files, including its own `_next/static` scripts. A tab opened on the newer version can then ask for scripts that no longer exist, and client navigation can fail until a full reload. Test navigation in an open tab before you rely on this kind of rollback. diff --git a/stack127_docs/seo.md b/stack127_docs/seo.md index 11677dba..c3f0d5d4 100644 --- a/stack127_docs/seo.md +++ b/stack127_docs/seo.md @@ -49,12 +49,27 @@ Follow these route rules: | Exact published slug with `is_indexable = true` | Render, no robots meta tag (indexing is the default), sitemap included, JSON-LD included | | Exact published slug with `is_indexable = false` | Render, `noindex, follow`, sitemap excluded, no JSON-LD, canonical points to itself | | Exact unpublished slug, whatever `is_indexable` says | `404`, sitemap excluded | -| Exact published slug with query params, for example `/self-host-apps?page=2` | Render the same static page, `noindex, follow` through the proxy header (or the Cloudflare header rule on the static frontend), sitemap excluded | +| Exact published slug with only tracking params, for example `/dockerignore-tester?utm_source=x` | Use the clean canonical; no query-based `noindex` header. The registry still controls whether the page is indexable | +| Exact published slug with other query params, for example `/self-host-apps?page=2` | Render the same static page, `noindex, follow` through the proxy header (or the Cloudflare header rule on the static frontend), query variant excluded from the sitemap | | Child route under a published root, for example `/self-host-apps/page/2` | Render if code exists, `noindex, follow`, sitemap excluded | | Exact published child slug in `stack127_pages` | Render; its `is_indexable` value controls indexing and sitemap inclusion | | Route outside a published root, for example `/random-test-page` | `404`, even if code exists | -A URL with a query string is never indexed. Search, filters, sort, and `?page=` stay out of the index. To index a category or other view, give it its own path, such as `/self-host-apps/categories/`. Publish that exact slug. See [caching.md](caching.md) for why pages never read the query on the server. +Tracking-only URLs rely on their clean canonical, following [Google’s duplicate URL guidance](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls). They get no query-based `noindex` header. Search, filters, sort, pagination, tool state, unknown parameters, and any mix of those with tracking still get `noindex, follow`. Only clean published URLs enter the sitemap. To index a category or other view, give it its own path, such as `/self-host-apps/categories/`. Publish that exact slug. See [caching.md](caching.md) for why pages never read the query on the server. + +### Query parameter policy + +The shared policy is in `lib/seo/query-indexing.mjs`. Both `proxy.ts` and the frontend deploy use it. + +| Kind | Parameters found in app and shared-link code | Query header | +| --- | --- | --- | +| Tracking only | App code generates no tracking links. Accept incoming `utm_*`, `gclid`, `dclid`, `gbraid`, `wbraid`, `fbclid`, `msclkid`, `ref`, `mc_cid`, and `mc_eid` | No query-based `noindex`; use the clean canonical | +| Tool state | `state` for storage and configuration tools; `name` and `type` for DNS tools; `site` for the ad-network checker; `query` for IP lookup; `apps` for shortcut sheets; calculator-specific inputs and ad-network inputs such as `niche`, `pv`, `visits`, `rev` | `noindex, follow` | +| Search and filters | `q`, `category`, `sort`, `license`, `source`, `docker`, `install`, `ram`, `storage`, `maintenance`, `signal`; launch-directory comparison uses `d` | `noindex, follow` | +| Pagination | `page`, and ad-network table `pageSize` | `noindex, follow` | +| Other names and mixed queries | Any unknown name, empty name, or tracking mixed with the above; `_rsc` keeps its existing noindex behavior for navigation data | `noindex, follow` | + +Names are case-sensitive and are checked without decoding, matching Cloudflare’s argument-name field. `UTM_SOURCE`, `%75tm_source`, and `gcl%69d` therefore remain noindex. Empty values and repeated tracking names are allowed. Values do not affect classification. Do not add generic names such as `source` or `query` to the tracking list: they can change the page state. This exception never removes registry noindex metadata or the static asset/404 rules. Use `is_indexable = false` for thin child pages, filtered views with their own paths, and useful public pages still being improved. This setting does not hide them from sidebars, catalogs, or homepage lists. Set it to `true` when the page is ready for search. @@ -63,8 +78,8 @@ Use `is_indexable = false` for thin child pages, filtered views with their own p Cloudflare hosting mode uses the SEO behavior below. See [cloudflare.md](cloudflare.md#static-frontend-on-cloudflare) for its activation and release checks. - The export holds all published pages, including noindex pages. Anything unpublished returns the static 404 with `noindex`. -- Titles, descriptions, canonicals, robots meta tags, and JSON-LD come from the registry, written into the exported HTML at build time. Indexable pages have no robots meta tag. A query URL shares its page's HTML file, so an `index` tag would contradict the `noindex` header it gets. -- `proxy.ts` doesn't run. A free Cloudflare response header transform rule sends `X-Robots-Tag: noindex, follow` on query URLs. It doesn't add the proxy's canonical `Link` header. The HTML canonical covers that. +- Titles, descriptions, canonicals, robots meta tags, and JSON-LD come from the registry, written into the exported HTML at build time. Indexable pages have no robots meta tag. A query URL shares its page's HTML file; state queries receive a separate `noindex` header. +- `proxy.ts` doesn't run. A free Cloudflare response header transform rule sends `X-Robots-Tag: noindex, follow` on queries containing any non-tracking name. It doesn't add the proxy's canonical `Link` header. The HTML canonical covers that. - RSC `.txt` files, `/frontend-release.json`, and the `workers.dev` preview send `noindex`. - A registry change reaches the public HTML only after the frontend workflow rebuilds and deploys the export. @@ -94,7 +109,7 @@ A parent layout with child routes uses `registryLayout(path, { structuredData: " `robots.txt` allows normal crawling and asks crawlers to skip `/api/`. It points crawlers to `/sitemap.xml`. The API rule guides crawlers; it does not control access. -Do not block useful pages in `robots.txt` just to handle duplicates. Prefer `noindex, follow` on duplicate URL states. Crawlers can still follow their links. +Do not block useful pages in `robots.txt` just to handle duplicates. Use clean canonicals for tracking duplicates and `noindex, follow` for search, filters, pagination, and shared tool states. Crawlers can still follow their links. ## Creating a new public page @@ -102,5 +117,5 @@ Do not block useful pages in `robots.txt` just to handle duplicates. Prefer `noi 2. Keep page code free of metadata, robots, canonical, sitemap, and JSON-LD exports. 3. Add or update the page docs under `content_docs//`. 4. When the page should become public, insert a reviewed row into `public.stack127_pages` with the correct `page_family`. -5. Check the indexing setting. Indexable pages send no robots meta tag and enter `/sitemap.xml`. Noindex pages render with their own canonical, send `noindex, follow`, omit JSON-LD, and stay out of the sitemap. Child/query variants stay `noindex, follow`. +5. Check the indexing setting. Indexable pages send no robots meta tag and enter `/sitemap.xml`. Noindex pages render with their own canonical, send `noindex, follow`, omit JSON-LD, and stay out of the sitemap. Unregistered child routes and non-tracking query variants stay `noindex, follow`. 6. Follow [caching.md](caching.md). The page must be static and use on-demand revalidation. Its data source must be covered by the revalidation pipeline. diff --git a/tests/stack127/analytics.test.mjs b/tests/stack127/analytics.test.mjs index 78673bf2..257755a1 100644 --- a/tests/stack127/analytics.test.mjs +++ b/tests/stack127/analytics.test.mjs @@ -3,6 +3,7 @@ import { readFileSync } from "node:fs"; import test from "node:test"; import vm from "node:vm"; import ts from "typescript"; +import * as queryIndexing from "../../lib/seo/query-indexing.mjs"; function publicationProxy({ published = false } = {}) { const lookups = []; @@ -29,6 +30,7 @@ function publicationProxy({ published = false } = {}) { if (name === "@/lib/tool-pages") return { getPublishedToolPage: async () => null }; if (name === "@/lib/page-source") return { sitePageAvailable: () => published }; if (name === "@/lib/seo") return { absoluteUrl: pathname => new URL(pathname, "https://stack127.com").toString() }; + if (name === "@/lib/seo/query-indexing.mjs") return queryIndexing; throw new Error(`Unexpected import: ${name}`); }, }); @@ -84,11 +86,16 @@ test("query responses send noindex and a clean canonical on every proxy response for (const published of [false, true]) { const { proxy } = publicationProxy({ published }); for (const pathname of ["/about", "/missing-page", "/vitals/api/send", "/api/health", "/sitemap.xml", "/robots.txt", "/publication-not-found"]) { - for (const query of ["?q=word&page=2", "?q=", "?_rsc=internal"]) { + for (const query of ["?q=word&page=2", "?q=", "?_rsc=internal", "?utm_source=email&q=word", "?state=shared&ref=newsletter"]) { const response = await proxy(request(pathname + query)); assert.equal(response.headers.get("X-Robots-Tag"), "noindex, follow", pathname + query); assert.equal(response.headers.get("Link"), `; rel="canonical"`, pathname + query); } + for (const query of ["?utm_source=email", "?utm_source=email&gclid=123&ref=newsletter"]) { + const response = await proxy(request(pathname + query)); + assert.equal(response.headers.get("X-Robots-Tag"), null, pathname + query); + assert.equal(response.headers.get("Link"), `; rel="canonical"`, pathname + query); + } const clean = await proxy(request(pathname)); assert.equal(clean.headers.get("X-Robots-Tag"), null, pathname); assert.equal(clean.headers.get("Link"), null, pathname); diff --git a/tests/stack127/query-indexing.test.mjs b/tests/stack127/query-indexing.test.mjs new file mode 100644 index 00000000..cefe9ffb --- /dev/null +++ b/tests/stack127/query-indexing.test.mjs @@ -0,0 +1,64 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { queryNeedsNoindex, trackingQueryNames, queryNoindexExpression } from "../../lib/seo/query-indexing.mjs"; +import { activateFrontend, deactivateFrontend } from "../../scripts/stack127/frontend/cloudflare.mjs"; + +test("clean URLs and tracking-only combinations rely on the clean canonical", () => { + for (const search of ["", "?", "?utm_source=x", "?utm_custom=x&utm_medium=email", "?utm_source=x&gclid=123&ref=newsletter", "?utm_source=x&utm_source=y", "?utm_source=", "?ref", ...trackingQueryNames.map(name => `?${name}=123`)]) { + assert.equal(queryNeedsNoindex(search), false, search); + } +}); + +test("tool state, search, filters, pagination and unknown names remain noindex, including tracking mixes", () => { + for (const name of ["state", "name", "type", "site", "query", "niche", "pv", "apps", "d", "q", "category", "sort", "page", "pageSize", "unknown", "_rsc", "UTM_SOURCE", "%75tm_source", "gcl%69d", "", "utm"]) { + for (const search of [`?${name}=x`, `?utm_source=email&${name}=x`, `?${name}=x&gclid=123`]) { + assert.equal(queryNeedsNoindex(search), true, search); + } + } + assert.equal(queryNeedsNoindex("?utm_source=x&q="), true); + assert.equal(queryNeedsNoindex("?utm_source=x%26q%3Dy"), false); + assert.equal(queryNeedsNoindex("?&&"), true); +}); + +test("activation patches the existing query rule on every run without replacing unrelated rules or routes", async () => { + const writes = []; + const rules = [{ id: "other", description: "Unrelated" }, { id: "query", description: "Stack127 query pages: noindex", enabled: false }]; + const routes = [ + { id: "api", pattern: "stack127.com/api/*", script: null }, + { id: "vitals", pattern: "stack127.com/vitals/*", script: null }, + { id: "frontend", pattern: "stack127.com/*", script: "stack127-frontend" }, + ]; + const api = async (url, options = {}) => { + if (options.method) { writes.push({ url, ...options }); return {}; } + if (url.endsWith("/workers/routes")) return routes; + if (url.endsWith("/rulesets")) return [{ id: "headers", phase: "http_response_headers_transform", kind: "zone" }]; + if (url.endsWith("/rulesets/headers")) return { rules }; + throw new Error(`Unexpected API call: ${url}`); + }; + const settings = { environment: { CLOUDFLARE_ZONE_ID: "zone" }, api }; + await activateFrontend(settings); + await activateFrontend(settings); + assert.equal(writes.length, 2); + for (const write of writes) { + assert.equal(write.method, "PATCH"); + assert.equal(write.url, "/zones/zone/rulesets/headers/rules/query"); + assert.equal(write.body.expression, queryNoindexExpression()); + assert.equal(write.body.enabled, true); + assert.equal(write.body.action_parameters.headers["X-Robots-Tag"].value, "noindex, follow"); + } + writes.length = 0; + await deactivateFrontend(settings); + assert.deepEqual(writes.map(({ url, method }) => ({ url, method })), [{ url: "/zones/zone/workers/routes/frontend", method: "DELETE" }]); +}); + +test("activation adds a missing query rule to an existing header ruleset", async () => { + let installed; + await activateFrontend({ environment: { CLOUDFLARE_ZONE_ID: "zone" }, api: async (url, options = {}) => { + if (options.method) { if (url.endsWith("/rules")) installed = { url, ...options }; return {}; } + if (url.endsWith("/workers/routes")) return []; + if (url.endsWith("/rulesets")) return [{ id: "headers", phase: "http_response_headers_transform", kind: "zone" }]; + return { rules: [{ id: "other", description: "Unrelated" }] }; + } }); + assert.equal(installed.method, "POST"); + assert.equal(installed.body.expression, queryNoindexExpression()); +});