Skip to content

🎨 Icons should be outline-variant where possible - #2672

Merged
AndyScherzinger merged 1 commit into
mainfrom
style/noid/oputlineIcon_and_TabIconState
May 19, 2026
Merged

🎨 Icons should be outline-variant where possible#2672
AndyScherzinger merged 1 commit into
mainfrom
style/noid/oputlineIcon_and_TabIconState

Conversation

@AndyScherzinger

@AndyScherzinger AndyScherzinger commented May 18, 2026

Copy link
Copy Markdown
Member

and tab icons should reflect a state (filled/outlined)

🖼️ Screenshots

🏚️ Before (current prod) 🏡 After
2026-05-19 14_10_44-Tables - Nextcloud — Mozilla Firefox 2026-05-19 14_10_05-Welcome to Nextcloud Tables! - Tables - Nextcloud — Mozilla Firefox
2026-05-19 14_11_01-Maintained applications - Tables - Nextcloud — Mozilla Firefox 2026-05-19 14_30_36-Welcome to Nextcloud Tables! - Tables - Nextcloud — Mozilla Firefox

@AndyScherzinger AndyScherzinger added this to the v2.2.0 milestone May 18, 2026
@AndyScherzinger AndyScherzinger added the 3. to review Waiting for reviews label May 18, 2026
@AndyScherzinger
AndyScherzinger force-pushed the style/noid/oputlineIcon_and_TabIconState branch from a8ec8ca to 0efbeb5 Compare May 19, 2026 11:57
@blizzz
blizzz requested a review from marcoambrosini May 19, 2026 12:05
@blizzz

blizzz commented May 19, 2026

Copy link
Copy Markdown
Member

before/after screenshots?

@AndyScherzinger
AndyScherzinger force-pushed the style/noid/oputlineIcon_and_TabIconState branch from 0efbeb5 to e9c9fe2 Compare May 19, 2026 12:17
…ns should reflect a state (filled/outlined)

AI-assistant: Claude Code v2.1.143 (Claude Sonnet 4.6)
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
@AndyScherzinger
AndyScherzinger force-pushed the style/noid/oputlineIcon_and_TabIconState branch from e9c9fe2 to d429c49 Compare May 19, 2026 12:31
@AndyScherzinger
AndyScherzinger merged commit 98a7df1 into main May 19, 2026
53 of 56 checks passed
@AndyScherzinger
AndyScherzinger deleted the style/noid/oputlineIcon_and_TabIconState branch May 19, 2026 13:43
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 16, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 16, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 17, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 17, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 17, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 17, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 17, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 17, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 17, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 17, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 17, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 17, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 18, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 18, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 18, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 18, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Mixing the two icon systems misaligns the nav: NcIconSvgWrapper reserves a
clickable-area box around its svg while a material design icon is only as
big as itself, which left the versions icon 7px below the sharing one. The
wrapper's inline modifier drops that box.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 18, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 18, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

The body is fetched for one note at a time, and which note that is guards the
result: the sidebar can be sent to another note while a fetch is still on its
way — from a row of the list, or by the list navigating — and the answer to the
first request must then neither be reported for the second note nor keep its
own fetch from starting.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 24, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Mixing the two icon systems misaligns the nav: NcIconSvgWrapper reserves a
clickable-area box around its svg while a material design icon is only as
big as itself, which left the versions icon 7px below the sharing one. The
wrapper's inline modifier drops that box.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 24, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

The body is fetched for one note at a time, and which note that is guards the
result: the sidebar can be sent to another note while a fetch is still on its
way — from a row of the list, or by the list navigating — and the answer to the
first request must then neither be reported for the second note nor keep its
own fetch from starting.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 24, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Mixing the two icon systems misaligns the nav: NcIconSvgWrapper reserves a
clickable-area box around its svg while a material design icon is only as
big as itself, which left the versions icon 7px below the sharing one. The
wrapper's inline modifier drops that box.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 24, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

The body is fetched for one note at a time, and which note that is guards the
result: the sidebar can be sent to another note while a fetch is still on its
way — from a row of the list, or by the list navigating — and the answer to the
first request must then neither be reported for the second note nor keep its
own fetch from starting.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 30, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

The body is fetched for one note at a time, and which note that is guards the
result: the sidebar can be sent to another note while a fetch is still on its
way — from a row of the list, or by the list navigating — and the answer to the
first request must then neither be reported for the second note nor keep its
own fetch from starting.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 30, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Mixing the two icon systems misaligns the nav: NcIconSvgWrapper reserves a
clickable-area box around its svg while a material design icon is only as
big as itself, which left the versions icon 7px below the sharing one. The
wrapper's inline modifier drops that box.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 30, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

