Wishble 1.0.0

Docs

On this page
  1. Features
  2. Requirements
  3. Installation
  4. Setting up the panels
  5. Telling Wishble about your app
  6. Words
  7. Plain text or the rich editor
  8. Order of lists
  9. Feature switches
  10. Navigation
  11. How the board keeps trust
  12. Leaving
  13. Uninstalling
  14. For your own code
  15. Licence

Wishble gives your customers a board to post ideas on, vote for the ones they want and talk them through, and gives your team one place to triage them, size them up and tell everyone following an idea what happened to it. It runs in any Filament 5 panel, in a single-database app or a multi-tenant one, and almost everything about it can be configured.

Features

For your customers

  • The board: every idea, filtered by Open, Coming, Shipped and Won't do, by area, by their own ideas or the ones they follow, and searched as typed.
  • Votes: one each, taken back by pressing again. "I'd rather you didn't" is counted for your team and never shown on the board.
  • Posting an idea: ideas already on the board are offered as they type, so a vote goes to the one already there. Each post is signed the way its author chooses: full name and organisation, first name only, or anonymously.
  • Each idea's page: your team's updates, the discussion, and Follow, with status changes only or comments too.
  • A Report button on every idea and comment. Nothing hides itself: a person on your team decides.

For your team

  • Triage: votes for and against, how many customers want it, Update status (which tells every follower), hide, lock the discussion, and merge a repeated idea into another, votes and followers included.
  • Sizing: impact, effort and cost, and the verdict their score falls into, seen only by staff.
  • Insights: what's most wanted by how many customers, where things stand, time to the first update, and who isn't taking part.
  • The board's words are yours: areas, statuses, sizing levels and verdicts are all edited in the staff panel.
  • Turn an idea into a support request, when the app has polycode/knowble.

For you, the developer

  • Config over convention: every feature switches off, and every page's label, icon, group, position and placement (sidebar, user menu, both, hidden, or a cluster's tabs) can be changed.
  • Single-database or multi-tenant: one host class tells Wishble who the tenant is, who may use the board, and how to reach people.
  • Email and the bell, each person asked before every one.

Requirements

  • PHP 8.3 or later
  • Laravel 12 or 13
  • Filament 5
  • polycode/knowble, to turn an idea into a support request (optional)
  • A staff panel that only your team signs in to, separate from your customers' panel, to triage ideas in the app (optional: without one, staff are emailed)

Installation

composer require polycode/wishble
php artisan wishble:install

The installer publishes the config and the migration, writes your choices into them, migrates if you ask, and adds the board's starting vocabulary: areas, statuses, sizing levels and verdicts, all yours to rename. It can run unattended:

php artisan wishble:install --no-interaction --without-against-votes --in-app \
    --staff-email=ideas@example.com --team="Acme team" --migrate

Run php artisan wishble:install --help for every option. The installer writes WISHBLE_STAFF_EMAILS and WISHBLE_TEAM to .env, and to .env.example when you have one.

Installing by hand

php artisan vendor:publish --tag=wishble-config
php artisan vendor:publish --tag=wishble-migrations
php artisan migrate
php artisan wishble:install --vocabulary-only

The last line adds the starting vocabulary to the migrated tables, and never changes a row you've edited. Then set WISHBLE_STAFF_EMAILS and WISHBLE_TEAM in .env.

Wishble serves its own compiled stylesheet from /wishble-assets/wishble.css on each panel page's own domain, so there's nothing to publish or deploy. It takes its colours from your panel, dark mode included.

Setting up the panels

Register WishblePlugin on the panel your customers use, and WishbleAdminPlugin on your staff panel:

use Polycode\Wishble\WishblePlugin;
use Polycode\Wishble\WishbleAdminPlugin;

$panel->plugin(WishblePlugin::make());       // the customer panel
$panel->plugin(WishbleAdminPlugin::make());  // the staff panel

The board joins the customer panel's Support group as Wishlist; Ideas, Insights and the settings join a Wishlist group on the staff panel.

The two plugins need separate panels, because Wishble tells staff from customers by the panel they're in. With both on one panel, the app stops with an error as the panel is built, so a deploy or php artisan optimize shows it. If your app has one panel, add a second for staff. The board works without a staff panel: staff are emailed new ideas and reports, and triage in a panel when you add one.

Telling Wishble about your app

config/wishble.php names a host class. The default, Hosts\SingleApp, suits a single-database app.

Filament's own tenancy

If your customer panel uses ->tenant(Team::class), SingleApp works with no changes: each team is a tenant, emailed links include the team's slug, and staff can filter by team. Outside a request (a queued job, a command), build a link with Wishble::ideaUrlFor($tenantId, $reference), as Wishble::ideaUrl() needs the team of the current request.

