Skip to content

Commit 25bd75c

Browse files
committed
Align VS Code tour better with web tour and move around some steps / add some steps to show intent clearer.
1 parent 1f6e67c commit 25bd75c

21 files changed

Lines changed: 810 additions & 251 deletions

File tree

apps/adr-manager/cypress/e2e/adrManagerTest/Tour.cy.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,37 @@ context("First-time tour", () => {
3333
});
3434

3535
cy.get("[data-cy=tourNext]").click();
36-
cy.get("[data-cy=tourStepTitle]").should("contain", "Search and filter ADRs");
36+
cy.get("[data-cy=tourStepTitle]").should("contain", "Search ADRs");
37+
cy.get("[data-cy=adr-filter-toggle]").should("be.visible");
3738

3839
cy.get("[data-cy=tourNext]").click();
40+
cy.get("[data-cy=tourStepTitle]").should("contain", "Filter ADRs");
41+
cy.get("[data-cy=adr-filter-panel]").should("be.visible");
42+
43+
cy.get("[data-cy=tourNext]").click();
44+
cy.get("[data-cy=tourStepTitle]").should("contain", "Apply a filter");
45+
cy.get("[data-cy=adr-filter-panel] .filter-chip.active").should("have.length", 1);
46+
47+
cy.get("[data-cy=tourNext]").click();
48+
cy.get("[data-cy=adr-filter-panel]").should("not.exist");
3949
cy.get("[data-cy=tourStepTitle]").should("contain", "Switch repositories");
4050

51+
cy.get("[data-cy=tourNext]").click();
52+
cy.get("[data-cy=tourStepTitle]").should("contain", "Delete an ADR");
53+
4154
cy.get("[data-cy=tourNext]").click();
4255
cy.get("[data-cy=tourStepTitle]").should("contain", "Create a new ADR");
4356

4457
cy.get("[data-cy=tourNext]").click();
45-
cy.get("[data-cy=tourStepTitle]").should("contain", "Edit with structured fields");
46-
cy.get("[data-cy=titleAdr]").should("exist");
58+
cy.get("[data-cy=tourStepTitle]").should("contain", "Choose the MADR version");
59+
cy.get("[data-cy=versionSelect]").should("contain", "MADR 2.1.2");
60+
cy.get("[data-cy=versionOption]").should("have.length.greaterThan", 1);
4761

4862
cy.get("[data-cy=tourNext]").click();
49-
cy.get("[data-cy=tourStepTitle]").should("contain", "Live Markdown preview");
63+
cy.get("[data-cy=versionOption]").should("not.exist");
64+
cy.get("[data-cy=tourStepTitle]").should("contain", "Edit with structured fields");
65+
cy.get("[data-cy=titleAdr]").should("exist");
5066

