Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/frontend
diff options
context:
space:
mode:
authorLeitwolf112026-09-07 21:30:40 +0300
committerLeitwolf112026-09-07 21:30:40 +0300
commitc0cd5f4699e44899956c19176ca47ede27a21a8f (patch)
treedc10ba78fde4c4dfaa91f4d43ffaee7bcb6c78b7 /frontend
parente77f4585d3a1be272fe83d5a06b1d2a32fa8f7b5 (diff)
downloadwebsite-c0cd5f4699e44899956c19176ca47ede27a21a8f.tar.gz
Separate ViGet from Progmasoft Account infrastructure
Move the Progmasoft mail operations baseline to its owning Progmaweb repository, remove the retired Astro account UI and duplicate identity backend, keep only the ViGet health boundary, add canonical sitemap signals, strengthen dependency CI, and adopt Patent Grant 1.1 across project-owned files.
Diffstat (limited to 'frontend')
-rw-r--r--frontend/.npmrc6
-rw-r--r--frontend/astro.config.mjs2
-rw-r--r--frontend/package-lock.json26
-rw-r--r--frontend/package.json4
-rw-r--r--frontend/public/robots.txt2
-rw-r--r--frontend/public/scripts/auth-flow.v3.js183
-rw-r--r--frontend/public/scripts/dashboard.v1.js146
-rw-r--r--frontend/public/sitemap.xml10
-rw-r--r--frontend/public/viget-robots.txt7
-rw-r--r--frontend/public/viget-sitemap.xml15
-rw-r--r--frontend/src/components/AuthPanel.astro178
-rw-r--r--frontend/src/components/Icon.astro2
-rw-r--r--frontend/src/components/RepositoryFooter.astro2
-rw-r--r--frontend/src/components/RepositoryHeader.astro2
-rw-r--r--frontend/src/layouts/SiteLayout.astro7
-rw-r--r--frontend/src/pages/index.astro3
-rw-r--r--frontend/src/pages/repo/dashboard/index.astro169
-rw-r--r--frontend/src/pages/repo/dslplugins/index.astro3
-rw-r--r--frontend/src/pages/repo/index.astro3
-rw-r--r--frontend/src/pages/repo/login/index.astro15
-rw-r--r--frontend/src/pages/repo/recover/index.astro15
-rw-r--r--frontend/src/pages/repo/register/index.astro15
-rw-r--r--frontend/src/styles/global.css603
-rw-r--r--frontend/src/styles/registry.css2
24 files changed, 73 insertions, 1347 deletions
diff --git a/frontend/.npmrc b/frontend/.npmrc
new file mode 100644
index 0000000..52be3f7
--- /dev/null
+++ b/frontend/.npmrc
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
+
+# Fail closed when a new transitive dependency introduces an unreviewed install
+# script. esbuild's exact reviewed version is pinned in package.json.
+strict-allow-scripts=true
diff --git a/frontend/astro.config.mjs b/frontend/astro.config.mjs
index 899b8b5..cadea29 100644
--- a/frontend/astro.config.mjs
+++ b/frontend/astro.config.mjs
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.0
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
import { defineConfig } from "astro/config";
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index ce598f2..14113a1 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -2992,9 +2992,9 @@
}
},
"node_modules/fast-uri": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
- "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
+ "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
"dev": true,
"funding": [
{
@@ -3299,9 +3299,9 @@
}
},
"node_modules/js-yaml": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
- "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"funding": [
{
"type": "github",
@@ -3778,9 +3778,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.16",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
- "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"funding": [
{
"type": "github",
@@ -3990,9 +3990,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.21",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.21.tgz",
- "integrity": "sha512-v4sDNP3fdNiWMfabO7OwOQdOX8TiQSztKyT1Wj0w+j7LDallJThJRBBBmzVGyYj0crMh7jlV4zepPkiNu9UwDQ==",
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"funding": [
{
"type": "opencollective",
@@ -4009,7 +4009,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.16",
+ "nanoid": "^3.3.18",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
diff --git a/frontend/package.json b/frontend/package.json
index b713669..e1b952e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -21,5 +21,9 @@
"engines": {
"node": ">=22.12.0",
"npm": ">=9.6.5"
+ },
+ "allowScripts": {
+ "esbuild@0.28.1": true,
+ "fsevents@2.3.3": true
}
}
diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt
index 929fb34..9bde1d2 100644
--- a/frontend/public/robots.txt
+++ b/frontend/public/robots.txt
@@ -1,5 +1,5 @@
# SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.0
+# SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
User-agent: Googlebot
Allow: /
diff --git a/frontend/public/scripts/auth-flow.v3.js b/frontend/public/scripts/auth-flow.v3.js
deleted file mode 100644
index 5570e8b..0000000
--- a/frontend/public/scripts/auth-flow.v3.js
+++ /dev/null
@@ -1,183 +0,0 @@
-// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.0
-
-// Versioned because the registry serves this security-sensitive flow through a cache.
-const root = document.querySelector("[data-auth-flow]");
-const form = root?.querySelector("[data-auth-form]");
-const emailInput = root?.querySelector("#auth-email");
-const passwordInput = root?.querySelector("#auth-password");
-const publisherNameInput = root?.querySelector("#auth-publisher-name");
-const codeInput = root?.querySelector("#auth-code");
-const newPasswordInput = root?.querySelector("#auth-new-password");
-const alert = root?.querySelector("[data-auth-alert]");
-const mode = root?.dataset.mode;
-const googleButton = root?.querySelector("[data-google]");
-const publisherBack = root?.querySelector("[data-publisher-back]");
-let step = "email";
-let email = "";
-let registrationPassword = "";
-let publisherName = "";
-let googleRegistration = false;
-let recoveryCode = "";
-
-function showAlert(message, error = false) {
- if (!alert) return;
- alert.textContent = message;
- alert.dataset.kind = error ? "error" : "info";
- alert.hidden = false;
-}
-
-function showStep(next) {
- step = next;
- root?.querySelectorAll("[data-step]").forEach((element) => {
- element.hidden = element.dataset.step !== next;
- });
- root?.querySelectorAll("[data-email-value], [data-code-email]").forEach((element) => {
- element.textContent = email;
- });
- alert?.setAttribute("hidden", "");
- root?.querySelector(`[data-step="${next}"] input`)?.focus();
-}
-
-async function request(path, body) {
- return fetch(`/api/v1/auth/${path}`, {
- method: body ? "POST" : "GET",
- credentials: "include",
- headers: body ? { "Content-Type": "application/json" } : undefined,
- body: body ? JSON.stringify(body) : undefined,
- });
-}
-
-async function explainFailure(response) {
- let failure = {};
- try {
- failure = await response.json();
- } catch {
- // An upstream error page is intentionally replaced with a stable message.
- }
- showAlert(failure.message ?? failure.errors?.join(" ") ?? "The request could not be completed.", true);
-}
-
-root?.querySelector("[data-back]")?.addEventListener("click", () => showStep("email"));
-publisherBack?.addEventListener("click", () => showStep("password"));
-root?.querySelector("[data-resend]")?.addEventListener("click", async () => {
- const path = mode === "recover" ? "recovery/start" : "verify-email/resend";
- const response = await request(path, { email });
- if (!response.ok) return explainFailure(response);
- showAlert("A new code has been requested. Check your email.");
-});
-
-googleButton?.addEventListener("click", async () => {
- window.location.assign("/api/v1/auth/google");
-});
-
-async function initializeProviders() {
- if (!googleButton) return;
- try {
- const response = await request("providers");
- const providers = await response.json();
- googleButton.disabled = !providers.google;
- if (!providers.google) googleButton.title = "Google sign-in is being configured.";
- } catch {
- googleButton.disabled = true;
- }
-}
-
-function initializeGoogleVerification() {
- const parameters = new URLSearchParams(window.location.search);
- const requestedEmail = parameters.get("email")?.trim().toLowerCase() ?? "";
- if (mode !== "register" || parameters.get("verify") !== "google" || !requestedEmail || !emailInput) {
- return;
- }
- emailInput.value = requestedEmail;
- if (!emailInput.checkValidity()) return;
- email = requestedEmail;
- googleRegistration = true;
- if (publisherBack) publisherBack.hidden = true;
- showStep("publisher");
- showAlert("Google sign-in succeeded. Choose your username to continue account verification.");
- window.history.replaceState({}, "", window.location.pathname);
-}
-
-form?.addEventListener("submit", async (event) => {
- event.preventDefault();
- if (!mode) return;
-
- if (step === "email") {
- email = emailInput?.value.trim().toLowerCase() ?? "";
- if (!emailInput?.checkValidity()) {
- emailInput?.reportValidity();
- return;
- }
- if (mode === "recover") {
- const response = await request("recovery/start", { email });
- if (!response.ok) return explainFailure(response);
- showStep("code");
- showAlert("Check your email for the recovery code.");
- return;
- }
- showStep("password");
- return;
- }
-
- if (step === "password") {
- const password = passwordInput?.value ?? "";
- if (!passwordInput?.checkValidity()) {
- passwordInput?.reportValidity();
- return;
- }
- if (mode === "register") {
- registrationPassword = password;
- showStep("publisher");
- return;
- }
- const response = await request("login", { email, password });
- if (!response.ok) return explainFailure(response);
- window.location.assign("https://account.progmasoft.com/");
- return;
- }
-
- if (step === "publisher") {
- publisherName = publisherNameInput?.value ?? "";
- if (!publisherNameInput?.checkValidity()) {
- publisherNameInput?.reportValidity();
- return;
- }
- if (!googleRegistration) {
- const response = await request("register", { email, password: registrationPassword, publisherName });
- if (!response.ok) return explainFailure(response);
- }
- showStep("code");
- showAlert("Check your email to verify this account.");
- return;
- }
-
- if (step === "code") {
- const code = codeInput?.value.trim().toUpperCase() ?? "";
- if (!codeInput?.checkValidity()) {
- codeInput?.reportValidity();
- return;
- }
- if (mode === "recover") {
- recoveryCode = code;
- showStep("new-password");
- return;
- }
- const response = await request("verify-email", { email, code, publisherName });
- if (!response.ok) return explainFailure(response);
- window.location.assign("https://account.progmasoft.com/");
- return;
- }
-
- const password = newPasswordInput?.value ?? "";
- if (!newPasswordInput?.checkValidity()) {
- newPasswordInput?.reportValidity();
- return;
- }
- const response = await request("recovery/complete", { email, code: recoveryCode, password });
- if (!response.ok) return explainFailure(response);
- window.location.assign("https://account.progmasoft.com/");
-});
-
-initializeGoogleVerification();
-void initializeProviders();
diff --git a/frontend/public/scripts/dashboard.v1.js b/frontend/public/scripts/dashboard.v1.js
deleted file mode 100644
index 9b3ce97..0000000
--- a/frontend/public/scripts/dashboard.v1.js
+++ /dev/null
@@ -1,146 +0,0 @@
-// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.0
-
-const root = document.querySelector("[data-dashboard]");
-const alert = root?.querySelector("[data-dashboard-alert]");
-const tokenList = root?.querySelector("[data-token-list]");
-const tokenCount = root?.querySelector("[data-token-count]");
-const dialog = document.querySelector("[data-token-dialog]");
-const tokenForm = dialog?.querySelector("[data-token-form]");
-const tokenSecret = dialog?.querySelector("[data-token-secret]");
-const tokenValue = dialog?.querySelector("[data-token-value]");
-const deleteDialog = document.querySelector("[data-delete-dialog]");
-const deleteForm = deleteDialog?.querySelector("[data-delete-form]");
-const deleteConfirmation = deleteDialog?.querySelector("[data-delete-confirmation]");
-let accountEmail = "";
-
-function showAlert(message, error = false) {
- if (!alert) return;
- alert.textContent = message;
- alert.dataset.kind = error ? "error" : "info";
- alert.hidden = false;
-}
-
-function renderTokens(tokens) {
- if (!tokenList) return;
- if (tokenCount) tokenCount.textContent = String(tokens.length);
- if (tokens.length === 0) {
- tokenList.innerHTML = '<p class="token-list-empty">No API tokens have been created.</p>';
- return;
- }
- tokenList.replaceChildren(
- ...tokens.map((token) => {
- const row = document.createElement("article");
- row.className = "token-row";
- const details = document.createElement("div");
- const name = document.createElement("strong");
- name.textContent = token.name;
- const metadata = document.createElement("span");
- metadata.textContent = `${token.prefix}… · ${token.scopes.join(", ")} · created ${new Date(token.createdAt).toLocaleDateString()}`;
- details.append(name, metadata);
- const revoke = document.createElement("button");
- revoke.type = "button";
- revoke.textContent = "Revoke";
- revoke.addEventListener("click", () => revokeToken(token.id));
- row.append(details, revoke);
- return row;
- }),
- );
-}
-
-async function loadTokens() {
- const response = await fetch("/api/v1/tokens/", { credentials: "include" });
- if (!response.ok) {
- showAlert("API tokens could not be loaded.", true);
- return;
- }
- renderTokens(await response.json());
-}
-
-async function revokeToken(id) {
- if (!window.confirm("Revoke this token? Existing CLI sessions using it will stop working.")) return;
- const response = await fetch(`/api/v1/tokens/${id}`, { method: "DELETE", credentials: "include" });
- if (!response.ok) return showAlert("The token could not be revoked.", true);
- await loadTokens();
-}
-
-async function initialize() {
- const response = await fetch("/api/v1/auth/me", { credentials: "include" });
- if (response.status === 401) {
- window.location.replace("https://account.progmasoft.com/login");
- return;
- }
- if (!response.ok) return showAlert("Account information could not be loaded.", true);
- const account = await response.json();
- const email = account.email ?? "";
- accountEmail = email;
- const displayName = account.displayName || email.split("@")[0] || "Account";
- const avatar = root?.querySelector("[data-account-avatar]");
- const name = root?.querySelector("[data-account-name]");
- const emailElement = root?.querySelector("[data-account-email]");
- const status = root?.querySelector("[data-email-status]");
- const deleteEmail = deleteDialog?.querySelector("[data-delete-email]");
- if (avatar) avatar.textContent = displayName.slice(0, 1).toUpperCase();
- if (name) name.textContent = displayName;
- if (emailElement) emailElement.textContent = email;
- if (status) status.textContent = account.emailVerified ? "Verified" : "Pending";
- if (deleteEmail) deleteEmail.textContent = email;
- await loadTokens();
-}
-
-root?.querySelector("[data-signout]")?.addEventListener("click", async () => {
- await fetch("/api/v1/auth/logout", { method: "POST", credentials: "include" });
- window.location.assign("https://account.progmasoft.com/login");
-});
-root?.querySelector("[data-token-open]")?.addEventListener("click", () => dialog?.showModal());
-dialog?.querySelector("[data-token-close]")?.addEventListener("click", () => dialog?.close());
-dialog?.querySelector("[data-token-copy]")?.addEventListener("click", async () => {
- if (tokenValue?.textContent) await navigator.clipboard.writeText(tokenValue.textContent);
-});
-
-root?.querySelector("[data-delete-open]")?.addEventListener("click", () => {
- if (deleteConfirmation) deleteConfirmation.value = "";
- deleteDialog?.showModal();
- deleteConfirmation?.focus();
-});
-deleteDialog?.querySelector("[data-delete-close]")?.addEventListener("click", () => deleteDialog?.close());
-deleteForm?.addEventListener("submit", async (event) => {
- event.preventDefault();
- const confirmation = deleteConfirmation?.value.trim() ?? "";
- if (!confirmation || confirmation.toLowerCase() !== accountEmail.toLowerCase()) {
- showAlert("Enter the account email address exactly to confirm deletion.", true);
- return;
- }
- if (!window.confirm("Permanently delete this registry account? This cannot be undone.")) return;
- const response = await fetch("/api/v1/auth/account", {
- method: "DELETE",
- credentials: "include",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ confirmation }),
- });
- if (!response.ok) {
- showAlert("The account could not be deleted.", true);
- return;
- }
- window.location.replace("https://account.progmasoft.com/?account=deleted");
-});
-
-tokenForm?.addEventListener("submit", async (event) => {
- event.preventDefault();
- const formData = new FormData(tokenForm);
- const name = String(formData.get("name") ?? "").trim();
- const scopes = formData.getAll("scope").map(String);
- const response = await fetch("/api/v1/tokens/", {
- method: "POST",
- credentials: "include",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ name, scopes }),
- });
- if (!response.ok) return showAlert("The token could not be created.", true);
- const created = await response.json();
- if (tokenValue) tokenValue.textContent = created.token;
- if (tokenSecret) tokenSecret.hidden = false;
- await loadTokens();
-});
-
-void initialize();
diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml
new file mode 100644
index 0000000..600af81
--- /dev/null
+++ b/frontend/public/sitemap.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> -->
+<!-- SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 -->
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+ <url>
+ <loc>https://xsharp-lang.xyz/</loc>
+ <changefreq>weekly</changefreq>
+ <priority>1.0</priority>
+ </url>
+</urlset>
diff --git a/frontend/public/viget-robots.txt b/frontend/public/viget-robots.txt
new file mode 100644
index 0000000..4e271f4
--- /dev/null
+++ b/frontend/public/viget-robots.txt
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
+
+User-agent: *
+Allow: /
+
+Sitemap: https://viget.progmasoft.com/sitemap.xml
diff --git a/frontend/public/viget-sitemap.xml b/frontend/public/viget-sitemap.xml
new file mode 100644
index 0000000..03d47e0
--- /dev/null
+++ b/frontend/public/viget-sitemap.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> -->
+<!-- SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 -->
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+ <url>
+ <loc>https://viget.progmasoft.com/</loc>
+ <changefreq>daily</changefreq>
+ <priority>1.0</priority>
+ </url>
+ <url>
+ <loc>https://viget.progmasoft.com/dslplugins/</loc>
+ <changefreq>daily</changefreq>
+ <priority>0.8</priority>
+ </url>
+</urlset>
diff --git a/frontend/src/components/AuthPanel.astro b/frontend/src/components/AuthPanel.astro
deleted file mode 100644
index dd7f46f..0000000
--- a/frontend/src/components/AuthPanel.astro
+++ /dev/null
@@ -1,178 +0,0 @@
----
-// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.0
-
-interface Props {
- mode: "login" | "register" | "recover";
-}
-
-const { mode } = Astro.props;
-const copy = {
- login: {
- title: "Sign in",
- description: "Use your Visual X# registry account.",
- submit: "Next",
- switchText: "New to the registry?",
- switchLabel: "Create an account",
- switchHref: "https://account.progmasoft.com/register",
- },
- register: {
- title: "Create account",
- description: "Publish and manage Visual X# packages.",
- submit: "Next",
- switchText: "Already have an account?",
- switchLabel: "Sign in",
- switchHref: "https://account.progmasoft.com/login",
- },
- recover: {
- title: "Recover account",
- description: "Reset your password securely.",
- submit: "Send code",
- switchText: "Remembered your password?",
- switchLabel: "Sign in",
- switchHref: "https://account.progmasoft.com/login",
- },
-} as const;
-const content = copy[mode];
----
-
-<main class="auth-main" data-auth-flow data-mode={mode}>
- <section class="auth-card glass-panel">
- <a class="auth-brand" href="https://viget.progmasoft.com/" aria-label="Visual X# package registry">
- <img src="/xsharp-logo.webp" width="44" height="44" alt="" />
- <span>Visual X# Registry</span>
- </a>
-
- <div class="auth-heading">
- <p class="eyebrow">Visual X# package registry</p>
- <h1>{content.title}</h1>
- <p>{content.description}</p>
- </div>
-
- <div class="auth-alert" data-auth-alert hidden aria-live="polite"></div>
-
- <button class="oauth-button" type="button" data-google>
- <span class="google-mark" aria-hidden="true">G</span>
- Continue with Google
- </button>
-
- <div class="auth-divider"><span>or continue with email</span></div>
-
- <noscript>
- <p class="auth-alert" data-kind="error">
- JavaScript is required for the secure account flow. Enable it and reload this page.
- </p>
- </noscript>
-
- <form class="auth-form" data-auth-form method="post" novalidate>
- <div class="auth-step" data-step="email">
- <label for="auth-email">Email address</label>
- <input
- id="auth-email"
- name="email"
- type="email"
- autocomplete="email"
- inputmode="email"
- placeholder="name@example.com"
- required
- />
- <button class="auth-submit" type="submit">{content.submit}</button>
- </div>
-
- <div class="auth-step" data-step="password" hidden>
- <button class="auth-back" type="button" data-back>← Change email</button>
- <span class="auth-email-value" data-email-value></span>
- <label for="auth-password">Password</label>
- <input
- id="auth-password"
- name="password"
- type="password"
- autocomplete={mode === "register" ? "new-password" : "current-password"}
- minlength="8"
- required
- />
- {
- mode === "register" && (
- <p class="field-hint">Use at least 8 characters. Passwordless accounts are not created.</p>
- )
- }
- <button class="auth-submit" type="submit">{mode === "register" ? "Create account" : "Sign in"}</button>
- {
- mode === "login" && (
- <a class="auth-recovery" href="https://account.progmasoft.com/recover">Forgot password?</a>
- )
- }
- </div>
-
- {
- mode === "register" && (
- <div class="auth-step" data-step="publisher" hidden>
- <button class="auth-back" type="button" data-publisher-back>← Change password</button>
- <span class="auth-email-value" data-email-value></span>
- <label for="auth-publisher-name">Username</label>
- <input
- id="auth-publisher-name"
- name="publisher-name"
- type="text"
- autocomplete="username"
- autocapitalize="none"
- spellcheck="false"
- pattern="[A-Z][A-Za-z0-9]{7,127}"
- minlength="8"
- maxlength="128"
- placeholder="MyPublisher"
- required
- />
- <p class="field-hint">
- Use 8–128 ASCII letters or digits and start with an uppercase letter. <code>Progmasoft</code> and{" "}
- <code>Leitwolf</code> are reserved.
- </p>
- <button class="auth-submit" type="submit">Continue</button>
- </div>
- )
- }
-
- <div class="auth-step" data-step="code" hidden>
- <span class="auth-email-value" data-code-email></span>
- <label for="auth-code">Verification code</label>
- <input
- id="auth-code"
- name="code"
- type="text"
- autocomplete="one-time-code"
- inputmode="text"
- pattern="[A-Za-z0-9]{8}"
- minlength="8"
- maxlength="8"
- placeholder="Enter the code from your email"
- required
- />
- <p class="field-hint">
- The code is sent by <code>noreply@progmasoft.com</code>. It expires and can only be used once. If it does not
- appear in your inbox, check your spam or junk folder.
- </p>
- <button class="auth-submit" type="submit">Verify email</button>
- <button class="auth-resend" type="button" data-resend>Send another code</button>
- </div>
-
- <div class="auth-step" data-step="new-password" hidden>
- <label for="auth-new-password">New password</label>
- <input
- id="auth-new-password"
- name="new-password"
- type="password"
- autocomplete="new-password"
- minlength="8"
- required
- />
- <button class="auth-submit" type="submit">Set new password</button>
- </div>
- </form>
-
- <p class="auth-switch">
- {content.switchText} <a href={content.switchHref}>{content.switchLabel}</a>
- </p>
- </section>
-</main>
-
-<script is:inline type="module" src="/scripts/auth-flow.v3.js"></script>
diff --git a/frontend/src/components/Icon.astro b/frontend/src/components/Icon.astro
index 9787db2..6231dbb 100644
--- a/frontend/src/components/Icon.astro
+++ b/frontend/src/components/Icon.astro
@@ -1,6 +1,6 @@
---
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.0
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
interface Props {
name:
diff --git a/frontend/src/components/RepositoryFooter.astro b/frontend/src/components/RepositoryFooter.astro
index 58b82af..cdb2c1f 100644
--- a/frontend/src/components/RepositoryFooter.astro
+++ b/frontend/src/components/RepositoryFooter.astro
@@ -1,6 +1,6 @@
---
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.0
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
---
<footer class="registry-footer">
diff --git a/frontend/src/components/RepositoryHeader.astro b/frontend/src/components/RepositoryHeader.astro
index 3fbc8f9..9755869 100644
--- a/frontend/src/components/RepositoryHeader.astro
+++ b/frontend/src/components/RepositoryHeader.astro
@@ -1,6 +1,6 @@
---
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.0
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
interface Props {
authenticated?: boolean;
diff --git a/frontend/src/layouts/SiteLayout.astro b/frontend/src/layouts/SiteLayout.astro
index ee29aae..482038b 100644
--- a/