Other multi-tenant apps

An app with its own tenancy, such as a database per tenant, extends SingleApp:

use App\Models\Tenant;
use Illuminate\Contracts\Auth\Authenticatable;
use Polycode\Wishble\Hosts\SingleApp;

class WishbleHost extends SingleApp
{
    public function tenantId(): string|int|null { return tenant('id'); }

    public function tenantName(string|int $tenantId): string { return Tenant::find($tenantId)?->name ?? (string) $tenantId; }

    public function tenants(): array { return Tenant::query()->pluck('name', 'id')->all(); }

    public function customerUrl(string|int|null $tenantId, string $path): string { /* that tenant's address */ }

    public function mayUseWishble(Authenticatable $user): bool { return $user->can('use-the-board'); }
}

mayUseWishble() is all or nothing: someone who may use the board may post, vote, comment and report, and someone who may not sees none of it. Implement any of these as well:

  • Contracts\SwitchesPerTenant::isOnFor(): each tenant can turn the board off. Off, its people see no board and hear nothing from it.
  • Contracts\NotifiesInApp: put notifications in the bell, however your app reaches a tenant's users. Wishble asks wantsInApp() before each one.
  • Contracts\NotificationPreferences::wantsMail(): each person's choice about email.

Wishble's tables live on one connection (WISHBLE_CONNECTION, a multi-tenant app's central), and keep their times in one zone (WISHBLE_STORAGE_TIMEZONE, or the app's zone as it starts), however the app switches zones for each tenant.

Words

  • brand.team (WISHBLE_TEAM) is how your team signs its posts: "Acme team".
  • brand.customers is what the board calls the customers behind the posts: "from 3 businesses".
  • Every other string goes through __(), so a lang/*.json file rewords anything.

Plain text or the rich editor

text_editor is rich by default: ideas, comments and the team's notes are written in Filament's rich editor, with bold, lists and links. Set it to plain in config/wishble.php for plain text boxes instead (wishble:install --plain-text does this, and the installer asks). Plain text is shown as typed, line breaks kept, on the board, in the emails and in the bell, and earlier rich posts still show as they were written.

Order of lists

Areas, statuses, the impact, effort and cost levels, and verdicts are each in the order your team drags them into on their staff screens. Set any of them to alphabetical under order in config/wishble.php to sort it by name instead: categories, statuses, scales and verdicts, all manual by default. The board, its filters and the staff selects follow the same order.

Feature switches

Each is in config/wishble.php under features: against_votes, sizing, reports, merging, comments, comment_emails, insights, in_app, new_idea_notices, per_tenant and convert_to_support. All but in_app are on.

Every page's label, icon, group, sort and placement come from config or the plugin, the plugin winning:

use App\Filament\Clusters\SettingsCluster;
use Polycode\Wishble\Navigation\Placement;

WishblePlugin::make()->navigation('board', label: 'Ideas', placement: Placement::UserMenu);

WishbleAdminPlugin::make()
    ->navigationGroup('Product')
    ->navigation('statuses', cluster: SettingsCluster::class);

The items are board on the customer panel, and ideas, insights, categories, statuses, impact, effort, cost and verdicts on the staff panel. ideaActions() adds your own actions to an idea's triage page.

How the board keeps trust

  • The author's name and email, and the business behind a post, never reach the browser: every model hides them when serialised.
  • Against-votes are counted for staff and never shown to a customer.
  • A status moves only through Update status, which tells every follower.
  • Counts are recalculated from the votes, never added to.
  • Nothing a customer sends is trusted: which side wrote a comment is decided on the server, and everything a customer can send is throttled.

Leaving

When a tenant leaves, call Wishble::forgetTenant($tenantId). Its follows are deleted and the email on its ideas, comments, votes and reports is blanked; its words and names stay, as other people's votes and replies hang on them.

Uninstalling

  1. Take WishblePlugin and WishbleAdminPlugin off your panels.
  2. To drop the tables, roll back the published create_wishble_tables migration with php artisan migrate:rollback --path=database/migrations/<its file name>, or drop the wishble_* tables yourself. The migration reads only config/wishble.php, so it runs with or without the package installed.
  3. Run composer remove polycode/wishble.
  4. Delete config/wishble.php, the migration file, and the WISHBLE_* lines in .env and .env.example.

For your own code

  • Wishble::boardUrl() and Wishble::ideaUrl($reference) link to the board.
  • Support\Votes, Support\Updates and Support\Merger are the same calls the screens make.
  • Support\Vocabulary::seed() adds the starting vocabulary; factories cover every model.

Licence

Commercial: see LICENSE.md.