51-
// The toggle-fields step forces professional mode while it is shown.
5267
cy.get("[data-cy=tourNext]").click();
5368
cy.get("[data-cy=tourStepTitle]").should("contain", "Toggle optional fields");
5469
cy.get("[data-cy=decisionDriversPro]").should("exist");
@@ -58,12 +73,9 @@ context("First-time tour", () => {
5873
cy.get("[data-cy=fieldsBtn]").should("be.visible");
5974

6075
cy.get("[data-cy=tourNext]").click();
61-
cy.get("[data-cy=tourStepTitle]").should("contain", "Delete an ADR");
76+
cy.get("[data-cy=tourStepTitle]").should("contain", "Live Markdown preview");
6277
cy.get("[data-cy=decisionDriversPro]").should("not.exist");
6378

64-
cy.get("[data-cy=tourNext]").click();
65-
cy.get("[data-cy=tourStepTitle]").should("contain", "The ADR directory");
66-
6779
cy.get("[data-cy=tourNext]").click();
6880
cy.get("[data-cy=tourStepTitle]").should("contain", "Commit your changes");
6981

apps/adr-manager/src/components/MadrVersionSelect.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
2-
<div ref="wrap" class="verselect">
3-
<button type="button" data-cy="versionSelect" class="verselect-btn" @click="open = !open">
2+
<div ref="wrap" class="verselect" data-tour="template-version" @tour-version-menu="setTourMenuOpen">
3+
<button type="button" data-cy="versionSelect" class="verselect-btn" :aria-expanded="open" @click="open = !open">
44
<span class="mdi mdi-file-tree-outline" aria-hidden="true"></span>
55
<span class="vlabel">MADR {{ modelValue }}</span>
66
<span class="ver-pill">TEMPLATE</span>
@@ -45,6 +45,10 @@ function select(version: MadrTemplateVersion): void {
4545
emit("update:modelValue", version);
4646
open.value = false;
4747
}
48+
49+
function setTourMenuOpen(event: Event): void {
50+
open.value = (event as CustomEvent<boolean>).detail;
51+
}
4852
</script>
4953

5054
<style scoped>

apps/adr-manager/src/composables/useTour.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { computed, readonly, ref } from "vue";
22
import { injectDemo, snapshotEditorState, teardownDemo } from "@/plugins/tour/demoRepository";
3-
import { tourSteps } from "@/plugins/tour/steps";
3+
import { resetTourFilterDemonstration, tourSteps } from "@/plugins/tour/steps";
44
import { store } from "@/plugins/store";
55
import type { TourSnapshot } from "@/plugins/tour/demoRepository";
66

@@ -37,6 +37,7 @@ export function useTour() {
3737
return;
3838
}
3939
currentStep.value?.onExit?.();
40+
resetTourFilterDemonstration();
4041
active.value = false;
4142
if (snapshot) {
4243
// Safe in both modes: without demo state the snapshot matches the live state.

apps/adr-manager/src/plugins/tour/demoRepository.ts

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
* step can point at real UI. It is removed on teardown and (via the `transient`
55
* filter in the store) never written to localStorage.
66
*/
7-
import { ArchitecturalDecisionRecord, Repository } from "@/plugins/classes";
8-
import { adr2md } from "@/plugins/parser";
7+
import { Repository } from "@/plugins/classes";
8+
import { serializeMadr } from "@/plugins/parser";
99
import { store } from "@/plugins/store";
1010
import { DEMO_REPO_FULL_NAME } from "@/plugins/tour/constants";
1111
import type { AdrFile } from "@/types/adr";
1212
import type { Mode } from "@/types/store";
13+
import { buildPrimaryDemoAdrFixture, setTagsInMd } from "@adr-manager/core";
1314

1415
export interface TourSnapshot {
1516
currentRepository: Repository | undefined;
@@ -18,44 +19,15 @@ export interface TourSnapshot {
1819
}
1920

2021
/**
21-
* The markdown is generated with adr2md from a constructed record (never hand-written)
22+
* The markdown is serialized from the shared demo fixture (never hand-written)
2223
* so it survives the round-trip check in useAdrEditor and opens in the form editor
2324
* instead of the convert view. Professional-only fields (drivers, consequences) are
2425
* populated so the Basic/Professional tour step visibly reveals content.
2526
*/
2627
export function buildDemoRepository(): Repository {
27-
const record = new ArchitecturalDecisionRecord({
28-
title: "Use Markdown Architectural Decision Records",
29-
status: "accepted",
30-
date: "2024-03-18",
31-
deciders: "Ada Lovelace, Grace Hopper",
32-
contextAndProblemStatement:
33-
"We want to record the architectural decisions made in this project, " +
34-
"so that new team members can understand why the system looks the way it does.",
35-
decisionDrivers: [
36-
"Decisions should live next to the code they affect",
37-
"The format should be reviewable in pull requests"
38-
],
39-
consideredOptions: [
40-
{
41-
title: "MADR",
42-
description: "Markdown Architectural Decision Records.",
43-
pros: ["Plain Markdown, works with any Git host", "Lightweight template"],
44-
cons: ["Needs discipline to keep up to date"]
45-
},
46-
{ title: "A wiki", pros: ["Easy to edit"], cons: ["Drifts away from the code"] },
47-
{ title: "No documentation" }
48-
],
49-
decisionOutcome: {
50-
chosenOption: "MADR",
51-
explanation: "it keeps the decision history versioned together with the code",
52-
positiveConsequences: ["Every decision is reviewable and traceable"],
53-
negativeConsequences: ["Writing a record takes a little time"]
54-
},
55-
links: []
56-
});
57-
const md = adr2md(record);
58-
const path = "docs/decisions/0001-use-markdown-architectural-decision-records.md";
28+
const fixture = buildPrimaryDemoAdrFixture();
29+
const md = setTagsInMd(serializeMadr(fixture.record, fixture.templateVersion), fixture.tags);
30+
const path = `docs/decisions/${fixture.fileName}`;
5931
const adrFile: AdrFile = {
6032
originalMd: md,
6133
editedMd: md,

apps/adr-manager/src/plugins/tour/steps.ts

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { store } from "@/plugins/store";
22
import type { Mode } from "@/types/store";
3+
import { TOUR_COPY } from "@adr-manager/core";
34

45
export interface TourStep {
56
id: string;
@@ -14,15 +15,49 @@ export interface TourStep {
1415

1516
let modeBeforeToggleStep: Mode | undefined;
1617
let modeBeforeFieldVisibilityStep: Mode | undefined;
18+
let demonstratedFilter: HTMLButtonElement | undefined;
19+
20+
function setFilterPanelOpen(open: boolean): void {
21+
const button = document.querySelector<HTMLButtonElement>('[data-cy="adr-filter-toggle"]');
22+
if (button && (button.getAttribute("aria-expanded") === "true") !== open) {
23+
button.click();
24+
}
25+
}
26+
27+
function setExampleFilterActive(active: boolean): void {
28+
if (active) {
29+
setFilterPanelOpen(true);
30+
demonstratedFilter =
31+
document.querySelector<HTMLButtonElement>(".filter-panel .filter-chip:not(.tags-more-btn):not(.active)") ??
32+
undefined;
33+
demonstratedFilter?.click();
34+
return;
35+
}
36+
if (demonstratedFilter?.getAttribute("aria-pressed") === "true") {
37+
demonstratedFilter.click();
38+
}
39+
demonstratedFilter = undefined;
40+
setFilterPanelOpen(false);
41+
}
42+
43+
function setTemplateVersionMenuOpen(open: boolean): void {
44+
const wrap = document.querySelector<HTMLElement>('[data-tour="template-version"]');
45+
wrap?.dispatchEvent(new CustomEvent<boolean>("tour-version-menu", { detail: open }));
46+
}
47+
48+
export function resetTourFilterDemonstration(): void {
49+
setExampleFilterActive(false);
50+
setFilterPanelOpen(false);
51+
setTemplateVersionMenuOpen(false);
52+
}
1753

1854
export const tourSteps: TourStep[] = [
1955
{
2056
id: "intro",
2157
title: "Architectural Decision Records",
2258
body:
23-
"An ADR is a short Markdown document that captures one architectural decision, the context behind it, " +
24-
"the options that were considered and the outcome. ADR Manager lets you create, edit and commit ADRs " +
25-
"in the MADR format directly in your Git repositories."
59+
TOUR_COPY.adrDefinition +
60+
" ADR Manager lets you create, edit and commit ADRs in the MADR format directly in your Git repositories."
2661
},
2762
{
2863
id: "explorer",
@@ -37,10 +72,25 @@ export const tourSteps: TourStep[] = [
3772
id: "adr-search",
3873
target: '[data-tour="adr-search"]',
3974
placement: "right",
40-
title: "Search and filter ADRs",
41-
body:
42-
"Type in the search bar to find ADRs by title across all your repositories. Use the filter button " +
43-
"to narrow down decisions by status, tags, or other criteria."
75+
title: "Search ADRs",
76+
body: TOUR_COPY.search
77+
},
78+
{
79+
id: "adr-filter",
80+
target: '[data-cy="adr-filter-toggle"]',
81+
placement: "right",
82+
title: "Filter ADRs",
83+
body: TOUR_COPY.filter,
84+
onEnter: () => setFilterPanelOpen(true)
85+
},
86+
{
87+
id: "adr-filter-example",
88+
target: () => document.querySelector(".filter-panel .filter-chip:not(.tags-more-btn)"),
89+
placement: "right",
90+
title: "Apply a filter",
91+
body: "Selecting a status or tag immediately narrows the ADR list. Select it again to remove the filter.",
92+
onEnter: () => setExampleFilterActive(true),
93+
onExit: () => setExampleFilterActive(false)
4494
},
4595
{
4696
id: "switch-repository",
@@ -52,43 +102,47 @@ export const tourSteps: TourStep[] = [
52102
"Click a repository to expand it and make it active. You can keep several repositories open " +
53103
"and switch between them at any time."
54104
},
105+
{
106+
id: "delete-adr",
107+
target: '[data-cy="deleteAdrBtn"]',
108+
placement: "right",
109+
title: "Delete an ADR",
110+
body:
111+
"Hover an ADR in the explorer and click the trash icon to delete it. The deletion is staged " +
112+
"locally and only applied to the repository when you commit."
113+
},
55114
{
56115
id: "create-adr",
57116
target: '[data-cy="newADR"]',
58117
placement: "right",
59118
title: "Create a new ADR",
60119
body:
61120
"New ADR adds a numbered Markdown file based on the MADR template to the repository's " +
62-
"ADR directory and opens it in the editor."
121+
"ADR directory and opens it in the editor. New setups default to docs/decisions; the status bar " +
122+
"shows the full path and active branch."
123+
},
124+
{
125+
id: "template-version",
126+
target: '[data-tour="template-version"]',
127+
placement: "bottom",
128+
title: "Choose the MADR version",
129+
body: TOUR_COPY.templateVersion,
130+
onEnter: () => setTemplateVersionMenuOpen(true),
131+
onExit: () => setTemplateVersionMenuOpen(false)
63132
},
64133
{
65134
id: "edit-adr",
66135
target: '[data-tour="editor"]',
67136
placement: "over",
68137
title: "Edit with structured fields",
69-
body:
70-
"The editor turns the MADR template into a form with fields like title, context, considered options " +
71-
"and decision outcome. Everything you type is converted to Markdown as you go, and the title also " +
72-
"becomes the file name."
73-
},
74-
{
75-
id: "preview",
76-
target: '[data-tour="preview"]',
77-
placement: "left",
78-
title: "Live Markdown preview",
79-
body:
80-
"This pane shows the generated Markdown in real time. You can also edit the raw Markdown here, " +
81-
"the form and the source stay in sync."
138+
body: TOUR_COPY.editorIntro
82139
},
83140
{
84141
id: "toggle-fields",
85142
target: '[data-tour="mode-toggle"]',
86143
placement: "bottom",
87144
title: "Toggle optional fields",
88-
body:
89-
"This switch toggles the optional MADR fields. Professional mode reveals decision drivers, pros and " +
90-
"cons per option and detailed consequences, while Basic keeps only the essentials. Hidden fields are " +
91-
"kept in the file.",
145+
body: TOUR_COPY.modeToggle,
92146
onEnter: () => {
93147
modeBeforeToggleStep = store.mode;
94148
// Direct assignment so the demonstration does not persist a mode change.
@@ -106,7 +160,7 @@ export const tourSteps: TourStep[] = [
106160
target: '[data-tour="field-visibility"]',
107161
placement: "bottom",
108162
title: "Customize visible fields",
109-
body: "The Fields button is a Professional mode feature that lets you toggle individual Professional mode sections on or off to match your personal preferences.",
163+
body: TOUR_COPY.fieldVisibility,
110164
onEnter: () => {
111165
modeBeforeFieldVisibilityStep = store.mode;
112166
store.mode = "professional";
@@ -119,23 +173,13 @@ export const tourSteps: TourStep[] = [
119173
}
120174
},
121175
{
122-
id: "delete-adr",
123-
target: '[data-cy="deleteAdrBtn"]',
124-
placement: "right",
125-
title: "Delete an ADR",
126-
body:
127-
"Hover an ADR in the explorer and click the trash icon to delete it. The deletion is staged " +
128-
"locally and only applied to the repository when you commit."
129-
},
130-
{
131-
id: "adr-directory",
132-
target: '[data-tour="adr-path"]',
133-
placement: "top",
134-
title: "The ADR directory",
176+
id: "preview",
177+
target: '[data-tour="preview"]',
178+
placement: "left",
179+
title: "Live Markdown preview",
135180
body:
136-
"Each repository keeps its ADRs in one directory, which ADR Manager detects automatically " +
137-
"(new setups default to docs/decisions). The status bar shows the full path of the open ADR " +
138-
"and the branch you are working on."
181+
"This pane shows the generated Markdown in real time. You can also edit the raw Markdown here, " +
182+
"the form and the source stay in sync."
139183
},
140184
{
141185
id: "commit",

apps/adr-manager/tests/tour-demo.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { matchesIgnoringFormatting } from "@adr-manager/core";
1+
import { matchesIgnoringFormatting, parseTagsFromMd, stripTagComment } from "@adr-manager/core";
22
import { Repository } from "@/plugins/classes";
33
import { adr2md, md2adr } from "@/plugins/parser";
44
import { store } from "@/plugins/store";
@@ -17,7 +17,8 @@ test("the demo ADR round-trips through the MADR parser", () => {
1717
const repo = buildDemoRepository();
1818
const md = repo.adrs[0]?.editedMd ?? "";
1919
// A failed round-trip would open the demo in the convert view and break the tour anchors.
20-
expect(matchesIgnoringFormatting(md, adr2md(md2adr(md)))).toBe(true);
20+
expect(matchesIgnoringFormatting(stripTagComment(md), adr2md(md2adr(md)))).toBe(true);
21+
expect(parseTagsFromMd(md).length).toBeGreaterThan(0);
2122
});
2223

2324
test("the demo repository is transient and opens its sample ADR", () => {

apps/adr-manager/tests/use-tour.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,26 @@ afterEach(() => {
2929
localStorage.clear();
3030
});
3131

32+
test("tour steps stay grouped by screen section", () => {
33+
expect(tourSteps.map((step) => step.id)).toEqual([
34+
"intro",
35+
"explorer",
36+
"adr-search",
37+
"adr-filter",
38+
"adr-filter-example",
39+
"switch-repository",
40+
"delete-adr",
41+
"create-adr",
42+
"template-version",
43+
"edit-adr",
44+
"toggle-fields",
45+
"field-visibility",
46+
"preview",
47+
"commit",
48+
"replay"
49+
]);
50+
});
51+
3252
test("starting over an empty workspace injects the demo repository", () => {
3353
tour.start();
3454

0 commit comments

Comments
 (0)