W Workhouse
Feature · visibility

Visibility is a column. Not a workaround.

Every task, comment, message, and audit event in Workhouse carries an internal or client flag. The portal's queries filter on it. The dashboard ignores it. One database, two audiences, zero leakage.

What every agency builds in their PM tool eventually.

Pick any PM tool — Notion, ClickUp, Asana, Trello. The agency-shaped problem shows up on day one. You need internal-only conversations and client-visible deliverables to coexist on the same project. The tools force a choice: invite the client to your whole workspace (and watch what you say), or maintain a parallel client-facing workspace (and sync it forever).

Both choices fail the same way. The internal/external boundary lives in your head, in the slack channel where you discuss the client comment, in the carefully renamed Trello column. Sooner or later, something slips.

How the model works

One flag, applied everywhere it matters.

1

Every task has a visibility.

When you create a task, you set it Internal or Client. Default is Internal. Flip it Client when you're ready for the client to see it.

2

Comments inherit, but you can override.

A comment on a Client-visible task defaults to client-visible. If you need to discuss something internally on the same task, flag the comment Internal. Both threads live on the same task; clients see one of them.

3

The portal filters in SQL.

Every portal query has `WHERE visibility = 'client'` in its where clause, plus the client scope. The portal cannot load internal rows. A guessed URL returns a 404, not a 403.

In the database

For agency operators who've been burned by “client portals” that hide things in the UI — here's where the boundary actually is.

-- Portal task query (simplified)

select * from tasks

where client_id = $1 -- scope to this client

and visibility = 'client' -- never load internal rows

The visibility column has a CHECK constraint — internal or client, no other values allowed. The portal's session can't escalate to internal scope. There's no UI toggle that flips it; the column is read-only from the client side.

Most “client portals” in PM tools render a filtered view of the same data the rest of the workspace sees. Hide a column in the UI, drop a key from a JSON response, call it a day. That works until someone reads the request log or guesses an ID. Workhouse handles visibility one layer down — what the application doesn't load, no UI bug can leak.

Where the flag lives

Visibility isn't just on tasks. It's on every entity where the boundary could matter.

Tasks

Internal-default. Flip to client when ready. Tasks themselves are the most-used surface for the flag.

Comments

Inherit the parent task's visibility but can be overridden per-comment. Lets you have an internal thread on a client-visible task.

Messages

Per-client message threads have visibility per-message. Useful for back-channel context without surfacing to the client.

Activity feed events

Every status change, assignment, approval gets a visibility flag inherited from its parent. Clients see the events that affect their view.

Audit log entries

Always internal. The audit log is the immutable internal record — never client-visible by design.

Action items

Always client-visible (they exist to assign to clients). Action items are the explicit cross-surface contract.

Per-field visibility, too

Beyond the binary internal/client flag, each team can configure which optional task fields (priority, due date, assignee, project name) appear in client-visible task views by default. Some agencies want the client to see who's working on what; others deliberately abstract that.

Title and status always render on a client-visible task — they're load-bearing for the portal UX. Everything else is a team setting, with per-task overrides when needed.

Common questions

What's the default visibility for a new task?

Internal. The agency works on it first; flip to Client when it's ready to share. This default is reversible — teams can set Client-default if their workflow is more client-facing-by-default, but most agencies leave it at Internal.

Can a client see when a task was Internal before becoming Client-visible?

No. From the client's perspective, the task appears when it becomes client-visible — they don't see prior history. If you want the client to see the work-in-progress thread, you can copy the relevant comments to the client-visible side; the original Internal thread stays where it was.

Can we bulk-flip a project from Internal to Client?

Yes. Mass-action on a list of tasks updates visibility for all selected. Useful when wrapping up a phase and exposing the whole batch at once.

What about file attachments and Loom links?

Attachments inherit the visibility of the task they're attached to. An Internal task with attachments doesn't expose those attachments to the portal — same SQL filter.

Can the visibility model be turned off for engagements that don't need it?

In a sense, yes. If you flag every task Client-visible by default, the model effectively becomes a no-op — your client sees everything. Some agencies operate that way for very transparent engagements. The model is still there in the data; you just stopped using it as a filter.

See the model in context: client portal → · audit log → · vs Notion's workspace model →

Make the boundary structural.

Stop trusting filename conventions and Slack channels to keep client conversations clean.

Free during beta · No credit card · Invite your team in 30 seconds