gate-38 scopes templates/settings/*.php as if each were an app entry point. Those files are ISettings::getForm() fragments embedded inside core's settings page, which already renders the skip links. Requiring one of them to carry its own is not just unnecessary — satisfying it would be a WCAG regression.
Measured
Full-tree run against ConductionNL/larpingapp@development c73df92d:
[gate-38] skip-link: FAIL — 1 root component(s) without skip-link / <NcContent>
templates/settings/admin.php: no <NcContent> shell, no skip-link affordance
The whole file:
<?php
use OCP\Util;
$appId = OCA\LarpingApp\AppInfo\Application::APP_ID;
Util::addScript($appId, $appId . '-shared-vendor');
Util::addScript($appId, $appId . '-shared-nc-vue');
Util::addScript($appId, $appId . '-settings');
Util::addStyle($appId, 'main');
?>
<div id="settings"></div>
Call path
lib/Settings/LarpingAppAdmin.php:
public function getForm(): TemplateResponse
{
...
return new TemplateResponse( ..., 'settings/admin', ... );
}
registered via appinfo/info.xml:
<settings>
<admin>OCA\LarpingApp\Settings\LarpingAppAdmin</admin>
...
</settings>
Nextcloud renders that response inside /settings/admin, whose layout is core's core/templates/layout.user.php:
<?php if ($_['id-app-content'] !== null) { ?><a href="<?php p($_['id-app-content']); ?>" class="button primary skip-navigation skip-content"><?php p($l->t('Skip to main content')); ?></a><?php } ?>
<?php if ($_['id-app-navigation'] !== null) { ?><a href="<?php p($_['id-app-navigation']); ?>" class="button primary skip-navigation"><?php p($l->t('Skip to navigation of app')); ?></a><?php } ?>
So the bypass links are present on the rendered page, one layer above where the gate can grep — the same "I cannot see it is not it is absent" situation the gate already carves out for <CnAppRoot> two lines earlier in its own source:
# All 18 fleet apps root on CnAppRoot, so this gate reported every one
# of them as shipping no skip link. ... "I cannot see it" is not
# "it is absent", and only the first of those is true here.
if grep -qE '<CnAppRoot\b' "$_f" 2>/dev/null; then return 0; fi
Satisfying it would make accessibility worse
The only way to clear this finding in-app is to add an <a href="#main"> (or similar) into the settings fragment. That link would:
- be the second "Skip to main content" affordance on the page, after core's;
- appear in the middle of the settings body rather than as the first focusable element, which is where SC 2.4.1 requires a bypass link to be;
- point at an anchor the fragment does not own.
That is a decoration added to turn a gate green, which is exactly what the evidence rules forbid. Left unfixed in larpingapp and documented in ConductionNL/larpingapp#289.
Suggested fix
Drop templates/settings/*.php from gate-38's scope, or gate it on the file actually being a full document (e.g. it emits <html/<body, or is not referenced from an ISettings/TemplateResponse render). An ISettings form template is never a document root by construction.
Likely fleet-wide: every app with an admin settings section has such a template.
gate-38 scopes
templates/settings/*.phpas if each were an app entry point. Those files areISettings::getForm()fragments embedded inside core's settings page, which already renders the skip links. Requiring one of them to carry its own is not just unnecessary — satisfying it would be a WCAG regression.Measured
Full-tree run against
ConductionNL/larpingapp@developmentc73df92d:The whole file:
Call path
lib/Settings/LarpingAppAdmin.php:registered via
appinfo/info.xml:Nextcloud renders that response inside
/settings/admin, whose layout is core'score/templates/layout.user.php:So the bypass links are present on the rendered page, one layer above where the gate can grep — the same "I cannot see it is not it is absent" situation the gate already carves out for
<CnAppRoot>two lines earlier in its own source:Satisfying it would make accessibility worse
The only way to clear this finding in-app is to add an
<a href="#main">(or similar) into the settings fragment. That link would:That is a decoration added to turn a gate green, which is exactly what the evidence rules forbid. Left unfixed in larpingapp and documented in ConductionNL/larpingapp#289.
Suggested fix
Drop
templates/settings/*.phpfrom gate-38's scope, or gate it on the file actually being a full document (e.g. it emits<html/<body, or is not referenced from anISettings/TemplateResponserender). AnISettingsform template is never a document root by construction.Likely fleet-wide: every app with an admin settings section has such a template.