The body is fetched for one note at a time, and which note that is guards the
result: the sidebar can be sent to another note while a fetch is still on its
way — from a row of the list, or by the list navigating — and the answer to the
first request must then neither be reported for the second note nor keep its
own fetch from starting.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 30, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Mixing the two icon systems misaligns the nav: NcIconSvgWrapper reserves a
clickable-area box around its svg while a material design icon is only as
big as itself, which left the versions icon 7px below the sharing one. The
wrapper's inline modifier drops that box.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 31, 2026
The sidebar hosts Sharing but says nothing about the note itself. This adds a
first tab with category, a reading estimate, size, created and modified times,
the file path, and a read-only marker when the note cannot be written.

Most of it is free: the sidebar already fetches the note's DAV node for the
sharing tab, so size and creation time come from data it was loading anyway,
and everything else is on the note in the store.

The reading estimate is not free, and that shapes the design. The note list
payload excludes `content`, so a note that has never been opened has none
client-side. Rather than fetching every body up front, the tab pulls the one
note it needs and only once its tab is actually selected — opening the sidebar
to share a note does not drag its body down with it. Until then the row shows a
placeholder, and a body that cannot be read shows a dash rather than
disappearing. fetchNote() only rejects on a missing note and reports everything
else itself, so the content is what says whether the fetch worked.

Estimating a reading time means counting words without the markup, otherwise
'#' and '**' inflate the number. noteStats.js strips the obvious things —
fenced code, image syntax, link targets while keeping labels, heading, quote
and list markers, setext underlines, emphasis — and leaves the rest alone. A
full parse would be much more code for a number nobody checks to the decimal.

Deliberately no word or character count. The Text app shows both for the note
open in rich mode, computed from its parsed document rather than from the
markdown, so a second pair of numbers here would differ from those and invite
the question of which is right. A reading estimate is the one figure Text does
not offer.

The sidebar hosted a single tab, so making room for a second one takes a little
with it:

* A new event notes:sidebar:open carries a tab id. notes:share:open is kept as
  a thin wrapper so anything already emitting it keeps working, and "Details"
  is offered in the note's action menu next to Share.
* NcAppSidebar falls back to its first tab when the requested one is not there
  but does not report that back, so the id is clamped in resolvedTab() for
  `active` to reach the right element.
* Both tab icons are outlined until their tab is active, following the pattern
  from nextcloud/tables#2672. The switch happens inside the #icon slot as
  @nextcloud/vue has no #icon-active yet.
* The header no longer says "Share", the "not available right now" empty state
  now also checks for a note so it cannot appear just because the sharing tab
  failed to register, and the context error copy no longer claims sharing is
  what failed.

The tab is Notes' own rather than a Files sidebar tab, so it renders outside
the registry lookup with order 0 to sit ahead of Sharing, and its rows are
inset to the same 8px the Files tabs put their own content at.

The body is fetched for one note at a time, and which note that is guards the
result: the sidebar can be sent to another note while a fetch is still on its
way — from a row of the list, or by the list navigating — and the answer to the
first request must then neither be reported for the second note nor keep its
own fetch from starting.

Assisted-by: Claude Code:claude-opus-5[1m]
Co-Authored-By: Andy Scherzinger <info@andy-scherzinger.de>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
AndyScherzinger added a commit to nextcloud/notes that referenced this pull request Aug 31, 2026
Sidebar tabs should carry outlined icons that fill once the tab is
active. The sharing tab now renders ShareVariantOutline while inactive
and ShareVariant while active, following the pattern from
nextcloud/tables#2672.

The switch happens inside the #icon slot rather than through a dedicated
slot, as @nextcloud/vue has no #icon-active yet: NcAppSidebarTab exposes
renderIcon() without arguments. That is enough here, because the tab
button invokes renderIcon() from its own render function, so reading the
resolved tab id there tracks it.

Only the sharing tab is overridden. Every other tab keeps the icon its
app registered, versions included — there is no outlined counterpart of
the backup-restore icon to fill in.

Mixing the two icon systems misaligns the nav: NcIconSvgWrapper reserves a
clickable-area box around its svg while a material design icon is only as
big as itself, which left the versions icon 7px below the sharing one. The
wrapper's inline modifier drops that box.

Assisted-by: Claude Code:claude-opus-5[1m]
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews AI assisted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants