fix(project-editor): close redo-staleness window, dedupe footer URL check
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Independent review pass over the Configuration Engine sprint (M1-M6). - Facade: a fresh edit burst now clears the redo (future) stack immediately, not just once its debounced commit lands ~300ms later. Previously, editing right after an undo left canRedo() true for that window; clicking Redo during it would have silently discarded the new edit and jumped back to the stale future snapshot. Reordered two interspersed imports/interface for readability while in the file. - footer-section: removed a local HTTP_URL regex + duplicate isValidUrl logic (its "shared/ui can't import features" justification didn't apply - this file already lives in features/project-editor/sections/, the same feature as schema/validators/). Now calls isValidHttpUrl from schema/validators/primitives, closing a validator duplication the sprint's "no duplicated validators" requirement was meant to catch. Gate: tsc --noEmit, npm test (33/33), arch:check, build all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,8 @@ import { ProjectValidator, ProjectValidationIssue } from '../services/project-va
|
|||||||
import { ProjectEditorSectionId } from '../models/project-editor.model';
|
import { ProjectEditorSectionId } from '../models/project-editor.model';
|
||||||
import { EditorSchemaService } from '../schema/editor-schema.service';
|
import { EditorSchemaService } from '../schema/editor-schema.service';
|
||||||
import { History, commit as commitHistory, emptyHistory, redo as redoHistory, undo as undoHistory } from '../schema/history.util';
|
import { History, commit as commitHistory, emptyHistory, redo as redoHistory, undo as undoHistory } from '../schema/history.util';
|
||||||
|
import { ProjectEditorDraftStorageService } from '../services/project-editor-draft-storage.service';
|
||||||
|
import { EDITOR_SECTION_BOOTSTRAP_KEYS } from '../models/project-editor.model';
|
||||||
|
|
||||||
const HISTORY_DEBOUNCE_MS = 300;
|
const HISTORY_DEBOUNCE_MS = 300;
|
||||||
|
|
||||||
@@ -23,8 +25,6 @@ export interface ChangeSummaryRow {
|
|||||||
readonly before: string;
|
readonly before: string;
|
||||||
readonly after: string;
|
readonly after: string;
|
||||||
}
|
}
|
||||||
import { ProjectEditorDraftStorageService } from '../services/project-editor-draft-storage.service';
|
|
||||||
import { EDITOR_SECTION_BOOTSTRAP_KEYS } from '../models/project-editor.model';
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ProjectEditorFacade {
|
export class ProjectEditorFacade {
|
||||||
@@ -195,8 +195,14 @@ export class ProjectEditorFacade {
|
|||||||
|
|
||||||
// Capture the pre-burst snapshot so a run of rapid edits collapses into one
|
// Capture the pre-burst snapshot so a run of rapid edits collapses into one
|
||||||
// undo step. The baseline is cleared when the debounce fires (or on undo).
|
// undo step. The baseline is cleared when the debounce fires (or on undo).
|
||||||
|
// A fresh burst also invalidates any redo stack immediately (not just once
|
||||||
|
// the debounced commit lands) - otherwise Redo would stay enabled for up to
|
||||||
|
// HISTORY_DEBOUNCE_MS after a post-undo edit and silently discard it.
|
||||||
if (this.pendingBaseline === null) {
|
if (this.pendingBaseline === null) {
|
||||||
this.pendingBaseline = current;
|
this.pendingBaseline = current;
|
||||||
|
if (this.history().future.length > 0) {
|
||||||
|
this.history.update(history => ({ ...history, future: [] }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const next = this.normalize(updater(JSON.parse(JSON.stringify(current)) as BootstrapConfig));
|
const next = this.normalize(updater(JSON.parse(JSON.stringify(current)) as BootstrapConfig));
|
||||||
this.state.update(state => ({ ...state, bootstrap: next, draftRestored: false }));
|
this.state.update(state => ({ ...state, bootstrap: next, draftRestored: false }));
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ import { KeyValueEditorComponent } from '../../../shared/ui/key-value-editor/key
|
|||||||
import { MediaPickerComponent } from '../../../shared/media/media-picker/media-picker.component';
|
import { MediaPickerComponent } from '../../../shared/media/media-picker/media-picker.component';
|
||||||
import { MediaAsset } from '../../../core/media/models/media-asset.model';
|
import { MediaAsset } from '../../../core/media/models/media-asset.model';
|
||||||
import { FooterPaymentIconConfig, FooterSocialLinkConfig } from '../../../shared/models/config';
|
import { FooterPaymentIconConfig, FooterSocialLinkConfig } from '../../../shared/models/config';
|
||||||
|
import { isValidHttpUrl } from '../schema/validators/primitives';
|
||||||
/** Mirrors the HTTP_URL check in project-validator.service.ts (kept as a local duplicate: shared/ui must not import from features). */
|
|
||||||
const HTTP_URL = /^https?:\/\/\S+$/;
|
|
||||||
|
|
||||||
type FooterMediaTarget = 'logo' | { type: 'paymentIcon'; index: number };
|
type FooterMediaTarget = 'logo' | { type: 'paymentIcon'; index: number };
|
||||||
|
|
||||||
@@ -118,6 +116,6 @@ export class ProjectEditorFooterSectionComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isValidUrl(url: string): boolean {
|
isValidUrl(url: string): boolean {
|
||||||
return !url || HTTP_URL.test(url);
|
return !url || isValidHttpUrl(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user