Skip to content

Commit 83b94b4

Browse files
committed
fix: centralize provider setup in Connections
1 parent 6172199 commit 83b94b4

18 files changed

Lines changed: 709 additions & 749 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'meta-sovereign': patch
3+
---
4+
5+
Move provider credential, archive import, and direct probe controls onto
6+
Connections provider detail pages while keeping data-section empty states as
7+
compact links into Connections.

.gitkeep

Lines changed: 0 additions & 2 deletions
This file was deleted.

js/src/web/app.css

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -317,27 +317,6 @@ pre {
317317
.connection-guide > h2 {
318318
margin: 0;
319319
}
320-
.connection-guide-provider {
321-
display: flex;
322-
flex-direction: column;
323-
gap: 0.5rem;
324-
}
325-
.connection-guide-provider .col {
326-
gap: 0.25rem;
327-
}
328-
.connection-guide-provider h3 {
329-
margin: 0;
330-
font-size: 1rem;
331-
}
332-
.connection-guide-provider h4 {
333-
margin: 0.25rem 0 0;
334-
font-weight: 600;
335-
font-size: 0.85rem;
336-
}
337-
.connection-guide-provider .probe-row {
338-
gap: 0.5rem;
339-
align-items: flex-start;
340-
}
341320
.local-server-help {
342321
background: var(--bg-elev);
343322
border: 1px dashed var(--border);

js/src/web/app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ const App = () => {
121121
setActive(detail.view);
122122
}
123123
if (typeof detail.anchor === 'string') {
124-
// Update the URL hash so the Settings view can scroll to the
125-
// matching `id="conn-{provider}"` card on first paint.
124+
// Update the URL hash so the Connections view can open the
125+
// matching `id="conn-{provider}"` detail on first paint.
126126
if (globalThis.location) {
127127
globalThis.location.hash = detail.anchor;
128128
}

js/src/web/app.min.js

Lines changed: 21 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/src/web/connection-guide.js

Lines changed: 12 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import React, { useState } from 'react';
1313
import {
1414
connectionGuides,
1515
getGuide,
16-
getProvider,
1716
isCrossOrigin,
1817
localServerHelp,
1918
providerCatalogue,
@@ -37,88 +36,6 @@ const tx = (t, value, key) => {
3736
return value;
3837
};
3938

40-
const ProbeRow = ({ provider }) => {
41-
const t = useT();
42-
// R-O1: per-section ProbeRow refuses to fire when no credentials are
43-
// saved (which would always 404/400 — the original bug). It points
44-
// the user at Settings → Connections instead.
45-
const ready = false;
46-
const placeholder = t('guide.probeDisabled', {
47-
label: tx(t, provider.label, provider.labelKey),
48-
});
49-
return el('div', { className: 'col probe-row' }, [
50-
el('div', { key: 'row', className: 'row' }, [
51-
el(
52-
'button',
53-
{
54-
key: 'probe',
55-
type: 'button',
56-
className: 'primary',
57-
disabled: !ready,
58-
},
59-
t('settings.probe.button')
60-
),
61-
el('span', { key: 'meta', className: 'meta' }, placeholder),
62-
]),
63-
]);
64-
};
65-
66-
const ProviderCard = ({ providerId }) => {
67-
const t = useT();
68-
const provider = getProvider(providerId);
69-
return el('section', { className: 'card connection-guide-provider' }, [
70-
el('h3', { key: 'h' }, tx(t, provider.label, provider.labelKey)),
71-
el('div', { key: 'archive', className: 'col' }, [
72-
el(
73-
'h4',
74-
{ key: 'h', className: 'meta' },
75-
tx(t, provider.archive.title, provider.archive.titleKey)
76-
),
77-
el(
78-
'p',
79-
{ key: 'p' },
80-
tx(t, provider.archive.hint, provider.archive.hintKey)
81-
),
82-
el(
83-
'div',
84-
{ key: 'fileHint', className: 'meta' },
85-
t('guide.filesLabel', { hint: provider.archive.fileHint })
86-
),
87-
]),
88-
el('div', { key: 'api', className: 'col' }, [
89-
el(
90-
'h4',
91-
{ key: 'h', className: 'meta' },
92-
tx(t, provider.apiCredentials.title, provider.apiCredentials.titleKey)
93-
),
94-
el(
95-
'p',
96-
{ key: 'p' },
97-
tx(t, provider.apiCredentials.hint, provider.apiCredentials.hintKey)
98-
),
99-
el(
100-
'div',
101-
{ key: 'env', className: 'meta' },
102-
t('guide.envVarLabel', { name: provider.apiCredentials.envVar })
103-
),
104-
el(
105-
'div',
106-
{ key: 'docs', className: 'meta' },
107-
el(
108-
'a',
109-
{
110-
href: provider.apiCredentials.docsUrl,
111-
target: '_blank',
112-
rel: 'noopener noreferrer',
113-
},
114-
t('settings.docsLink')
115-
)
116-
),
117-
el(ProbeRow, { key: 'probe', provider }),
118-
]),
119-
]);
120-
};
121-
12239
export const LocalServerHelp = () => {
12340
const t = useT();
12441
const [override, setOverride] = useState('');
@@ -181,23 +98,22 @@ export const LocalServerHelp = () => {
18198
]);
18299
};
183100

184-
// R-O7: per-section "Connect first" CTA. Renders above the in-place
185-
// guide and deep-links into Settings → Connections, scrolling to the
186-
// matching provider card via the `meta-sovereign:navigate` event that
187-
// app.js listens for.
188-
export const SettingsConnectFirstCta = ({ providerId }) => {
101+
// Issue #27: per-section "Connect first" CTA. Empty data surfaces keep
102+
// their explanation but deep-link into the dedicated Connections detail
103+
// page instead of repeating provider setup cards inline.
104+
export const ConnectionsConnectFirstCta = ({ providerId }) => {
189105
const t = useT();
190106
const provider = providerId ? providerCatalogue[providerId] : null;
191107
const target = provider
192-
? t('guide.openSettingsTarget', {
108+
? t('guide.openConnectionsTarget', {
193109
label: tx(t, provider.label, provider.labelKey),
194110
})
195-
: t('guide.openSettingsRoot');
111+
: t('guide.openConnectionsRoot');
196112
const navigate = () => {
197113
const anchor = providerId ? `#conn-${providerId}` : '';
198114
globalThis.dispatchEvent?.(
199115
new CustomEvent('meta-sovereign:navigate', {
200-
detail: { view: 'settings', anchor },
116+
detail: { view: 'connections', anchor },
201117
})
202118
);
203119
};
@@ -210,10 +126,11 @@ export const SettingsConnectFirstCta = ({ providerId }) => {
210126
type: 'button',
211127
className: 'primary',
212128
onClick: navigate,
213-
'data-action': 'open-settings',
129+
'data-action': 'open-connections',
130+
'data-target-provider': providerId ?? '',
214131
'data-target-anchor': providerId ? `#conn-${providerId}` : '',
215132
},
216-
t('guide.openSettings', { target })
133+
t('guide.openConnections', { target })
217134
),
218135
]);
219136
};
@@ -232,21 +149,19 @@ export const ConnectionGuide = ({ section }) => {
232149
'section',
233150
{
234151
className: 'connection-guide',
152+
'data-guide-mode': guide.providers.length > 0 ? 'compact' : 'help',
235153
'data-connection-guide': section,
236154
role: 'note',
237155
},
238156
[
239157
el('h2', { key: 'h' }, tx(t, guide.title, guide.titleKey)),
240158
el('p', { key: 'body' }, tx(t, guide.body, guide.bodyKey)),
241159
guide.providers.length > 0
242-
? el(SettingsConnectFirstCta, {
160+
? el(ConnectionsConnectFirstCta, {
243161
key: 'cta',
244162
providerId: guide.connectFirst?.providerId ?? guide.providers[0],
245163
})
246164
: null,
247-
...guide.providers.map((id) =>
248-
el(ProviderCard, { key: id, providerId: id })
249-
),
250165
guide.providers.length === 0
251166
? el(LocalServerHelp, { key: 'help' })
252167
: null,

js/src/web/connection-guides.js

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { saveServerOverride } from './discover.js';
1919
//
2020
// Issue #16 / R-O1, R-O2 additions per provider:
2121
// - `apiCredentials.fields` enumerates the credential inputs the
22-
// Settings → Connections card renders (text/password). Each field
22+
// Connections provider detail renders (text/password). Each field
2323
// names the `secret:*` link id it persists into.
2424
// - `apiCredentials.probeUrlTemplate` is a string with `{token}` /
2525
// `{phoneNumberId}` / `{appId}` placeholders that resolve against
@@ -32,7 +32,7 @@ import { saveServerOverride } from './discover.js';
3232
// - `apiCredentials.errorHints` maps an HTTP status to a user-facing
3333
// remediation hint surfaced by `<ProbeRow>`.
3434
// - `archive.accept` is the `<input type="file" accept>` filter used
35-
// by the Settings → Connections archive uploader (R-O4).
35+
// by the Connections archive uploader (R-O4).
3636
export const providerCatalogue = {
3737
email: {
3838
label: 'Email',
@@ -664,15 +664,15 @@ export const providerCatalogue = {
664664

665665
// Section -> guide. Keys mirror `navItems` in views.js (R-M1).
666666
//
667-
// Issue #16 / R-O7 additions:
667+
// Issue #16 / R-O7 and issue #27 additions:
668668
// - `connectFirst` is the per-section deep-link the empty-state card
669-
// shows. It points the user at Settings → Connections and scrolls to
670-
// the first relevant provider card (`#conn-{providerId}`).
669+
// shows. It points the user at Connections -> provider detail, where
670+
// the matching provider owns credentials, archive import, and probe.
671671
export const connectionGuides = {
672672
chat: {
673673
title: 'Your unified inbox starts empty.',
674674
titleKey: 'guide.chat.title',
675-
body: 'meta-sovereign keeps every chat from every connected service in one place. Connect a provider below, or import an exported archive to populate this view.',
675+
body: 'meta-sovereign keeps every chat from every connected service in one place. Open Connections to connect a provider or import an exported archive, then come back here.',
676676
bodyKey: 'guide.chat.body',
677677
providers: [
678678
'email',
@@ -691,7 +691,7 @@ export const connectionGuides = {
691691
operator: {
692692
title: 'Operator queue is empty.',
693693
titleKey: 'guide.operator.title',
694-
body: 'The operator card stream walks you through unread messages chat by chat. Connect a chat-capable provider below to start the queue.',
694+
body: 'The operator card stream walks you through unread messages chat by chat. Open Connections and add a chat-capable provider to start the queue.',
695695
bodyKey: 'guide.operator.body',
696696
providers: [
697697
'email',
@@ -710,7 +710,7 @@ export const connectionGuides = {
710710
contacts: {
711711
title: 'No contacts yet.',
712712
titleKey: 'guide.contacts.title',
713-
body: 'Contacts are aggregated from every connected provider so the same person across networks shows up once. Add a provider to populate this list.',
713+
body: 'Contacts are aggregated from every connected provider so the same person across networks shows up once. Open Connections to add a provider and populate this list.',
714714
bodyKey: 'guide.contacts.body',
715715
providers: [
716716
'email',
@@ -732,7 +732,7 @@ export const connectionGuides = {
732732
automation: {
733733
title: 'No automation graphs yet.',
734734
titleKey: 'guide.automation.title',
735-
body: 'Automation graphs route incoming messages from a pattern to a reply variation. Drop a node above, or import an archive first so you have data to match against.',
735+
body: 'Automation graphs route incoming messages from a pattern to a reply variation. Open Connections to import provider data before building automations.',
736736
bodyKey: 'guide.automation.body',
737737
providers: [
738738
'email',
@@ -748,15 +748,15 @@ export const connectionGuides = {
748748
patterns: {
749749
title: 'No patterns yet.',
750750
titleKey: 'guide.patterns.title',
751-
body: 'Patterns are inferred from example messages. Connect a provider, or import an archive, then come back here and feed the inferrer a few examples.',
751+
body: 'Patterns are inferred from example messages. Open Connections to connect a provider or import an archive, then return with examples to infer from.',
752752
bodyKey: 'guide.patterns.body',
753753
providers: ['email', 'telegram', 'vk', 'x', 'whatsapp'],
754754
connectFirst: { providerId: 'telegram' },
755755
},
756756
replies: {
757757
title: 'No reply variation groups yet.',
758758
titleKey: 'guide.replies.title',
759-
body: 'Reply groups are extracted from your previous outgoing messages by fuzzy similarity. Connect a chat-capable provider to seed your reply library.',
759+
body: 'Reply groups are extracted from your previous outgoing messages by fuzzy similarity. Open Connections to add a chat-capable provider and seed your reply library.',
760760
bodyKey: 'guide.replies.body',
761761
providers: [
762762
'email',
@@ -773,15 +773,15 @@ export const connectionGuides = {
773773
facts: {
774774
title: 'No facts extracted yet.',
775775
titleKey: 'guide.facts.title',
776-
body: 'Facts are question -> answer pairs extracted across messages by your patterns. Add a pattern with a capture group, or connect a chat provider to start gathering data.',
776+
body: 'Facts are question -> answer pairs extracted across messages by your patterns. Open Connections to gather provider data, then add a capture pattern here.',
777777
bodyKey: 'guide.facts.body',
778778
providers: ['email', 'telegram', 'vk', 'x', 'whatsapp'],
779779
connectFirst: { providerId: 'telegram' },
780780
},
781781
audience: {
782782
title: 'Build your first audience.',
783783
titleKey: 'guide.audience.title',
784-
body: 'Cross-reference contacts using AND/OR/NOT plus dimensions like network:, chat:, sender:, kind:, fact:. Connect at least one provider so you have contacts to filter.',
784+
body: 'Cross-reference contacts using AND/OR/NOT plus dimensions like network:, chat:, sender:, kind:, fact:. Open Connections to add at least one provider first.',
785785
bodyKey: 'guide.audience.body',
786786
providers: [
787787
'email',
@@ -800,15 +800,15 @@ export const connectionGuides = {
800800
broadcast: {
801801
title: 'No broadcast targets yet.',
802802
titleKey: 'guide.broadcast.title',
803-
body: 'Broadcasts post the same message to every connected feed. Connect a public-posting provider below to enable a target checkbox.',
803+
body: 'Broadcasts post the same message to every connected feed. Open Connections to connect a public-posting provider and enable target checkboxes.',
804804
bodyKey: 'guide.broadcast.body',
805805
providers: ['x', 'vk', 'facebook', 'linkedin', 'telegram'],
806806
connectFirst: { providerId: 'x' },
807807
},
808808
outreach: {
809809
title: 'No outreach surface yet.',
810810
titleKey: 'guide.outreach.title',
811-
body: 'Mass-personal outreach sends a templated message 1:1 to each contact in an audience query. Connect a chat-capable provider to enable outreach.',
811+
body: 'Mass-personal outreach sends a templated message 1:1 to each contact in an audience query. Open Connections to add a chat-capable provider first.',
812812
bodyKey: 'guide.outreach.body',
813813
providers: [
814814
'email',
@@ -826,7 +826,7 @@ export const connectionGuides = {
826826
profile: {
827827
title: 'No profile yet.',
828828
titleKey: 'guide.profile.title',
829-
body: 'Edit your profile and resume here; saves are pushed to every connected provider. Connect at least one provider so the sync envelope has somewhere to go.',
829+
body: 'Edit your profile and resume here; saves are pushed to every connected provider. Open Connections to add at least one provider for profile sync.',
830830
bodyKey: 'guide.profile.body',
831831
providers: [
832832
'telegram',
@@ -865,7 +865,7 @@ export const connectionGuides = {
865865
settings: {
866866
title: 'Settings',
867867
titleKey: 'guide.settings.title',
868-
body: 'Provider connections, credentials, and archive imports live here. The list below mirrors every catalogued provider; pick one to enter its credentials, upload an archive, and probe the live API.',
868+
body: 'App-level preferences live here. Provider credentials, archive imports, and live probes live on the dedicated Connections screen.',
869869
bodyKey: 'guide.settings.body',
870870
providers: [],
871871
},
@@ -1078,8 +1078,8 @@ export const buildProbeHeaders = ({ provider, credentials = {} } = {}) => {
10781078
return out;
10791079
};
10801080

1081-
// Convenience: used by the Settings UI to read every credential field
1082-
// for a provider out of an array of `secret:*` links.
1081+
// Convenience: used by the Connections UI to read every credential
1082+
// field for a provider out of an array of `secret:*` links.
10831083
export const credentialsFromLinks = (provider, links = []) => {
10841084
const fields = provider?.apiCredentials?.fields ?? [];
10851085
const byId = new Map();

0 commit comments

Comments
 (0)