Problem/Motivation

ReaderController::home() falls back to a static message when the current user has no default_timeline stored in user.data:

// src/Controller/ReaderController.php
$settings = $this->userData->get('reader', $this->currentUser()->id(), 'settings');
$default_timeline = !empty($settings['default_timeline']) ? $settings['default_timeline'] : '';
if (!empty($default_timeline)) {
  [$type, $id] = explode(ReaderInterface::SEPARATOR, $default_timeline);
  return $this->timeline($request, $type, $id, TRUE);
}

$build['main'] = ['#markup' => '<div class="general-content">' . $this->t('Click on a channel to start reading') . '</div>'];

On a fresh site with a fresh user, that instruction cannot be followed, because the channel list sitting right next to it is empty. With activitypub_reader enabled, `Reader::getChannels()` returns early when the user has no ActivityPub actor:

// modules/activitypub_reader/src/Reader.php
public function getChannels() {
  if (!$this->getActor()) {
    return [];
  }
  // ... Home / Notifications / Direct messages / Local timeline / Bookmarks
}

A newly registered user does not have an actor — creating one is an explicit opt-in at `/user//activitypub`, gated behind `allow users to enable activitypub`. The base `reader` module contributes a channel only when `aggregator` is installed.

So the two columns say contradictory things:

  • main column — “Click on a channel to start reading”
  • channels block — “No channels found”

The third column does offer Sources and Settings links, but both lead to empty
pages for this user, so the loop is closed:

  • `/reader/settings` — the *Default timeline* select contains only `- None -`, because ReaderSettingsUserForm` builds its options from the same `hook_reader_channels` results.
  • `/reader/sources` — lists nothing, because `ReaderController::sources()` skips any module whose channels are empty:
  foreach (reader_get_implementators() as $module) {
    $function = $module . '_reader_channels';
    if (function_exists($function)) {
      $data = $function();
      if (!empty($data['channels'])) {   // <-- skipped when the user has no actor
        $sources[] = Link::fromTextAndUrl(...);
      }
    }
  }

Every affordance visible to a new user leads back to an empty page, and none of them mentions the thing that would actually unblock them: creating an ActivityPub actor at /user/<uid>/activitypub.

It is worth separating two distinct states, because they need different answers.
They map one-to-one onto the two branches proposed further down.

  1. The user has an actor, but never picked a default timeline. Five channels are available (`home`, `notifications`, `direct`, `local`, `bookmarks`). Today: one pointless extra click before they see anything.
  2. The user has no actor — which is every newly registered user. Zero channels are available. Today: the dead end described above.

State 2 is the first thing anyone sees after the module is installed.

There is a second-order effect worth mentioning: because default_timeline lives in user.data rather than in config, a site owner cannot pre-seed it — not through config, not through a recipe, not through config:set. Recipes only reach configuration, and this is per-user runtime data. So “ship a site that works out of the box” is currently not achievable for Reader without custom code. (I worked around it in my own environment with a small drush php:script.) That makes the fallback below more valuable than it first looks: it is the only lever that does not require per-user provisioning.

Steps to reproduce

  1. Install Drupal 11 with `reader`, `activitypub`, `activitypub_reader`, and install the `reader_theme` theme.
  2. Grant `access reader` to the authenticated role.
  3. Create a new user. Do not create an ActivityPub actor for them, and do not visit `/user//reader`.
  4. Log in as that user and go to `/reader`.

Result:

column 1 : Click on a channel to start reading
column 2 : Read | No channels found
column 3 : Write | Blog post | Other | Posts | Sources | Settings

5. Follow the two links that look like a way out:

  • `/reader/settings` → *Default timeline* select offers only `- None -`
  • `/reader/sources` → empty list

For contrast, a user who *does* have an actor but still has no `default_timeline`
sees the same “Click on a channel to start reading”, while the channel column
correctly lists five channels — one click away from content:

Fediverse
  Home (1)
  Notifications
  Direct messages
  Local timeline
  Bookmarks

Proposed resolution

Remaining tasks

User interface changes

- Better guidance on /reader when either no actor is available, or when no modules are installed at all that integrate with reader
- ability to set default global timeline
- ability in code to override the #empty text - activitypub overrides this in case the global default timeline is set to activitypub_reader::home to create an actor when necessary.

Issue fork reader-3615845

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

foolfitz created an issue. See original summary.

mukeshaddweb made their first commit to this issue’s fork.

mukeshaddweb’s picture

Status: Active » Needs review
swentel’s picture

Status: Needs review » Needs work

Makes sense, from a usability point of view, this is not the best start indeed :)

I'm not sure though if it makes sense to go to /reader/settings when there are no channels at all. You won't be helped further there either. Maybe we should add some info about either installing e.g AP, aggregator or IndieWeb if none are installed or link to the actor page for instance when AP is installed.

Regarding default_timeline in user:data: I agree that's not optimal. Maybe we could add a default setting on line 106 via Settings to set a global default?

swentel’s picture

Status: Needs work » Needs review

Changed two things:

- added a default_global_timeline config setting - which can be set on the settings page too
- change the default wording a bit when either no modules are installed which provide channels, or in case activitypub is installed but no actor has been created yet for the current authenticated user

swentel’s picture

Added a commit into #3572271: Prepare module for Fediverse recipe too to render information when no actor exists yet, and in case the global default is set on activitypub::{x}

swentel’s picture

Issue summary: View changes

swentel’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

swentel’s picture

Status: Fixed » Closed (fixed)