<feed xmlns='http://www.w3.org/2005/Atom'>
<title>progmaweb/apps, branch feature/durable-account-store</title>
<subtitle>The site and account service of Progmasoft: a Next.js frontend and an ASP.NET Core API.</subtitle>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/'/>
<entry>
<title>Keep accounts and sessions in PostgreSQL</title>
<updated>2026-10-06T19:53:46+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-06T19:53:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=1c986ff99b3c8f349e4a63c0e922276ddf7dc836'/>
<id>1c986ff99b3c8f349e4a63c0e922276ddf7dc836</id>
<content type='text'>
The account API kept everything in the memory of its process, so every
restart and every deployment of the API deleted all accounts and signed
everyone out.

PostgresAccountStore implements IAccountStore on PostgreSQL through Npgsql.
The three uniqueness rules are unique constraints, so two requests that race
for one name, address or Google identity are decided by the database. A
refused account is an ordinary answer, not an exception, and the rules are
reported in the order the in-memory store reports them. A session refers to
its account, and a session of an account that is not stored is refused; the
in-memory store now refuses it as well.

The schema is a list of numbered steps. The service takes the missing ones
when it starts, in one transaction under an advisory lock, and does not
start when the database cannot be reached or is newer than the release.
/health answers 503 while the database does not answer.

ConnectionStrings:Accounts selects PostgreSQL. A production service that is
given no database does not start; keeping accounts in memory there has to
be asked for with Accounts:Store=Memory. Development and the tests keep the
in-memory store by default.

The store gains RemoveExpiredSessionsAsync, and a background service calls
it at the start and once an hour: a session whose token is never presented
again no longer keeps its row for ever.

PostgreSQL keeps a timestamp to the microsecond. The store cuts a moment to
that precision and returns what it stored, and the service uses the
returned account, so the creation time in the registration response equals
the one every later read returns.

Tests. AccountStoreContractTests states the rules once and runs them on
both stores. Further tests cover the schema steps applied concurrently and a
database that is too new, the choice of the store from the configuration,
the removal of expired sessions, and an account with its session surviving
a restart of the service over HTTP. The PostgreSQL tests read their
database from PROGMAWEB_TEST_POSTGRES and work in a schema of their own
each. Without the variable they are skipped; CI starts PostgreSQL 16 for
them, and a test fails the run when the variable is missing there.

Operations. ops/README.md describes the database: the Unix socket with peer
authentication, so that no password exists, the one-time preparation, and
rollback across a schema step. The service unit starts after PostgreSQL.
docs/LIMITATIONS.md no longer lists the in-memory store and now lists the
missing backup and the missing deletion of accounts.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The account API kept everything in the memory of its process, so every
restart and every deployment of the API deleted all accounts and signed
everyone out.

PostgresAccountStore implements IAccountStore on PostgreSQL through Npgsql.
The three uniqueness rules are unique constraints, so two requests that race
for one name, address or Google identity are decided by the database. A
refused account is an ordinary answer, not an exception, and the rules are
reported in the order the in-memory store reports them. A session refers to
its account, and a session of an account that is not stored is refused; the
in-memory store now refuses it as well.

The schema is a list of numbered steps. The service takes the missing ones
when it starts, in one transaction under an advisory lock, and does not
start when the database cannot be reached or is newer than the release.
/health answers 503 while the database does not answer.

ConnectionStrings:Accounts selects PostgreSQL. A production service that is
given no database does not start; keeping accounts in memory there has to
be asked for with Accounts:Store=Memory. Development and the tests keep the
in-memory store by default.

The store gains RemoveExpiredSessionsAsync, and a background service calls
it at the start and once an hour: a session whose token is never presented
again no longer keeps its row for ever.

PostgreSQL keeps a timestamp to the microsecond. The store cuts a moment to
that precision and returns what it stored, and the service uses the
returned account, so the creation time in the registration response equals
the one every later read returns.

Tests. AccountStoreContractTests states the rules once and runs them on
both stores. Further tests cover the schema steps applied concurrently and a
database that is too new, the choice of the store from the configuration,
the removal of expired sessions, and an account with its session surviving
a restart of the service over HTTP. The PostgreSQL tests read their
database from PROGMAWEB_TEST_POSTGRES and work in a schema of their own
each. Without the variable they are skipped; CI starts PostgreSQL 16 for
them, and a test fails the run when the variable is missing there.

Operations. ops/README.md describes the database: the Unix socket with peer
authentication, so that no password exists, the one-time preparation, and
rollback across a schema step. The service unit starts after PostgreSQL.
docs/LIMITATIONS.md no longer lists the in-memory store and now lists the
missing backup and the missing deletion of accounts.
</pre>
</div>
</content>
</entry>
<entry>
<title>Merge remote-tracking branch 'origin/main' into fix/api-without-google</title>
<updated>2026-10-03T15:46:59+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-03T15:46:59+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=b84a2216c26cc5392d0dc300b7a605ccb288d8c8'/>
<id>b84a2216c26cc5392d0dc300b7a605ccb288d8c8</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
<entry>
<title>Run the account API without a Google client</title>
<updated>2026-10-03T15:46:35+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-03T15:46:35+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=ec774db15976385a6d5ea6f48d3622fe507d3843'/>
<id>ec774db15976385a6d5ea6f48d3622fe507d3843</id>
<content type='text'>
With an empty Google client identifier or secret the API answered every
request with 500, including registration, password sign-in and the status
route. The Google scheme was always registered, and the authentication
middleware initializes every remote scheme for each request; with empty
credentials the validation of the Google options threw there, before any
endpoint ran. The API could not be started locally without credentials, and
the "Google sign-in is unavailable" answer of the start endpoint was never
reached.

The Google scheme is now registered only when both values are configured.
Without it the service runs normally, the start endpoint answers 503, the
completion endpoint redirects to the failed sign-in page without a session,
and the callback path is not a route. With both values nothing changes.

The test factory passes the two values as host settings. The program makes
the decision while it registers services, and configuration sources a test
factory adds are applied only when the host is built, after that point.

New tests run the service without a Google client: the ordinary routes,
a full register, sign-out and sign-in round trip, and the three Google
paths. They failed with 500 against the previous program and pass now, also
when the environment of the test run carries Google credentials.

The guides no longer list this as a limitation or ask for placeholder
credentials.

Deploying this change restarts the API, which deletes every account while
the store is in memory.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
With an empty Google client identifier or secret the API answered every
request with 500, including registration, password sign-in and the status
route. The Google scheme was always registered, and the authentication
middleware initializes every remote scheme for each request; with empty
credentials the validation of the Google options threw there, before any
endpoint ran. The API could not be started locally without credentials, and
the "Google sign-in is unavailable" answer of the start endpoint was never
reached.

The Google scheme is now registered only when both values are configured.
Without it the service runs normally, the start endpoint answers 503, the
completion endpoint redirects to the failed sign-in page without a session,
and the callback path is not a route. With both values nothing changes.

The test factory passes the two values as host settings. The program makes
the decision while it registers services, and configuration sources a test
factory adds are applied only when the host is built, after that point.

New tests run the service without a Google client: the ordinary routes,
a full register, sign-out and sign-in round trip, and the three Google
paths. They failed with 500 against the previous program and pass now, also
when the environment of the test run carries Google credentials.

The guides no longer list this as a limitation or ask for placeholder
credentials.

Deploying this change restarts the API, which deletes every account while
the store is in memory.
</pre>
</div>
</content>
</entry>
<entry>
<title>Require documentation for every item of the API reference</title>
<updated>2026-10-03T15:27:22+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-03T15:27:22+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=b1cf9a3bea0c81c1cb3ed2e0ddce2b9a56e175ef'/>
<id>b1cf9a3bea0c81c1cb3ed2e0ddce2b9a56e175ef</id>
<content type='text'>
The DocFX gate only failed on a broken link or an unresolved reference.
DocFX does not report a declaration without a comment, and the API had
none, so the gate passed over a reference with no descriptions at all.

The gate now has three parts:

- DocFX still builds the reference with warnings as errors.
- The API project compiles its XML documentation. With warnings as errors
  already on, an undocumented public declaration or a wrong cref now fails
  the build.
- docs/api/CheckDocumentation.cs reads the metadata DocFX generated and
  fails when a namespace, type or member has no summary. This covers
  internal and private members, which the compiler does not check. It runs
  as a file-based .NET program, so the gate needs no tool beyond the SDK.
  It refuses to pass when the metadata is missing or defines nothing.

To satisfy the gate, every declaration of apps/api has documentation
comments, 131 reference items in all, and the two namespaces are described
by overwrite files in docs/api/namespaces. Only comment lines were added to
the C# sources; no statement changed.

docs/api/filterConfig.yml leaves generator-written code out of the
reference: the generated regular-expression namespace, and the one partial
method whose generated half replaces the comment of its declaration.

Verified locally: the solution builds with no warnings, the 23 API tests
pass, DocFX builds with no warnings and the coverage check passes. Removing
one summary and one namespace file made the check fail and name both.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The DocFX gate only failed on a broken link or an unresolved reference.
DocFX does not report a declaration without a comment, and the API had
none, so the gate passed over a reference with no descriptions at all.

The gate now has three parts:

- DocFX still builds the reference with warnings as errors.
- The API project compiles its XML documentation. With warnings as errors
  already on, an undocumented public declaration or a wrong cref now fails
  the build.
- docs/api/CheckDocumentation.cs reads the metadata DocFX generated and
  fails when a namespace, type or member has no summary. This covers
  internal and private members, which the compiler does not check. It runs
  as a file-based .NET program, so the gate needs no tool beyond the SDK.
  It refuses to pass when the metadata is missing or defines nothing.

To satisfy the gate, every declaration of apps/api has documentation
comments, 131 reference items in all, and the two namespaces are described
by overwrite files in docs/api/namespaces. Only comment lines were added to
the C# sources; no statement changed.

docs/api/filterConfig.yml leaves generator-written code out of the
reference: the generated regular-expression namespace, and the one partial
method whose generated half replaces the comment of its declaration.

Verified locally: the solution builds with no warnings, the 23 API tests
pass, DocFX builds with no warnings and the coverage check passes. Removing
one summary and one namespace file made the check fail and name both.
</pre>
</div>
</content>
</entry>
<entry>
<title>Change which AI crawlers may read the site</title>
<updated>2026-10-03T13:34:18+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-03T13:34:18+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=4c803fe10d3120a7dc7f594a1fb2eb205eca34fc'/>
<id>4c803fe10d3120a7dc7f594a1fb2eb205eca34fc</id>
<content type='text'>
- Google-Extended, Google's token for model training, is now allowed.
- GPTBot, OpenAI's training crawler, is now disallowed. OAI-SearchBot and
  ChatGPT-User, which index and fetch pages for answers, stay allowed.
- ClaudeBot, Anthropic's training crawler, is now allowed, next to
  Claude-SearchBot and Claude-User, which already were.

Every other rule is unchanged.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
- Google-Extended, Google's token for model training, is now allowed.
- GPTBot, OpenAI's training crawler, is now disallowed. OAI-SearchBot and
  ChatGPT-User, which index and fetch pages for answers, stay allowed.
- ClaudeBot, Anthropic's training crawler, is now allowed, next to
  Claude-SearchBot and Claude-User, which already were.

Every other rule is unchanged.
</pre>
</div>
</content>
</entry>
<entry>
<title>Revise the Hebrew wording</title>
<updated>2026-10-03T10:38:29+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-03T10:38:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=ef8875267978042cf91a16062630ec583cac8eba'/>
<id>ef8875267978042cf91a16062630ec583cac8eba</id>
<content type='text'>
A second pass over the Hebrew text replaced words a Hebrew reader would
misread or that are not used for the concept:

- "native" was rendered with the word for "local"; it is now machine code
  for output and the Latin term for the native parts of the toolchain.
- Typed boundaries, the ecosystem, the dedicated account area and the
  first-party infrastructure use the usual terms instead of literal ones.
- The greetings and the new-visitor prompt no longer address one gender.

The text is still not reviewed by a native speaker.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
A second pass over the Hebrew text replaced words a Hebrew reader would
misread or that are not used for the concept:

- "native" was rendered with the word for "local"; it is now machine code
  for output and the Latin term for the native parts of the toolchain.
- Typed boundaries, the ecosystem, the dedicated account area and the
  first-party infrastructure use the usual terms instead of literal ones.
- The greetings and the new-visitor prompt no longer address one gender.

The text is still not reviewed by a native speaker.
</pre>
</div>
</content>
</entry>
<entry>
<title>Add Hebrew and follow the browser language by default</title>
<updated>2026-10-03T10:00:20+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-03T10:00:20+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=5a321c2eb1c691bdb7cc96bbab1e28b88ef92aab'/>
<id>5a321c2eb1c691bdb7cc96bbab1e28b88ef92aab</id>
<content type='text'>
Hebrew (he-IL) joins English, German and Russian with a complete message
tree and its own entry in the language menu.

A visitor who has not chosen a language now gets the language their browser
prefers. The Accept-Language header is negotiated by quality, only the
primary language is compared, and `iw` is accepted as the former code for
Hebrew. A browser that prefers no published language gets en-US, as before.
A language chosen in the menu is still stored in the cookie and always wins.

Hebrew does not mirror the page. The layout, the navigation and code keep
their left-to-right arrangement; only text follows the language. Blocks of
prose run right to left, short pieces such as links and buttons take the
direction of their own first letter, and inputs and code stay left to right.
The onward arrow points left in Hebrew, and a left-to-right mark keeps
"Visual X#" intact inside right-to-left text.

The Hebrew text was not reviewed by a native speaker.

Verified locally: node tests, type check, lint, prettier and a production
build; language selection per Accept-Language header against the dev server;
the Hebrew home and registration pages in a browser.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Hebrew (he-IL) joins English, German and Russian with a complete message
tree and its own entry in the language menu.

A visitor who has not chosen a language now gets the language their browser
prefers. The Accept-Language header is negotiated by quality, only the
primary language is compared, and `iw` is accepted as the former code for
Hebrew. A browser that prefers no published language gets en-US, as before.
A language chosen in the menu is still stored in the cookie and always wins.

Hebrew does not mirror the page. The layout, the navigation and code keep
their left-to-right arrangement; only text follows the language. Blocks of
prose run right to left, short pieces such as links and buttons take the
direction of their own first letter, and inputs and code stay left to right.
The onward arrow points left in Hebrew, and a left-to-right mark keeps
"Visual X#" intact inside right-to-left text.

The Hebrew text was not reviewed by a native speaker.

Verified locally: node tests, type check, lint, prettier and a production
build; language selection per Accept-Language header against the dev server;
the Hebrew home and registration pages in a browser.
</pre>
</div>
</content>
</entry>
<entry>
<title>Update Next.js and override brace-expansion for security advisories</title>
<updated>2026-10-02T22:50:07+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-02T22:50:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=209da8ed364ae406e91c0d92db64b3a520c72ef9'/>
<id>209da8ed364ae406e91c0d92db64b3a520c72ef9</id>
<content type='text'>
The dependency audit reports a critical Next.js advisory for releases before
16.3.6 (remote code execution in next/og ImageResponse) and high-severity
denial-of-service advisories for brace-expansion before 5.0.12. The site does
not import next/og, but production runs the affected Next.js release.

- next and eslint-config-next: 16.3.3 to 16.3.8.
- brace-expansion: the lockfile resolves 1.1.21 within its range; the 5.x
  line, which minimatch pins to 5.0.9, is overridden to 5.0.12 in the
  workspace until minimatch depends on a fixed release.

pnpm audit reports no known vulnerabilities. Prettier, eslint, the type
check, the tests and the production build pass.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The dependency audit reports a critical Next.js advisory for releases before
16.3.6 (remote code execution in next/og ImageResponse) and high-severity
denial-of-service advisories for brace-expansion before 5.0.12. The site does
not import next/og, but production runs the affected Next.js release.

- next and eslint-config-next: 16.3.3 to 16.3.8.
- brace-expansion: the lockfile resolves 1.1.21 within its range; the 5.x
  line, which minimatch pins to 5.0.9, is overridden to 5.0.12 in the
  workspace until minimatch depends on a fixed release.

pnpm audit reports no known vulnerabilities. Prettier, eslint, the type
check, the tests and the production build pass.
</pre>
</div>
</content>
</entry>
<entry>
<title>Switch language in place and add Russian</title>
<updated>2026-10-02T21:20:50+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-10-02T21:20:50+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=35158de28f4a0dfc300f2d3b5787d2064a06105c'/>
<id>35158de28f4a0dfc300f2d3b5787d2064a06105c</id>
<content type='text'>
The language control no longer reloads the page. It calls a server action
that stores the preference cookie; Next.js then renders the current route
again and applies it in place, so scroll position, theme and client state
stay. The lang query parameter remains for links and for browsers without
script, and is the fallback when the action cannot reach the server. Both
paths write the cookie with the same attributes from one function.

The toggle button is now a language menu, a native select that lists each
language by its own name, and Russian (ru-RU) is the third locale with a
complete translation of every message.

Tests require every locale to have exactly the message shape of the default
locale with no empty text, and cover the cookie attributes and host
normalization. Verified locally: prettier, eslint, type check, tests, the
production build, and a browser run on the apex and ViGet hosts where the
language changed with no navigation and the scroll position kept.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The language control no longer reloads the page. It calls a server action
that stores the preference cookie; Next.js then renders the current route
again and applies it in place, so scroll position, theme and client state
stay. The lang query parameter remains for links and for browsers without
script, and is the fallback when the action cannot reach the server. Both
paths write the cookie with the same attributes from one function.

The toggle button is now a language menu, a native select that lists each
language by its own name, and Russian (ru-RU) is the third locale with a
complete translation of every message.

Tests require every locale to have exactly the message shape of the default
locale with no empty text, and cover the cookie attributes and host
normalization. Verified locally: prettier, eslint, type check, tests, the
production build, and a browser run on the apex and ViGet hosts where the
language changed with no navigation and the scroll position kept.
</pre>
</div>
</content>
</entry>
<entry>
<title>Harden Progmaweb account API and CI coverage credentials</title>
<updated>2026-09-27T22:10:33+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-09-27T22:10:33+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/progmaweb/commit/?id=bcf10c927358157bedf6ab09ff6bff0a8aa86a14'/>
<id>bcf10c927358157bedf6ab09ff6bff0a8aa86a14</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
</feed>
