Scrolling inside an app such as OnlyOffice could make the whole page bounce
with it, exposing the blank page background behind the app frame. Set
overscroll-behavior-y: none on the root element while AppWrapper is mounted
and restore the previous value on unmount. The logic lives in a new
useDisableRootOverscroll composable, which is covered by unit tests.
Signed-off-by: Lukas Hirt <info@hirt.cz>
* fix(web): [OCISDEV-1511] refresh files stuck in processing after upload or copy
A postprocessing-finished event that arrives while a folder reload or a
fetch after copy is still in flight is dropped, because the resource is
not in the store yet. The response then adds the resource as processing
and nothing clears it until a page reload.
Remember finished items for a short time and refetch them when they show
up in the store as processing.
* chore: [OCISDEV-1511] add changelog
Save the MFA status after the user is resolved so signed-URL downloads
inherit it, and check its age on read. Build the signing-key URL with
the URL API so it works with the vault base URL.
Remove the "Session expiring" extend/dismiss popup and its expiry
worker. Drop the frontend session_duration capability and config;
OCIS_MFA_SESSION_DURATION stays in use by the proxy.
`requireAcr()`, used by the admin settings route guards, redirected to the
IdP whenever the `acr` did not match, so a user without a second factor
looped forever.
It now uses the same protection as the vault: the step-up carries its
target as OIDC state, and if the IdP answers it without the required
`acr`, `requireAcr()` returns false. The admin settings guards then
redirect to the default view and an error message is shown.
Follow-up to https://github.com/owncloud/ocis/pull/12985, which fixed the same loop for the vault.
Selecting a language dropped focus to the page body and the save
confirmation went unannounced, because the select's own best-effort
refocus raced against the handler's translation reload and network
save. Restore focus explicitly once that work settles, before
announcing the save.
* fix(web): stop redirect loop when vault MFA step-up fails
Signed-off-by: Samuel Schumacher <samuel.schumacher@werkspiel.de>
Signed-off-by: Samuel Schumacher <schumacher@b1-systems.de>
* fix(web): leave vault with a full page load after failed MFA step-up
Returning to `/` in-app kept the vault clients and loaded vault spaces
with the non-MFA token, which ended on the RUNTIME-BOOTSTRAP-040 crash
page. Vault mode is fixed per page load, so reload outside the vault
instead and show the MFA error message after the reload. The spaces
load error handler no longer routes to the crash page while leaving.
Also addresses review feedback:
- use $pgettext with translator context for the error message
- test that an expired user with a fresh marker is still redirected
* fix(web): only treat a vault step-up as failed after the IdP answered it
The step-up marker relied on a 5 minute TTL, so a user who went to the
IdP, pressed back and opened the vault again got the error without ever
seeing the IdP. The step-up request now carries its target as OIDC
state; the sign-in callback picks it up and only the navigation right
after the callback counts as the answer. The sessionStorage marker and
the TTL are gone.
Also addresses review feedback:
- simplify the auth guard check to `redirect !== undefined`
- drop the storage key re-export, the spec imports it from the helper
Signed-off-by: Samuel Schumacher <schumacher@b1-systems.de>
---------
Signed-off-by: Samuel Schumacher <samuel.schumacher@werkspiel.de>
Signed-off-by: Samuel Schumacher <schumacher@b1-systems.de>
* fix(web): [OCISDEV-1478] move combobox role to the focusable input
The combobox role and its ARIA state lived on vue-select's non-focusable
wrapper div, so screen readers never announced the actual input as a
combobox and its accessible name fell back to a generic hardcoded label.
Moves role, aria-expanded, aria-controls, aria-haspopup and
aria-activedescendant to the focusable search input and gives it its own
label.
* fix(web): [OCISDEV-1478] fix CI, drop QuotaSelect's own search slot
It rendered a plain unlabeled input, bypassing OcSelect's accessible
one. That only worked by accident because vue-select's wrapper div
used to carry a fallback label, which the combobox fix removes.
feat(web): adopt Nx as task orchestrator for web/
Introduces Nx on top of the existing pnpm workspace, so lint/typecheck/
test can be scoped to only the projects affected by a change instead of
running against the whole monorepo on every PR.
- Phase 0: add nx (^23.2.1) as a workspace devDependency, nx.json with a
targetDefaults entry for test:unit (cache: true), ignore .nx/cache and
.nx/workspace-data. No behavior change to any existing script.
- Phase 1: register @nx/vitest's inference plugin so every package with
a vitest.config.ts gets a real, cached "test" target read directly
from its own config, not just the packages that happened to already
declare a "test:unit" npm script. Excludes vite.config.common.ts,
the root vite.config.ts and vite.cern.config.ts from the plugin's
default glob - none of them are test configs, and loading them as one
aborts the whole project graph. defaultBase: "master" (bare `nx
affected` defaults to a branch named "main"). testMode: "run" so `nx
run <project>:test` doesn't default to vitest's watch mode locally.
- Phase 2a: register @nx/eslint/plugin for a per-project "lint" target
on every project (root `lint` script untouched).
- Phase 2b: register @nx/vite/plugin's typecheckTargetName for a
per-project "typecheck" target on projects with their own real
vite.config.ts (design-system, web-client, web-pkg, web-test-helpers).
The ~20 web-app-*/web-runtime projects only have a vitest.config.ts
and aren't covered; root `pnpm run check:types` (whole-repo vue-tsc)
stays the one check that covers everything.
- Phase 3 (CI): adds a `full-run` output to detect-web-changes - true for
push to master/stable-* or a `[full-ci]` PR title (unchanged behavior),
false for a normal PR, which instead runs `nx affected -t lint` / `nx
affected -t test --base=<PR base sha>`. Added fetch-depth: 0 to
web-build-test's checkout, needed for `nx affected --base=<sha>` to
find that commit. Check types and the full-run unit-test path
deliberately stay whole-repo (see inline comments for why).
Also:
- removes design-system's broken "lint": "lint:stylelint" alias (needed
the "lint" name free for the new per-project target; it was never
callable as written - "lint:stylelint" isn't a binary - and nothing
called it).
- makes the design system's webfontloader import fire-and-forget so an
in-flight rejection at test teardown doesn't surface as an unhandled
rejection under vitest's per-project runs.
- pin `typescript` via `pnpm.overrides`. Adding nx's own devDependencies
makes pnpm re-resolve the workspace's peer graph, and today's registry
now has a typescript 6.x that some peer ranges accept - without the
pin, half the vue-peer-dependent packages (pinia, vue-router,
vue3-gettext, focus-trap-vue, ...) fork into a second, incompatible
module instance. Same pin as the dependency-cycle fix, for an
unrelated reason; needed independently here since this branch changes
package.json on its own.
nx's `dependsOn: ["^target"]` errors outright on a circular task graph,
so this only works once the pre-existing web-pkg/design-system/
web-test-helpers dependency cycle is fixed - see
fix(web): break web-pkg/design-system/web-test-helpers dependency cycle,
which this PR targets rather than master.
Verified: nx run-many -t test / lint / typecheck succeed for all
projects; nx show projects --affected --files=... resolves correctly for
shared config changes; nx affected -t lint --base=<sha> matches pnpm
lint; root pnpm lint/check:types/check:format/test:unit/build all still
pass unchanged (380 files / 3128 tests).
fix(web): break web-pkg/design-system/web-test-helpers dependency cycle
web-pkg, design-system and web-test-helpers formed a cycle: the mount
helpers have to install the design system (its components are registered
globally) and web-pkg's mocked pinia stores, so web-test-helpers was
imported by both packages that imported it back.
Resolved by layering the helpers onto the packages that already need
them, instead of adding another one:
design-system/testing mount/shallowMount, prop types, design system +
gettext + router-link stub plugins
web-pkg/src/testing the above, extended with casl abilities, mocked
pinia stores and component mocks
web-test-helpers re-export of web-pkg/src/testing
design-system sits at the bottom of the workspace graph, so it cannot
depend on a package that installs it - hence the generic half lives
there. web-pkg composes those plugins rather than duplicating them, so
every helper is still defined exactly once, and no package depends on
one above it.
The public API of `@ownclouders/web-test-helpers` is unchanged, so
external extensions are unaffected.
Also:
- pin `typescript` via `pnpm.overrides` - without it, a second peer-
resolved typescript version forks `vue`, which forks `vue3-gettext`
into two module instances, breaking `inject(GetTextSymbol)` for any
component mounted through these helpers.
- keep `@ownclouders/design-system/testing` external in web-pkg's lib
build. rollup matches `external` strings exactly, so the subpath was
inlined, shipping a second copy of the design system and of
@vue/test-utils into the published bundle - components and wrappers
were then not identity-equal to the consumer's own, and gettext
injection broke.
- name the web-test-helpers umd output `.cjs`. The package is ESM, so the
`.js` file that publishConfig.exports pointed `require` at could not be
required.
Verified: `pnpm install --frozen-lockfile` clean, zero cyclic-workspace
warnings; `pnpm test:unit` 380 files / 3129 tests pass; `pnpm check:types`
0 errors; `pnpm lint`/`check:format` 0 errors; `pnpm build` succeeds;
published artifacts checked by mounting through the built `dist/` bundles
the way an external extension resolves them via publishConfig.exports.
* fix(web): [OCISDEV-1474] keep focus on trigger when dropdown closes
Dropdowns that close on item click removed the clicked item from the
DOM, dropping keyboard focus to the page body and causing screen
readers to re-announce the whole page over any status message the
action triggered. OcDrop now restores focus to its trigger on close,
unless the action already moved focus elsewhere itself.
* fix(web): [OCISDEV-1474] hide trigger tooltip when refocused on close
Restoring focus to the trigger button popped its own tooltip back
open, since tippy's default trigger includes focus. The tooltip then
sat on top of the button and blocked the next click, breaking the
share e2e test. Hide the tooltip right after refocusing.
Toast/status messages (e.g. clipboard copy, file restore) were often
dropped by screen readers because the live region was never cleared
between messages. Clear it before each message and refill after a
short delay so announcements survive focus changes. Toasts themselves
are no longer live regions, since a freshly inserted aria-live node
isn't reliably announced.
The Shares navigation (desktop tab list and mobile drop-down) always offered a
"Shared via link" entry, even when public link sharing was disabled for the account —
for example in the Vault, where the backend rejects public link creation and the
capability `files_sharing.public.enabled` reports `false`. The entry led to a page
that could never contain anything.
The Shares navigation now hides the "Shared via link" entry whenever
`files_sharing.public.enabled` is `false`. Anyone who still reaches the via-link
route directly, including through the legacy `/list/shared-via-link` redirect, is
redirected to "Shared with me" at navigation time, keeping the current route scope
(e.g. `vault`) intact. When public sharing is enabled, all three entries behave as before.
PR: https://github.com/owncloud/ocis/pull/13005
Tab is bound to indenting in the text editor rather than moving focus,
and the editor's own built-in escape hatch for this (Ctrl-M / Shift-Alt-M
on Mac) doesn't work on a standard Mac keyboard layout, since Option+Shift+M
is consumed by macOS as a diacritic combo before it reaches the page.
Added a Ctrl+M handler, identical on every platform, that moves focus
to the first enabled toolbar button, intercepted ahead of the editor's
own listener. The edit area's accessible description now announces
this shortcut for assistive technology users.
The notification bell's accessible name was a static "Notifications"
string regardless of the unread count, so screen reader users never
heard the count shown visually on the badge.
The accessible name (and tooltip) now include the exact unread count,
with correct singular/plural wording.
OCISDEV-1461
Arrow-key navigation in the notification bell's dropdown only started
working after an extra Tab press once it was open. Pressing an arrow
key too early fell through to the browser's default scroll action,
which looked like the dropdown had closed.
Added focus-on-open to the dropdown, matching other click-triggered
dropdowns in the codebase, so focus moves to the first item as soon
as it opens.
* fix: improve contrast on dropdown menu item hover text
Dropdown menu items (e.g. the "New" file/folder menu) rendered their
hovered text in the brand-hover color, which is designed to work as
a background color, not as text on top of the generic hover
background. This produced poor contrast in some themes. The hover
text now uses the same passive-default color already used at rest,
which is verified to read well against the hover background in
every theme.
* fix: improve contrast on nav item and primary button hover states
Non-active sidebar navigation items and primary-variation buttons
(filled and outline appearances) rendered black text/icons on top of
a dark brown hover background in the vault theme, producing
insufficient contrast. Both now use the text-inverse color for their
hover state instead of the primary-contrast token, which resolves
correctly across all themes.
OCISDEV-1451
* docs: add changelog for nav/primary button hover contrast fix
* docs: link changelog entry to PR #12993
* fix: improve low contrast on disabled danger buttons
Disabled danger-filled buttons had insufficient contrast between their
label text and background, caused by a disabled-state opacity fade
fighting against the muted background color. Danger buttons now keep
full opacity when disabled and use a darker muted background.
* feat(web): add mermaid-editor app for standalone .mmd diagrams
md-editor-v3's mermaid support only fires inside a Markdown fenced
code block, so there was no way to edit a Mermaid diagram on its own.
This adds a dedicated split-pane editor/preview app (mirroring
web-app-html-editor's shape) for the mmd/mermaid extensions, feeding
raw diagram source straight into mermaid.render() - no Markdown
wrapping involved. Invalid syntax surfaces an inline error instead of
crashing the preview pane, and the rendered SVG is sanitized with
DOMPurify the same way the embedded markdown-mermaid renderer already
does.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* test(web): add e2e coverage and WCAG 2.2 checks for mermaid-editor
Wires the new 'mmdFile' resource type into the create/edit e2e
resource helpers (mirroring txtFile/mdFile) and adds a spec covering
diagram rendering, invalid-syntax error surfacing, and the view-mode
toggle. The new mermaidEditor.ts page-object asserts the existing
WCAG 2.1 a11y baseline every other e2e spec uses, plus an additional
WCAG 2.2 tag scan (target-size, focus-appearance, etc.) scoped to
this file only - the shared a11y helper used by every other spec
stays on its current WCAG 2.1 tag set, since widening it repo-wide is
a separate decision with its own pre-existing-violation triage cost.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* fix(web): register mermaid-editor in the default enabled apps list
CI's e2e run for the new mermaid-editor app timed out waiting for the
"New > Mermaid diagram" menu entry: config.json.dist (a reference/dev
file) had "mermaid-editor" added, but the actual runtime default config
a packaged oCIS binary serves comes from services/web's Go defaults,
which didn't. Also runs Prettier over the 3 files check:format flagged.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* fix(web): correct vue3-gettext version, add changelog entry
vue3-gettext was pinned to an exact "2.4.0" in web-app-mermaid-editor's
package.json (a copy-paste slip) instead of "^4.0.1" like every other
web app package, pulling in a stray v2 dependency tree with its own
deprecated transitives (glob@7.2.3, inflight@1.0.6). Also adds the
changelog/unreleased entry this PR was missing.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* fix(web): resolve stale mermaid lockfile entry from merge conflict
web/pnpm-lock.yaml pinned mermaid to 11.16.0 for the new
web-app-mermaid-editor importer, left over from a badly-resolved merge
with master (which resolves mermaid to 11.16.1 in every other
importer and in the packages snapshot). pnpm install --frozen-lockfile
failed in CI with ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY since 11.16.0
had no corresponding packages entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: dj4oC <david.walter@kiteworks.com>
* test(web): register mermaid-editor in the e2e CI oCIS config
tests/config/ci/ocis-config.json is the WEB_UI_CONFIG_FILE the e2e
docker-compose stack (tests/acceptance/docker/src/ocis-base.yml) feeds
the oCIS instance under test. It keeps its own "apps" allowlist,
separate from services/web's Go defaults (already fixed) and
config.json.dist (also already fixed) - and it never got
"mermaid-editor" added. Without it, the running instance never
registers the app, so the "New > Mermaid diagram" menu entry the
mermaidEditor.spec.ts e2e test waits for never renders, and the click
times out after all retries.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: dj4oC <david.walter@kiteworks.com>
* fix(web): give the mermaid-editor CodeMirror pane an accessible name
The plain-text CodeMirror content div renders with role="textbox"
aria-multiline="true" but no aria-label/aria-labelledby/title, which
axe-core (and screen readers) flag as a serious WCAG violation - this
is exactly what the e2e a11y scan caught on the new mermaidEditor
spec. Fixed via EditorView.contentAttributes, the same mechanism
CodeMirror exposes for this. MermaidEditorPane.spec.ts is updated to
install defaultPlugins() like its sibling specs already do, since the
component now calls useGettext() for the label text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: dj4oC <david.walter@kiteworks.com>
---------
Signed-off-by: David Walter <david.walter@kiteworks.com>
Signed-off-by: dj4oC <david.walter@kiteworks.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix: [OCISDEV-1413] unify when the maintenance banner clears
The webdav client and ClientService disagreed about when to clear
maintenance mode: webdav cleared it on any non-503 error, including a
plain 404 or 500, while ClientService only cleared it on a genuine
successful response. Whether the maintenance banner disappeared during
real maintenance depended on which client made the last request.
Both clients now only clear maintenance mode on a 2xx response, and the
now-unused clearOnUnrelatedError option is dropped.
* docs: link changelog entry to #12966
* fix: [OCISDEV-1413] treat the 503 signal, not general unhealthiness, as maintenance
Per review feedback: maintenance is an explicit, deliberately-chosen
server state, not the same as a general error. An unrelated response
(a 401, a 404, even a 500) proves the server answered and is not the
same as the 503 maintenance signal, so it should clear the banner too.
Only a transport-level failure, where no response is received at all,
is left untouched, since that is not evidence either way.
* chore: [OCISDEV-1412] remove the readOnly workaround from the libre-graph client generation
The client generation stripped every `readOnly: true` line from the spec
before running the generator, because the typescript-fetch templates leave
read-only fields out of the serializers they emit and a few fields oCIS does
accept on write were annotated read-only. Stripping them all also unmarked
the fields that genuinely are response-only.
The annotations are corrected upstream now, so the generator runs on the spec
as published. Everything oCIS writes is still serialized: the drive quota
total, the drive type, a space image and readme, the hidden flag and remote
item of a drive item, and the fields of a sharing permission. Response-only
fields such as the computed quota values are now typed read-only, which the
handful of places that assigned to them had to be adjusted for.
* chore: [OCISDEV-1412] regenerate the libre-graph client after the driveType split
drive now declares its own writable driveType while driveUpdate marks it
readOnly, so updateDrive no longer accepts a value the server rejects.
* fix: [OCISDEV-1412] keep the special folder id in the drive update body
Setting a space image or readme patches the drive with the id of an existing
item under special. driveItem.id was marked readOnly in the spec, so the
generated serializer dropped it and the server skipped the entry: the request
returned 200 and did nothing, which the e2e space image test caught as a
timeout waiting for the image to load.
The annotation is gone from the spec, and updateDrive now has tests asserting
the serialized body, since neither type checking nor a mocked call notices a
field that quietly disappears.
* fix: [OCISDEV-1412] regenerate after splitting the drive special folder schema
An update to a drive assigns its special folders by referencing existing
items by id. Upstream now models that with a dedicated schema instead of
reusing the response-only drive item, so the update body keeps the id while
the drive item itself is read-only again.
The wrapper around the generated client typed the update body as a drive
rather than a drive update, which no longer holds now that the two differ in
how they describe the special folders.