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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@
"id": "jump-to-kickoff",
"type": "eventJump",
"title": "Fly across the board",
"instruction": "Press F to reveal jump letters, then type the one on Team kickoff.",
"hint": "Press F and letters appear on every block. Type the letter sitting on Team kickoff.",
"instruction": "Press f to reveal jump labels, then type the one on Team kickoff.",
"hint": "Press f and labels appear on every block. Type the lowercase label on Team kickoff.",
"keycaps": [
"F"
"f"
],
"targetEventId": "game-kickoff"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,7 @@
"eventJump": {
"bareLetter": "f",
"keycaps": [
"F"
"f"
]
},
"moveEvent": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ final class WeekDayColumnHeaderControl: NSView {

if showDayJumpPrefixes {
let weekday = EffectiveTimeZone.calendar.component(.weekday, from: date)
let prefix = DayJumpPrefixes.prefix(forWeekday: weekday - 1).uppercased()
let prefix = DayJumpPrefixes.prefix(forWeekday: weekday - 1)
chipLabel.stringValue = dayJumpUseShiftPrefix ? "⇧\(prefix)" : prefix
chipLabel.isHidden = false
chipLabel.textColor = palette.text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import CompassKit
import XCTest
@testable import CompassUI

/// Web `DayLabels`: while Mod is held the weekday name becomes a ⇧SU / ⇧M
/// keycap, and event-jump mode shows the bare letter.
/// Web `DayLabels`: while Mod is held the weekday name becomes a ⇧su / ⇧m
/// keycap, and event-jump mode shows the bare lowercase prefix.
@MainActor
final class TimeGridDayJumpPrefixTests: XCTestCase {
private let dayKeys = ["2026-05-17", "2026-05-18"]
Expand All @@ -16,14 +16,14 @@ final class TimeGridDayJumpPrefixTests: XCTestCase {

func testWeekHeadersShowShiftPrefixedChipsWhileModIsHeld() {
let view = render(showDayJumpPrefixes: true)
XCTAssertTrue(headerTexts(in: view, dayKey: "2026-05-17").contains("⇧SU"))
XCTAssertTrue(headerTexts(in: view, dayKey: "2026-05-18").contains("⇧M"))
XCTAssertTrue(headerTexts(in: view, dayKey: "2026-05-17").contains("⇧su"))
XCTAssertTrue(headerTexts(in: view, dayKey: "2026-05-18").contains("⇧m"))
XCTAssertFalse(headerTexts(in: view, dayKey: "2026-05-17").contains("Sun"))
}

func testEventJumpModeShowsBarePrefixes() {
let view = render(showDayJumpPrefixes: true, dayJumpUseShiftPrefix: false)
XCTAssertTrue(headerTexts(in: view, dayKey: "2026-05-17").contains("SU"))
XCTAssertTrue(headerTexts(in: view, dayKey: "2026-05-17").contains("su"))
}

private func render(showDayJumpPrefixes: Bool, dayJumpUseShiftPrefix: Bool = true) -> TimeGridView {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const PracticeBlock: FC<{
}`}
data-practice-jump-letter={jumpLetter}
>
{jumpLetter.toUpperCase()}
{jumpLetter}
</span>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,13 @@ describe("ShortcutShowcase", () => {
pressKey("Enter");

// Esc-skip to the jump task. With every board task skipped, letters run
// over the three seed events alone, so kickoff's chip is A here.
// over the three seed events alone, so kickoff's chip is a here.
for (let i = 0; i < 7; i += 1) pressKey("Escape");
expect(screen.getByText("Fly across the board")).toBeTruthy();
expect(screen.getByText("A")).toBeTruthy();
expect(screen.getByText("a")).toBeTruthy();
pressKey("f");
// The chips on the blocks join the one already on the card.
expect(screen.getAllByText("A").length).toBe(2);
expect(screen.getAllByText("a").length).toBe(2);

// Skip past the jump and page-jump tasks to reach the palette task:
// its Mod+K hint becomes Esc once the palette opens.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ describe("keycap progress", () => {
>;
expect(task.id).toBe("jump-to-kickoff");
// The full sequence is on the card up front: H, then kickoff's letter.
expect(getDisplayKeycaps(task, state)).toEqual(["F", "S"]);
expect(getDisplayKeycaps(task, state)).toEqual(["f", "s"]);
expect(getNextKeycapIndex(task, state)).toBe(0);

state = handleGameKey(state, key.jump, T0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ export const getDisplayKeycaps = (
): readonly string[] => {
if (task.type === "eventJump") {
const letter = getJumpLetters(state.practice)[task.targetEventId];
return letter ? [...task.keycaps, letter.toUpperCase()] : task.keycaps;
return letter ? [...task.keycaps, letter] : task.keycaps;
}
if (
(task.type === "palette" && state.simOverlay === "palette") ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ export const RUN_TASKS: readonly GameTask[] = [
type: "eventJump",
title: "Fly across the board",
instruction:
"Press F to reveal jump letters, then type the one on Team kickoff.",
hint: "Press F and letters appear on every block. Type the letter sitting on Team kickoff.",
"Press f to reveal jump labels, then type the one on Team kickoff.",
hint: "Press f and labels appear on every block. Type the lowercase label on Team kickoff.",
keycaps: KEYMAP.eventJump.keycaps,
targetEventId: "game-kickoff",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe("ShiftHintOverlay", () => {

render(<ShiftHintOverlay hints={[hintFor(event)]} />);

expect(overlayRoot()?.textContent).toBe("M1");
expect(overlayRoot()?.textContent).toBe("m1");
expect(overlayChip()?.style.top).toBe("224px");
expect(overlayChip()?.style.left).toBe("214px");
});
Expand Down Expand Up @@ -108,7 +108,7 @@ describe("ShiftHintOverlay", () => {

render(<ShiftHintOverlay hints={[hintFor(event, "su1")]} />);

expect(overlayChip()?.textContent).toBe("SU1");
expect(overlayChip()?.textContent).toBe("su1");
expect(overlayChip()?.style.top).toBe("204px");
expect(overlayChip()?.style.left).toBe("206px");
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export function ShiftHintOverlay({ hints }: { hints: ActiveShiftHint[] }) {
return null;
}

const label = hint.hint.toUpperCase();
// Longer labels (SU1, F10) need more room than the old AA chips.
const label = hint.hint;
// Longer labels (su1, f10) need more room than the old AA chips.
const chipWidth = Math.max(22, 10 + label.length * 8);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Day-prefix event jump labels.
*
* SU/M/T/W/R/F/SA + per-day chronological index (W4, SU1, F10), in both week
* su/m/t/w/r/f/sa + per-day chronological index (w4, su1, f10), in both week
* and day view. Day view groups on the single date it shows, so its labels are
* that weekday's prefix (W1, W2, …). One scheme for both views is what keeps a
* bare digit free everywhere for quick-time create (shortcuts/quick-time).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ describe("typed-time deferred commit", () => {
);
});

it("still toggles jump off with f after a half-typed column time", () => {
it("exits jump with Esc after a half-typed column time", () => {
const { createAt, type } = mountColumn();

act(() => {
Expand All @@ -443,7 +443,7 @@ describe("typed-time deferred commit", () => {
}),
);
type(["9"]);
dispatch(keydown("f"));
dispatch(keydown("Escape"));
});

expect(createAt).not.toHaveBeenCalled();
Expand Down Expand Up @@ -519,6 +519,40 @@ describe("typed-time deferred commit", () => {
fridayElements.splice(0, fridayElements.length);
});

it("opens the fourth Friday event on bare f4 after flash jump, with no draft", async () => {
const { createAt, openEvent } = mountFriday();
const fourthId = fridayIds[3]!;
const fourthEl = fridayElements[3]!;

act(() => {
dispatch(keydown("f"));
});
expect(useEventJumpStore.getState().isActive).toBe(true);
dispatch(keydown("f"));
dispatch(digit("4"));

await act(
() =>
new Promise<void>((resolve) => {
setTimeout(resolve, DIGIT_AMBIGUOUS_COMMIT_MS * 2 + 50);
}),
);

expect(document.activeElement).toBe(fourthEl);
expect(openEvent).not.toHaveBeenCalled();
expect(createAt).not.toHaveBeenCalled();

const enter = new KeyboardEvent("keydown", {
bubbles: true,
cancelable: true,
key: "Enter",
});
act(() => {
fourthEl.dispatchEvent(enter);
});
expect(openEvent).toHaveBeenCalledWith(fourthId);
});

it("opens the fourth Friday event on f then 4 then Enter, with no draft", () => {
const { createAt, openEvent, pressFriday } = mountFriday();
const fourthId = fridayIds[3]!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ describe("useShiftHoldEventHints", () => {

expect(useEventJumpStore.getState()).toMatchObject({
isActive: true,
pointerHintKey: "W2",
pointerHintKey: "w2",
pointerHintEventId: EVENT_B,
});
expect(focus).toHaveBeenCalledWith(
Expand Down Expand Up @@ -571,7 +571,7 @@ describe("useShiftHoldEventHints", () => {
act(() => {
requestPointerEventJump(clicked);
});
expect(useEventJumpStore.getState().pointerHintKey).toBe("W2");
expect(useEventJumpStore.getState().pointerHintKey).toBe("w2");
focus.mockClear();

act(() => {
Expand Down Expand Up @@ -683,7 +683,7 @@ describe("useShiftHoldEventHints", () => {
act(() => {
requestPointerEventJump(EVENT_B);
});
expect(useEventJumpStore.getState().pointerHintKey).toBe("W2");
expect(useEventJumpStore.getState().pointerHintKey).toBe("w2");

act(() => {
rerender({
Expand All @@ -694,7 +694,7 @@ describe("useShiftHoldEventHints", () => {
});
});

expect(useEventJumpStore.getState().pointerHintKey).toBe("W3");
expect(useEventJumpStore.getState().pointerHintKey).toBe("w3");
});

it("publishes every visible column as a day key can select empty ones", () => {
Expand Down Expand Up @@ -783,20 +783,25 @@ describe("useShiftHoldEventHints", () => {
expect(result.current.hints).toEqual([]);
});

it("toggles off with a second f", () => {
const { result } = mountHints();
it("selects Friday with bare f while jump mode is on", () => {
const friday = WEEK_DAYS[5]!;
const fridayId = EventIdSchema.parse("ffffffffffffffffffffffff");
const { focus, elements } = mountHints([
timedFixture(fridayId, friday.hour(10).format()),
]);

act(() => {
pressEventJump();
dispatch("keydown", "f");
});
expect(useEventJumpStore.getState().isActive).toBe(true);
expect(result.current.hints).toHaveLength(3);

act(() => {
pressEventJump();
});
expect(useEventJumpStore.getState().isActive).toBe(false);
expect(result.current.hints).toEqual([]);
expect(useEventJumpStore.getState().isActive).toBe(true);
expect(focus).toHaveBeenCalledWith(
expect.objectContaining({ eventId: fridayId, element: elements[0] }),
);
expect(useEventJumpStore.getState().activeDayKeys).toEqual([
friday.format("YYYY-MM-DD"),
]);
});

it("focuses Saturday from idle with Shift+S then a", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ const buildDayJumpAssignments = (
* its first one. Shift is what makes the day columns always available: bare
* `t`, `p`, and `m` keep their own commands. Once jump mode is on, the labels
* are typed bare (`w`, `w1`…) and win over global shortcuts. Esc exits, a
* second `f` toggles off. `s` is only the Sunday/Saturday prefix. `e` is not
* Esc exits jump mode (`f` is Friday's column letter while jump is on). `s` is
* only the Sunday/Saturday prefix. `e` is not
* a day prefix: jump yields to an armed (or about-to-arm) edit sequence so
* `e` then `t` edits the title instead of selecting Tuesday. `c` is left
* unclaimed too, so the create shortcuts can consume the selected column.
Expand Down Expand Up @@ -604,15 +605,6 @@ export function useShiftHoldEventHints({
return;
}

// The flash letter is also Friday's day prefix; bare F toggles off instead
// of jumping to Friday (Shift+F still enters Friday from idle).
if (isBareLetterKey(event, KEYMAP.eventJump.bareLetter)) {
event.preventDefault();
event.stopPropagation();
deactivate();
return;
}

// Empty buffer: digits are times. Once a day letter is typed ("w"),
// 1-2 digits still index that day's events. A four-digit HHMM still
// creates on the selected column so focusing Tuesday then 1230 lands
Expand Down Expand Up @@ -749,7 +741,7 @@ export function useShiftHoldEventHints({
eventJumpActions.setActiveDayKeys([assignment.dayKey]);
eventJumpActions.setPointerHint({
eventId: assignment.eventId,
key: assignment.hint.toUpperCase(),
key: assignment.hint,
});
setHints(toActiveHints(assignments, visibleByIdRef.current));
// Focus now so the advertised Enter path works, including when this
Expand Down Expand Up @@ -830,7 +822,7 @@ export function useShiftHoldEventHints({
if (!assignment) {
eventJumpActions.setPointerHint(null);
} else {
const nextKey = assignment.hint.toUpperCase();
const nextKey = assignment.hint;
if (nextKey !== useEventJumpStore.getState().pointerHintKey) {
eventJumpActions.setPointerHint({
eventId: assignment.eventId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe("getHintPlainText", () => {
expect(plainTextById["create-event"]).toBe("C creates an event");
expect(plainTextById["page-jump"]).toBe(`Hold ${mod} to see jump targets`);
expect(plainTextById["event-jump"]).toBe(
"F shows event and open-time shortcuts",
"f shows event and open-time shortcuts",
);
expect(plainTextById["week-day-focus"]).toBe("Shift+M jumps to Monday");
expect(plainTextById.nudge).toBe("Shift and an arrow moves the event");
Expand All @@ -46,10 +46,10 @@ describe("getHintPlainText", () => {
"Shift+R jumps to Thursday",
);
expect(getHintPlainText(weekDayFocusHint("su"))).toBe(
"Shift+S then U jumps to Sunday",
"Shift+S then u jumps to Sunday",
);
expect(getHintPlainText(weekDayFocusHint("sa"))).toBe(
"Shift+S then A jumps to Saturday",
"Shift+S then a jumps to Saturday",
);
});

Expand Down
7 changes: 1 addition & 6 deletions apps/calendar-web/src/shortcuts/tips/shortcut-tips.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,7 @@ function weekDayFocusParts(prefix: DayJumpPrefix): readonly ShortcutTipPart[] {
const [first, second] = prefix;
const shifted = { keys: ["Shift", first.toUpperCase()] } as const;
if (!second) return [shifted, ` jumps to ${dayName}`];
return [
shifted,
" then ",
{ key: second.toUpperCase() },
` jumps to ${dayName}`,
];
return [shifted, " then ", { key: second }, ` jumps to ${dayName}`];
}

/** The weekday tip names whichever column the sidebar picked, so it teaches the
Expand Down
Loading
Loading