Skip to content

gate-38 skip-link treats templates/settings/*.php as a document root — an ISettings fragment cannot carry a bypass link #232

Description

@rubenvdlinde

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions