appview: persist newsletter signup/dismiss per-user for cross-device hiding
the newsletter widget used only localStorage to remember whether a user
had signed up or dismissed it, so the cta kept reappearing whenever a
user opened tangled on another device or browser. for logged-in users,
store the state in a newsletter_preferences table keyed on did with an
enum status ('subscribed' | 'dismissed') and the email they gave us.
the home and timeline handlers read this row to decide whether to
render the widget, and the server-rendered gfi banner widens when the
widget is gone so the grid doesn't leave an empty column.
resend stays the source of truth for the mailing list itself (sending,
bounces, one-click unsubscribes) — the new table only answers the
render-time question 'should this did see the widget right now?',
which resend cannot cheaply answer because it's keyed on email rather
than did and would add a network hop to every timeline render.
anonymous visitors keep the localStorage fallback. the client-side
'already dismissed in a past session' path deliberately only calls
hide() (not dismiss()) so that a stale localStorage flag can't clobber
a subscribed row set from another device.
Signed-off-by: eti <eti@eti.tf>