Skip to content
Draft
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
55 changes: 55 additions & 0 deletions .github/workflows/hr-workspace-performance-goal-review-state.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: HR Workspace Performance Goal Review State Quality

on:
pull_request:
branches:
- develop
- feat/hr-workspace-protected-read-state
paths:
- "apps/hr-workspace/performance-goal-review-state.js"
- "apps/hr-workspace/performance-goal-review-state.css"
- "apps/hr-workspace/performance-goal-review-state.stories.js"
- "tests/hr-workspace-performance-goal-review-state.test.mjs"
- "docs/traceability/hr-workspace-performance-goal-review-state.md"
- "docs/doctoring/hr-workspace-performance-goal-accessibility-references.md"
- ".github/workflows/hr-workspace-performance-goal-review-state.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: hr-workspace-performance-goal-review-state-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
performance-goal-review-state:
name: Performance goal review state contract
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout exact candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- name: Prove exact candidate checkout
env:
ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA"
- name: Set up Node.js LTS
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24"
check-latest: false
- name: Run performance-goal review accessibility contract with exact coverage
run: >-
node --test --experimental-test-coverage
--test-coverage-lines=100
--test-coverage-branches=100
--test-coverage-functions=100
tests/hr-workspace-performance-goal-review-state.test.mjs
- name: Require clean checkout
run: |
git diff --exit-code
test -z "$(git status --porcelain)"
62 changes: 62 additions & 0 deletions apps/hr-workspace/performance-goal-review-state.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.performance-goal-review-state {
display: grid;
gap: var(--orgmetra-space-md);
max-width: 40rem;
padding: var(--orgmetra-space-lg);
border: 1px solid var(--orgmetra-border-subtle);
border-radius: var(--orgmetra-radius-md);
background: var(--orgmetra-surface-card);
color: var(--orgmetra-text-primary);
}

.performance-goal-review-status,
.performance-goal-review-next-action {
display: grid;
gap: var(--orgmetra-space-xs);
margin: 0;
}

.performance-goal-review-status span,
.performance-goal-review-next-action span {
color: var(--orgmetra-text-muted);
}

.performance-goal-review-state[data-interaction-state="high-risk-confirmation"] {
border-color: var(--orgmetra-action-review);
}

.performance-goal-review-state[data-interaction-state="read-only"] {
border-color: var(--orgmetra-border-subtle);
}

.performance-goal-review-state[data-interaction-state="permission-denied"],
.performance-goal-review-state[data-interaction-state="validation-error"],
.performance-goal-review-state[data-interaction-state="error"] {
border-color: var(--orgmetra-danger);
}

.performance-goal-review-state[aria-busy="true"] {
cursor: progress;
}

.performance-goal-review-submit {
justify-self: start;
min-height: 44px;
padding: var(--orgmetra-space-sm) var(--orgmetra-space-md);
border: 0;
border-radius: var(--orgmetra-radius-sm);
background: var(--orgmetra-action-review);
color: #fff;
font: inherit;
cursor: pointer;
}

.performance-goal-review-submit:disabled {
cursor: not-allowed;
opacity: 0.62;
}

