fix: block renaming main project variant; dynamic page titles

- Add ErrCannotRenameMainVariant; ProjectService.Update now returns
  this error if the caller tries to change the Variant of a main
  project (empty Variant) — ensures there is always exactly one main
- Handle ErrCannotRenameMainVariant in PUT /api/projects/:uuid with 400
- Set document.title dynamically from breadcrumb data:
  - Configurator: "CODE / variant / Config name — QuoteForge"
  - Project detail: "CODE / variant — QuoteForge"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-03-24 17:29:02 +03:00
parent a5dafd37d3
commit f448111e77
3 changed files with 9 additions and 1 deletions

View File

@@ -21,6 +21,7 @@ var (
ErrProjectCodeExists = errors.New("project code and variant already exist")
ErrCannotDeleteMainVariant = errors.New("cannot delete main variant")
ErrReservedMainVariant = errors.New("variant name 'main' is reserved")
ErrCannotRenameMainVariant = errors.New("cannot rename main variant")
)
type ProjectService struct {
@@ -108,7 +109,12 @@ func (s *ProjectService) Update(projectUUID, ownerUsername string, req *UpdatePr
localProject.Code = code
}
if req.Variant != nil {
localProject.Variant = strings.TrimSpace(*req.Variant)
newVariant := strings.TrimSpace(*req.Variant)
// Block renaming of the main variant (empty Variant) — there must always be a main.
if strings.TrimSpace(localProject.Variant) == "" && newVariant != "" {
return nil, ErrCannotRenameMainVariant
}
localProject.Variant = newVariant
if err := validateProjectVariantName(localProject.Variant); err != nil {
return nil, err
}

View File

@@ -477,6 +477,7 @@ function updateConfigBreadcrumbs() {
configEl.textContent = truncateBreadcrumbSpecName(fullConfigName);
configEl.title = fullConfigName;
versionEl.textContent = 'main';
document.title = code + ' / ' + variant + ' / ' + fullConfigName + ' — QuoteForge';
const configNameLinkEl = document.getElementById('breadcrumb-config-name-link');
if (configNameLinkEl && configUUID) {
configNameLinkEl.href = '/configs/' + configUUID + '/revisions';

View File

@@ -363,6 +363,7 @@ function renderVariantSelect() {
if (item.uuid === projectUUID) {
option.className += ' font-semibold text-gray-900';
label.textContent = variantLabel;
document.title = (project && project.code ? project.code : '—') + ' / ' + variantLabel + ' — QuoteForge';
}
option.textContent = variantLabel;
option.onclick = function() {