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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion BlogWriter.Web.Tests/BlogWorkspaceServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,30 @@

namespace BlogWriter.Web.Tests;

public sealed class BlogWorkspaceServiceTests
public sealed class BlogWorkspaceServiceTests : IDisposable
{
private readonly SynchronizationContext? _previousSynchronizationContext = SynchronizationContext.Current;

public BlogWorkspaceServiceTests() =>
// BlogWorkspaceService reports progress via IProgress<T>, which posts to
// SynchronizationContext.Current. Without an ambient context (the default
// under xUnit), Progress<T> falls back to ThreadPool.QueueUserWorkItem,
// which races with the synchronous continuation after each awaited
// operation and can append reviewer feedback out of order. Installing an
// immediate, single-threaded context here makes that ordering
// deterministic for every test in this class, matching how a Blazor
// Server circuit's single-threaded dispatcher behaves in production.
SynchronizationContext.SetSynchronizationContext(new ImmediateSynchronizationContext());

public void Dispose() => SynchronizationContext.SetSynchronizationContext(_previousSynchronizationContext);

private sealed class ImmediateSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object? state) => d(state);

public override void Send(SendOrPostCallback d, object? state) => d(state);
}

[Fact]
public async Task NewState_DisablesRevisionInputUntilDraftExists()
{
Expand Down
6 changes: 3 additions & 3 deletions BlogWriter.Web.Tests/HomePageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public sealed class HomePageTests : BunitContext
public HomePageTests() => JSInterop.Mode = JSRuntimeMode.Loose;

[Fact]
public void Home_RendersWritingWorkspaceAndFourCommands()
public void Home_RendersWritingWorkspaceAndFiveCommands()
{
BlogWorkspaceService workspace = RegisterWorkspace();

Expand All @@ -21,7 +21,7 @@ public void Home_RendersWritingWorkspaceAndFourCommands()
Assert.NotNull(cut.Find("#revision-prompt"));
Assert.NotNull(cut.Find("[aria-labelledby='draft-heading']"));
Assert.NotNull(cut.Find("[aria-labelledby='review-heading']"));
Assert.Equal(["New", "List", "Quit", "?"],
Assert.Equal(["New", "List", "Go", "Quit", "?"],
cut.FindAll(".command-bar button").Select(button => button.TextContent.Trim()).ToArray());
Assert.True(cut.Find("#revision-prompt").HasAttribute("disabled"));
Assert.False(workspace.State.IsSelectionVisible);
Expand Down Expand Up @@ -98,7 +98,7 @@ public void Home_UsesAccessibleRegionsLabelsAndLiveStatus()
RegisterWorkspace();
IRenderedComponent<Home> cut = Render<Home>();

Assert.Equal("New writing prompt", cut.Find("label[for='initial-prompt']").TextContent.Trim());
Assert.Equal("New query", cut.Find("label[for='initial-prompt']").TextContent.Trim());
Assert.Equal("Revision request", cut.Find("label[for='revision-prompt']").TextContent.Trim());
Assert.NotNull(cut.Find("nav[aria-label='Workspace commands']"));
Assert.NotNull(cut.Find("[aria-live='polite']"));
Expand Down
2 changes: 2 additions & 0 deletions BlogWriter.Web/wwwroot/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ button, a { touch-action: manipulation; }
cursor: pointer;
}
.range-go:hover:not(:disabled) { background: var(--forest-dark); }
.range-go:active:not(:disabled) { background: var(--forest-dark); }
.range-go:disabled { border-color: var(--line); background: #ecece7; color: var(--muted); cursor: not-allowed; }
.range-go:active:disabled { background: #ecece7; }

.work-grid { min-height: 32vh; display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(280px, .75fr); border: 1px solid var(--ink); }
.work-pane { min-width: 0; display: grid; grid-template-rows: auto minmax(0, 1fr); background: var(--surface); }
Expand Down
34 changes: 34 additions & 0 deletions specs/010-go-button-press-colors/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Specification Quality Checklist: Go Button Press Color Feedback

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-09-24
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- All checklist items pass on first pass; no spec updates required before `/speckit-clarify` or `/speckit-plan`.
25 changes: 25 additions & 0 deletions specs/010-go-button-press-colors/contracts/workspace-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Workspace UI Contract: Go Button Press Color Feedback

## Controls

| Control | Identifier/relationship | Behavior |
|---|---|---|
| Go | Existing command-bar button, `.range-go` (`CommandBar.razor`) | Idle/enabled: light green background (`--forest`). Hover (enabled, not pressed): dark green background (`--forest-dark`), unchanged from existing behavior. Pressed (enabled, mouse-down/touch-active/keyboard-activation in progress): dark green background (`--forest-dark`) via `:active`. Disabled: existing muted/gray disabled styling takes precedence over pressed styling. |

## Visual State Transitions

- Idle → Pressed: on press start (`:active`), while enabled.
- Pressed → Idle: on press end, cancellation, or pointer/focus leaving the button before release; no stuck-pressed state is permitted.
- Any state → Disabled: the `disabled` attribute styling always wins; a disabled button never shows the pressed (dark green) color even if a press gesture is attempted.

## Accessibility and Keyboard Behavior

- No change to the Go button's accessible name, role, tab order, or `disabled` semantics.
- The pressed color is a supplementary visual cue only; it does not replace or alter any existing text, label, or ARIA attributes conveying the button's action or state.
- Keyboard activation (Space/Enter) on a focused, enabled Go button must trigger the same pressed-color feedback as a mouse or touch press.

## Non-Goals

- No new HTTP, public API, storage, or hosted-agent contract.
- No change to the Go button's click/submission behavior, word-range parsing, session ownership, cancellation, or workflow-output contracts (see `009-explicit-go-submission`).
- No change to any other command-bar control's styling or behavior.
18 changes: 18 additions & 0 deletions specs/010-go-button-press-colors/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Phase 1 Data Model: Go Button Press Color Feedback

This feature introduces no persisted data, domain entities, or state stored beyond the
browser's native rendering of CSS pseudo-classes. There is no `IBlogSessionStore` /
`ResearchState` change and nothing to add to the workflow's domain model.

## Conceptual state (not persisted)

| State | Trigger | Visual Result | Notes |
|-------|---------|----------------|-------|
| Idle | Default; button enabled, not pressed | Light green background (`--forest`) | Existing behavior, unchanged |
| Hover | Pointer over enabled, non-pressed button | Dark green background (`--forest-dark`) | Existing `:hover:not(:disabled)` rule, unchanged |
| Pressed | Pointer/touch/keyboard press held on enabled button | Dark green background (`--forest-dark`) | New `:active:not(:disabled)` rule (this feature) |
| Disabled | `disabled` attribute present | Muted gray background, no pointer cursor | Existing `:disabled` rule; MUST take precedence over Pressed (FR-005), enforced by an `:active:disabled` override |

These are transient CSS/browser render states, not application entities — they have no
identifiers, relationships, or persistence lifecycle, and require no repository, store, or
model class changes.
80 changes: 80 additions & 0 deletions specs/010-go-button-press-colors/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Implementation Plan: Go Button Press Color Feedback

**Branch**: `010-go-button-press-colors` | **Date**: 2026-09-24 | **Spec**: [spec.md](./spec.md)

**Input**: Feature specification from `/specs/010-go-button-press-colors/spec.md`

**Note**: This template is filled in by the `/speckit-plan` command; its definition describes the execution workflow.

## Summary

Add visible press-state feedback to the existing `Go` command-bar button: it already renders with a light-green background (`--forest`) and darkens (`--forest-dark`) on hover, but has no distinct feedback while actually pressed/activated. The fix is a CSS-only addition of an `:active` (and disabled-safe) style rule on `.range-go` in `BlogWriter.Web/wwwroot/app.css`, using the existing `--forest` / `--forest-dark` custom properties, with no Razor markup or C# behavior changes required.

## Technical Context

**Language/Version**: C# / .NET 10 (Blazor Web App, `BlogWriter.Web`); plain CSS for styling

**Primary Dependencies**: ASP.NET Core Blazor (existing `CommandBar.razor` component); no new packages

**Storage**: N/A (purely presentational; no persisted state)

**Testing**: bUnit component tests in `BlogWriter.Web.Tests` (e.g. `HomePageTests.cs`) for markup/class assertions; manual/visual verification for `:active` pseudo-class behavior, which bUnit cannot exercise directly since it does not run real CSS pseudo-class matching

**Target Platform**: Web browser (desktop and touch), served by the Blazor Web App

**Project Type**: Web application — single existing Blazor project (`BlogWriter.Web`) plus its test project; no frontend/backend split to introduce

**Performance Goals**: N/A beyond standard browser rendering (color change must be perceptible within a single frame of press/release, per SC-001)

**Constraints**: Must not alter the button's label, size, position, disabled semantics, or keyboard/pointer accessibility; must preserve sufficient contrast between idle and pressed colors (FR-007)

**Scale/Scope**: Single UI element (`.range-go` in the command bar); no other buttons, pages, or components affected

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

- **I. Hosted-Agent Boundaries** — N/A. No hosted-agent, workflow, or console-orchestration code is touched; this is a Blazor UI styling change confined to `BlogWriter.Web`.
- **II. MAF-Native Workflow Composition** — N/A. No agents, executors, or workflow edges/states are involved.
- **III. Identity, Secrets, and Budget Control** — N/A. No Azure/Foundry calls, credentials, or token budget are affected.
- **IV. Testable and Observable Behavior** — Satisfied. The change is small enough to verify via existing bUnit component tests (asserting the button still renders with its expected class/attributes and disabled behavior is unchanged) plus a manual/visual check of the new `:active` color, since bUnit does not evaluate CSS pseudo-classes.
- **V. Simple, Compatible Evolution** — Satisfied. This is the smallest possible change (a CSS rule addition reusing existing `--forest`/`--forest-dark` tokens) that preserves the existing markup, component API, and all other button behavior.

No violations identified. Complexity Tracking section is not needed.

## Project Structure

### Documentation (this feature)

```text
specs/[###-feature]/
├── plan.md # This file (/speckit-plan command output)
├── research.md # Phase 0 output (/speckit-plan command)
├── data-model.md # Phase 1 output (/speckit-plan command)
├── quickstart.md # Phase 1 output (/speckit-plan command)
├── contracts/ # Phase 1 output (/speckit-plan command)
└── tasks.md # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan)
```

### Source Code (repository root)

```text
BlogWriter.Web/
├── Components/
│ └── CommandBar.razor # Renders the .range-go "Go" button (markup unchanged)
└── wwwroot/
└── app.css # .range-go rule set: add :active / :active:disabled styling here

BlogWriter.Web.Tests/
└── HomePageTests.cs # Existing bUnit coverage of the command bar / Go button markup
```

**Structure Decision**: This feature stays entirely inside the existing single Blazor web
project `BlogWriter.Web` and its companion test project `BlogWriter.Web.Tests`. No new
projects, folders, or cross-project contracts are introduced; the only source change is a
CSS rule addition in `BlogWriter.Web/wwwroot/app.css` for the `.range-go` selector already
used by `CommandBar.razor`.

## Complexity Tracking

*No Constitution Check violations were identified; this section is not applicable.*
59 changes: 59 additions & 0 deletions specs/010-go-button-press-colors/quickstart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Quickstart: Validate Go Button Press Color Feedback

## Prerequisites

- .NET 10 SDK installed
- Repository checked out on branch `010-go-button-press-colors`
- No additional packages or services required (pure front-end CSS change)

## Setup

```powershell
cd E:\ai\.net\blog\BlogWriter
dotnet build BlogWriter.Web\BlogWriter.Web.csproj
```

## Automated check (regression only)

Run the existing Blazor component tests to confirm the Go button's markup, class name,
and disabled behavior are unchanged (bUnit does not evaluate `:active` CSS, so this does
not verify the color itself — see manual check below):

```powershell
dotnet test BlogWriter.Web.Tests\BlogWriter.Web.Tests.csproj
```

Expected: all existing tests referencing the command bar / Go button continue to pass
with no changes required to the test assertions (see `contracts/workspace-ui.md` for the
behavior these tests should continue to cover).

## Manual validation (visual)

1. Run the web app:
```powershell
dotnet run --project BlogWriter.Web\BlogWriter.Web.csproj
```
2. Open the app in a browser and locate the **Go** button in the command bar (after the
word-range Min/Max fields).
3. **Idle**: Confirm the Go button shows a light green background (`--forest`) when not
hovered or pressed.
4. **Pressed — mouse**: Click and hold the mouse button down on Go. Confirm the background
switches to dark green (`--forest-dark`) while held down, and reverts to light green on
release.
5. **Pressed — drag off**: Press down on Go, then drag the pointer off the button before
releasing. Confirm the button returns to light green and does not stay stuck in the
dark green pressed state.
6. **Pressed — keyboard**: Tab to focus the Go button, then press and hold Space or press
Enter. Confirm the same dark green pressed feedback appears.
7. **Pressed — touch** (if a touch device/emulator is available): Touch and hold the Go
button. Confirm the same dark green pressed feedback appears, and reverts on release.
8. **Disabled**: Trigger a state where Go is disabled (e.g. per existing validation rules
in the word-range row) and attempt to press it. Confirm it never shows the dark green
pressed color and instead keeps its existing disabled (muted) appearance.

## Expected Outcome

- All steps above match the acceptance scenarios and edge cases in
`spec.md` (User Story 1, Edge Cases).
- No other command-bar button's appearance or behavior changes.
- The Go button's label, size, position, and click/submission behavior are unchanged.
56 changes: 56 additions & 0 deletions specs/010-go-button-press-colors/research.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Phase 0 Research: Go Button Press Color Feedback

No `NEEDS CLARIFICATION` markers remain in the Technical Context — the feature is scoped
entirely within existing, well-understood project conventions. This document records the
small set of decisions made while confirming the approach.

## Decision: Reuse existing color tokens instead of introducing new ones

- **Decision**: Implement "light green" and "dark green" using the existing CSS custom
properties `--forest` (`#1e5a46`) and `--forest-dark` (`#123d30`), already defined in
`BlogWriter.Web/wwwroot/app.css` and already used as the Go button's idle background and
hover background respectively.
- **Rationale**: These tokens already read as "lighter green" vs. "darker green," are used
consistently elsewhere in the stylesheet (e.g. `.sign-out`), and satisfy FR-007's
contrast/brightness requirement without introducing new design tokens or inconsistency
with the rest of the app's palette.
- **Alternatives considered**: Defining new `--go-pressed` / `--go-idle` variables was
considered but rejected as unnecessary — it would duplicate values that are functionally
identical to `--forest`/`--forest-dark` and add indirection for no benefit, conflicting
with Constitution Principle V (smallest change that preserves existing conventions).

## Decision: Implement press feedback via CSS `:active` pseudo-class, not component state

- **Decision**: Add an `:active` (and `:active:disabled` guard) rule to the existing
`.range-go` selector rather than introducing C#/Razor state (e.g. a `isPressed` field
toggled by `@onmousedown`/`@onmouseup`).
- **Rationale**: The browser's native `:active` pseudo-class already fires for mouse,
touch, and keyboard (Space/Enter) activation on a `<button>` element, automatically
reverts when the pointer leaves the element or the press is cancelled, and requires no
JavaScript interop or component re-renders. This satisfies FR-002–FR-004 and the edge
cases (drag-off, keyboard activation) with a single, dependency-free CSS rule, and keeps
parity with the existing `:hover:not(:disabled)` / `:disabled` rules already on
`.range-go`.
- **Alternatives considered**:
- *Blazor event-handler-driven state* (`@onmousedown` / `@onmouseup` toggling a CSS
class): rejected — adds C# state, re-render churn, and must be re-implemented per
input modality (mouse/touch/keyboard) to match what `:active` already provides for
free; higher risk of a "stuck pressed" bug on drag-off (an edge case the spec calls
out).
- *JavaScript interop for press detection*: rejected — introduces a new dependency and
interop surface for a purely visual, already-native browser behavior.

## Decision: Verification strategy given bUnit's CSS limitations

- **Decision**: Use existing bUnit tests in `BlogWriter.Web.Tests` to assert the Go
button's markup, class name (`range-go`), and disabled-attribute behavior remain
unchanged, and rely on manual/visual verification (per `quickstart.md`) for the actual
`:active` color transition, since bUnit renders a virtual DOM and does not execute a real
browser's CSS engine or pseudo-class matching.
- **Rationale**: This matches Constitution Principle IV (testable/observable business
logic) while being honest about the boundary of what a component-test framework can
verify for pure CSS pseudo-class styling; it avoids introducing a heavier E2E/browser
test framework for a one-selector CSS change.
- **Alternatives considered**: Adding a full browser-automation test (e.g. Playwright) was
considered but rejected as disproportionate tooling investment for a single CSS rule,
and no such framework currently exists in this repository.
Loading
Loading