.performance-goal-review-submit:focus-visible {
outline: 3px solid var(--orgmetra-focus-ring);
outline-offset: 3px;
}
86 changes: 86 additions & 0 deletions apps/hr-workspace/performance-goal-review-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const STATE_MODELS = Object.freeze({
idle: Object.freeze({
ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: false,
interactionState: 'default', actionLabel: 'Load goal-plan evidence',
label: 'Review performance-goal plan evidence',
message: 'Load fresh governed performance-goal plan evidence before asking a human reviewer to confirm the plan.',
nextAction: 'Load the current governed goal-plan evidence for this authorized HR task.',
}),
loading: Object.freeze({
ariaBusy: 'true', ariaLive: 'polite', role: 'status', submitDisabled: true,
interactionState: 'loading', actionLabel: 'Loading goal-plan evidence',
label: 'Loading current goal-plan evidence',
message: 'Orgmetra is resolving fresh Employment, Job, performance-cycle, goal-set, measurement, cadence, actor, and chronology evidence.',
nextAction: 'Wait for the current governed goal-plan evidence load to finish.',
}),
review: Object.freeze({
ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: false,
interactionState: 'high-risk-confirmation', actionLabel: 'Record human review',
label: 'Human review required before goal-plan activation',
message: 'A human reviewer may confirm the governed plan evidence. This review does not activate the plan and does not authorize a performance rating, compensation action, or employment decision.',
nextAction: 'Confirm only the reviewed evidence; activation remains a separately governed operation.',
}),
recording: Object.freeze({
ariaBusy: 'true', ariaLive: 'polite', role: 'status', submitDisabled: true,
interactionState: 'loading', actionLabel: 'Recording review evidence',
label: 'Recording goal-plan review evidence',
message: 'Orgmetra is recording the human-review evidence. Duplicate confirmation is disabled while this operation is in flight.',
nextAction: 'Wait for the immutable review evidence to finish recording before continuing.',
}),
recorded: Object.freeze({
ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: true,
interactionState: 'read-only', actionLabel: 'Review evidence recorded',
label: 'Goal-plan review evidence recorded',
message: 'This is read-only review evidence. Recording it does not activate the plan and does not authorize performance rating, compensation, or an employment decision.',
nextAction: 'If activation is required, continue through the separately governed activation boundary using fresh authoritative evidence.',
}),
denied: Object.freeze({
ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false,
interactionState: 'permission-denied', actionLabel: 'Review access',
label: 'Goal-plan review access denied',
message: 'The current purpose or actor authority does not permit this performance-goal review task.',
nextAction: 'Check the purpose and access authority before loading or reviewing goal-plan evidence again.',
}),
stale: Object.freeze({
ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false,
interactionState: 'validation-error', actionLabel: 'Reload evidence',
label: 'Goal-plan evidence is stale',
message: 'The authoritative goal-plan scope changed before the review evidence could be relied on.',
nextAction: 'Reload authoritative Employment, Job, performance-cycle, goal-set, measurement, cadence, actor, and chronology evidence before reviewing again.',
}),
activationBlocked: Object.freeze({
ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false,
interactionState: 'validation-error', actionLabel: 'Review activation scope',
label: 'Goal-plan activation is blocked',
message: 'The recorded review cannot proceed to activation while authoritative scope or chronology is inconsistent.',
nextAction: 'Resolve Employment, Job, performance cycle, goal-set, measurement, cadence, actor, and chronology evidence through the governed owners before requesting activation.',
}),
error: Object.freeze({
ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false,
interactionState: 'error', actionLabel: 'Retry governed load',
label: 'Performance-goal review unavailable',
message: 'The governed performance-goal review service did not return usable authoritative evidence.',
nextAction: 'Do not activate or infer a performance outcome from cached or partial evidence; verify the service and authorization before retrying.',
}),
});

function requireExactState(value) {
if (typeof value !== 'string') {
throw new TypeError('performance-goal review state must be an exact built-in string');
}
const model = Object.hasOwn(STATE_MODELS, value) ? STATE_MODELS[value] : undefined;
if (!model) throw new TypeError(`unsupported performance-goal review state: ${value}`);
return model;
}

/** Return immutable accessibility semantics for one governed performance-goal review state. */
export function performanceGoalReviewViewModel(state) {
return requireExactState(state);
}

/** Render static Storybook evidence without accepting caller-controlled HR values or identifiers. */
export function performanceGoalReviewStateMarkup(state) {
const model = requireExactState(state);
const disabled = model.submitDisabled ? ' disabled' : '';
return `<section class="performance-goal-review-state" data-figma-node-id="1:64" data-interaction-state="${model.interactionState}" aria-busy="${model.ariaBusy}">\n <p class="performance-goal-review-status" role="${model.role}" aria-live="${model.ariaLive}"><strong>${model.label}</strong><span>${model.message}</span></p>\n <p class="performance-goal-review-next-action"><strong>Next action</strong><span>${model.nextAction}</span></p>\n <button class="performance-goal-review-submit" type="button"${disabled}>${model.actionLabel}</button>\n</section>`;
}
26 changes: 26 additions & 0 deletions apps/hr-workspace/performance-goal-review-state.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { performanceGoalReviewStateMarkup } from './performance-goal-review-state.js';
import './performance-goal-review-state.css';

export default {
title: 'HR Workspace/Performance Goal Review States',
parameters: {
design: {
type: 'figma',
url: 'Orgmetra Baseline — Storybook Inventory node 1:64',
},
},
};

function story(state) {
return () => performanceGoalReviewStateMarkup(state);
}

export const Idle = story('idle');
export const Loading = story('loading');
export const HumanReview = story('review');
export const Recording = story('recording');
export const RecordedReadOnly = story('recorded');
export const PermissionDenied = story('denied');
export const StaleEvidence = story('stale');
export const ActivationBlocked = story('activationBlocked');
export const Error = story('error');
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# HR Workspace performance-goal review accessibility references

Status: active-PR evidence for the dependency-first HR Workspace performance-goal review interaction. This document does not claim protected-main integration or release authorization.

## Primary standards

World Wide Web Consortium. (2023). *Web Content Accessibility Guidelines (WCAG) 2.2*. https://www.w3.org/TR/WCAG22/

World Wide Web Consortium. (2023). *Accessible Rich Internet Applications (WAI-ARIA) 1.2*. https://www.w3.org/TR/wai-aria-1.2/

## Applied contract

- In-flight load/record operations expose `aria-busy="true"` and disable duplicate action submission.
- Denied, stale, blocked, and error states use assertive alert semantics and always explain the next safe action.
- Interactive controls preserve the existing Orgmetra `:focus-visible` token and a minimum 44px target height.
- Human review is visually and semantically separated from authoritative activation. A recorded review is read-only evidence and cannot be interpreted as a performance rating, compensation action, or employment decision.
- Storybook evidence remains correlated to Figma `Orgmetra Baseline`, Storybook Inventory node `1:64`, whose required states are default / hover / focus / disabled / loading / validation-error / read-only / high-risk-confirmation.
19 changes: 19 additions & 0 deletions docs/traceability/hr-workspace-performance-goal-review-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# HR Workspace performance-goal review state traceability

Status: active stacked PR only; not protected-main truth and not release authorization.

| Need / risk | Owner boundary | Executable evidence |
| --- | --- | --- |
| Shared protected-read interaction semantics | #130 | Existing HR Workspace design tokens, focus treatment, loading/read-only/error patterns |
| Human-reviewed goal-plan governance evidence | #92 | Separate backend authority; this UI does not import or duplicate the unmerged implementation |
| Authoritative goal-plan activation | #121 | Separate backend authority; this UI never activates a plan |
| Durable goal-plan persistence | #125 | Separate persistence boundary; this UI stores no HR truth |
| Figma / Storybook interaction-state inventory | Figma `Orgmetra Baseline`, node `1:64` | Storybook stories and `data-figma-node-id="1:64"` correlation |
| Prevent review → activation/rating/compensation/employment-decision confusion | This PR | `review` and `recorded` state regressions explicitly deny those authorities |
| Prevent stale or inconsistent scope from becoming consequential action | This PR | `stale` and `activationBlocked` states require fresh authoritative evidence and governed owner resolution |
| Minimize UI-state evidence | This PR | View-model key allowlist excludes identifiers, goal text/value, ratings, compensation, assessments, credentials, prompts, and model output |
| Accessibility and actionable failure recovery | This PR | WCAG 2.2 / WAI-ARIA 1.2 doctoring, Storybook state inventory, exact 100% line/branch/function focused gate |

## Dependency-first integration

Base this slice on #130. Process #53 → #130 first. After the parent actually integrates, retarget/revalidate this child against fresh `develop`, reconcile intervening HR Workspace and performance-goal changes, and rerun every applicable browser/accessibility/Foundation/Recovery/SAST/Security and central required workflow on one exact resulting head. Parent/backend checks and reviews never transfer.
Loading
Loading