studio-engine 0.66.1 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/Gemfile +25 -0
- data/README.md +22 -24
- data/app/controllers/studio/knowledge_docs_controller.rb +89 -0
- data/app/models/studio/knowledge_doc.rb +207 -0
- data/app/views/solana_sessions/phantom_callback.html.erb +6 -5
- data/app/views/studio/knowledge_docs/_access_chips.html.erb +10 -0
- data/app/views/studio/knowledge_docs/_browser.html.erb +115 -0
- data/app/views/studio/knowledge_docs/_doc_row.html.erb +18 -0
- data/app/views/studio/knowledge_docs/_upload_form.html.erb +48 -0
- data/app/views/studio/knowledge_docs/index.html.erb +6 -0
- data/app/views/studio/knowledge_docs/show.html.erb +87 -0
- data/app/views/style/_modals.html.erb +73 -21
- data/app/views/style/modals/_wallet_connect.html.erb +11 -5
- data/db/migrate/20260901000001_create_studio_knowledge_docs.rb +49 -0
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +21 -0
- metadata +10 -5
- data/app/views/studio/modals/_wallet_connect.html.erb +0 -330
- data/app/views/studio/modals/_web3_step_up.html.erb +0 -292
- data/app/views/studio/solana/_deeplink_assets.html.erb +0 -37
- data/app/views/studio/solana/_phantom_deeplink.html.erb +0 -167
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<%# /admin/knowledge/:id — one document: metadata, access, download, and the
|
|
2
|
+
triage form that files it out of the inbox. %>
|
|
3
|
+
<style>
|
|
4
|
+
.knowledge-chip { display: inline-block; padding: 0 .5rem; border-radius: 9999px;
|
|
5
|
+
font-size: .7rem; line-height: 1.4rem; border: 1px solid transparent; white-space: nowrap; }
|
|
6
|
+
.knowledge-chip-full { background: rgb(16 185 129 / .12); border-color: rgb(16 185 129 / .4); color: rgb(5 150 105); }
|
|
7
|
+
.knowledge-chip-aware { background: rgb(245 158 11 / .12); border-color: rgb(245 158 11 / .4); color: rgb(180 83 9); }
|
|
8
|
+
.knowledge-chip-none { background: rgb(148 163 184 / .12); border-color: rgb(148 163 184 / .4); color: rgb(100 116 139); }
|
|
9
|
+
</style>
|
|
10
|
+
|
|
11
|
+
<div class="mb-4 text-sm">
|
|
12
|
+
<%= link_to "← Knowledge", admin_knowledge_path(folder: @doc.path.presence) %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div class="flex flex-wrap items-start justify-between gap-3 mb-4">
|
|
16
|
+
<div>
|
|
17
|
+
<h1 class="text-xl font-semibold"><%= @doc.title %></h1>
|
|
18
|
+
<div class="text-sm opacity-70"><%= @doc.entity %> · <%= @doc.path.presence || "root" %></div>
|
|
19
|
+
</div>
|
|
20
|
+
<% if @doc.file? %>
|
|
21
|
+
<%= link_to "Download (15-min link)", admin_knowledge_doc_download_path(@doc),
|
|
22
|
+
id: "knowledge-download", class: "px-3 py-1.5 border rounded font-medium" %>
|
|
23
|
+
<% end %>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<dl id="knowledge-doc-meta" class="grid gap-x-8 gap-y-2 md:grid-cols-2 text-sm mb-6">
|
|
27
|
+
<div><dt class="opacity-60">Status</dt><dd><%= @doc.status %></dd></div>
|
|
28
|
+
<div><dt class="opacity-60">Category</dt><dd><%= @doc.category.presence || "—" %></dd></div>
|
|
29
|
+
<div><dt class="opacity-60">Document date</dt><dd><%= @doc.document_date&.iso8601 || "—" %></dd></div>
|
|
30
|
+
<div><dt class="opacity-60">Uploaded</dt><dd><%= @doc.created_at&.to_date&.iso8601 %> by <%= @doc.uploaded_by.presence || "—" %></dd></div>
|
|
31
|
+
<div><dt class="opacity-60">Source</dt><dd><%= @doc.source_note.presence || "—" %></dd></div>
|
|
32
|
+
<div><dt class="opacity-60">File</dt><dd><%= @doc.file? ? "#{@doc.mime_type.presence || 'file'} · #{number_to_human_size(@doc.byte_size) if @doc.byte_size}" : "metadata only" %></dd></div>
|
|
33
|
+
<div class="md:col-span-2"><dt class="opacity-60">Access</dt><dd><%= render "studio/knowledge_docs/access_chips", doc: @doc %></dd></div>
|
|
34
|
+
</dl>
|
|
35
|
+
|
|
36
|
+
<% if @doc.summary.present? %>
|
|
37
|
+
<div class="mb-6">
|
|
38
|
+
<h2 class="font-semibold mb-1">Summary <span class="text-xs opacity-60">(what an "aware" agent may read)</span></h2>
|
|
39
|
+
<p class="text-sm whitespace-pre-line"><%= @doc.summary %></p>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
|
|
43
|
+
<div id="knowledge-triage" class="p-4 border rounded">
|
|
44
|
+
<h2 class="font-semibold mb-3">File it</h2>
|
|
45
|
+
<%= form_with url: admin_knowledge_doc_path(@doc), method: :patch, local: true do |form| %>
|
|
46
|
+
<div class="grid gap-3 md:grid-cols-2">
|
|
47
|
+
<label class="block text-sm">
|
|
48
|
+
<span class="block mb-1">Title</span>
|
|
49
|
+
<%= form.text_field "knowledge_doc[title]", value: @doc.title, class: "block w-full border rounded px-2 py-1" %>
|
|
50
|
+
</label>
|
|
51
|
+
<label class="block text-sm">
|
|
52
|
+
<span class="block mb-1">Folder</span>
|
|
53
|
+
<%= form.text_field "knowledge_doc[path]", value: @doc.path, class: "block w-full border rounded px-2 py-1" %>
|
|
54
|
+
</label>
|
|
55
|
+
<label class="block text-sm">
|
|
56
|
+
<span class="block mb-1">Category</span>
|
|
57
|
+
<%= form.text_field "knowledge_doc[category]", value: @doc.category, class: "block w-full border rounded px-2 py-1" %>
|
|
58
|
+
</label>
|
|
59
|
+
<label class="block text-sm">
|
|
60
|
+
<span class="block mb-1">Document date</span>
|
|
61
|
+
<%= form.date_field "knowledge_doc[document_date]", value: @doc.document_date, class: "block w-full border rounded px-2 py-1" %>
|
|
62
|
+
</label>
|
|
63
|
+
<label class="block text-sm">
|
|
64
|
+
<span class="block mb-1">Status</span>
|
|
65
|
+
<%= form.select "knowledge_doc[status]",
|
|
66
|
+
Studio::KnowledgeDoc::STATUSES.map { |s| [s, s] },
|
|
67
|
+
{ selected: @doc.status }, class: "block w-full border rounded px-2 py-1" %>
|
|
68
|
+
</label>
|
|
69
|
+
<% (Array(Studio.knowledge_agents) | @doc.access.keys).each do |agent| %>
|
|
70
|
+
<label class="block text-sm">
|
|
71
|
+
<span class="block mb-1">Access — <%= agent %></span>
|
|
72
|
+
<%= form.select "knowledge_doc[access][#{agent}]",
|
|
73
|
+
[["none", ""], ["aware", "aware"], ["full", "full"]],
|
|
74
|
+
{ selected: @doc.access_for(agent) == "none" ? "" : @doc.access_for(agent) },
|
|
75
|
+
class: "block w-full border rounded px-2 py-1" %>
|
|
76
|
+
</label>
|
|
77
|
+
<% end %>
|
|
78
|
+
<label class="block text-sm md:col-span-2">
|
|
79
|
+
<span class="block mb-1">Summary (safe for "aware" agents)</span>
|
|
80
|
+
<%= form.text_area "knowledge_doc[summary]", value: @doc.summary, rows: 3, class: "block w-full border rounded px-2 py-1" %>
|
|
81
|
+
</label>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="mt-3">
|
|
84
|
+
<%= form.submit "Save", class: "px-3 py-1.5 border rounded font-medium" %>
|
|
85
|
+
</div>
|
|
86
|
+
<% end %>
|
|
87
|
+
</div>
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
app-supplied-policy age-gate DOB primitive, gated by Studio.feature?(:age_gate),
|
|
19
19
|
plus the app-specific entry-tokens purchase flow documented via engine chrome),
|
|
20
20
|
Profile (crop / upload, opened
|
|
21
|
-
through studio/cropper_assets), Web3 (wallet-connect
|
|
22
|
-
|
|
23
|
-
gated by Studio.feature?(:web3) —
|
|
21
|
+
through studio/cropper_assets), Web3 (wallet-connect and web3-step-up now
|
|
22
|
+
rendered from the solana-studio gem / on-chain-tx / deposit / the generic
|
|
23
|
+
entry-confirmed celebration, gated by Studio.feature?(:web3) —
|
|
24
|
+
disabled-but-present-yet-openable when off, except the two gem-backed ids,
|
|
25
|
+
which an app without solana-studio lists but cannot open),
|
|
24
26
|
System and status (the reusable card blocks), Templates (the copy-from
|
|
25
27
|
archetypes), and Rewards (level-up + the entry-confirmed seeds bar + the
|
|
26
28
|
Free Entry Earned reward, gated by :leveling).
|
|
@@ -33,6 +35,44 @@
|
|
|
33
35
|
leveling_on = Studio.feature?(:leveling)
|
|
34
36
|
age_gate_on = Studio.feature?(:age_gate)
|
|
35
37
|
|
|
38
|
+
# THE WEB3 SPECIMENS NOW COME FROM A GEM THIS ENGINE DOES NOT DEPEND ON.
|
|
39
|
+
# wallet_connect and web3_step_up used to live in app/views/studio/modals and
|
|
40
|
+
# were rendered here unconditionally. The two-template split moved them to
|
|
41
|
+
# solana-studio: BASE is studio-engine + mcritchie-studio, WEB3 ADD is
|
|
42
|
+
# solana-studio + turf-monster. The engine keeps the SESSION half (the
|
|
43
|
+
# /auth/solana routes, Solana::SessionAuth, the phantom callback) and ships no
|
|
44
|
+
# wallet UI at all.
|
|
45
|
+
#
|
|
46
|
+
# So resolution is a RUNTIME question here, not a build-time one. Three of the
|
|
47
|
+
# six apps mounting this engine (acquisition-studio, mcritchie-industries,
|
|
48
|
+
# moms-app) bundle no solana-studio, and an unconditional render would turn
|
|
49
|
+
# their /admin/style into a missing-template 500. The gem is a development and
|
|
50
|
+
# test dependency of this engine precisely so the guide can show the REAL
|
|
51
|
+
# cards; where it is absent the specimens stay listed but unopenable.
|
|
52
|
+
#
|
|
53
|
+
# Same three-term lookup_context.exists? the modal host uses for host_extras
|
|
54
|
+
# (studio/modals/_host.html.erb): name, prefixes, partial.
|
|
55
|
+
web3_gem = lookup_context.exists?("wallet_connect", ["solana_studio/modals"], true)
|
|
56
|
+
|
|
57
|
+
# web3_gem gates the REGISTRATION below. It must also gate the TRIGGER on the
|
|
58
|
+
# three gem-backed cards (Connect wallet + the two Sign Wallet cards), or they
|
|
59
|
+
# stay role=button over a modal id nothing registered: a card that opens an
|
|
60
|
+
# EMPTY panel. The registration gate cannot prevent that on its own — it is
|
|
61
|
+
# what stops the missing-template 500, one layer down.
|
|
62
|
+
#
|
|
63
|
+
# Both terms are load-bearing, because the two gates answer different
|
|
64
|
+
# questions and a BASE app can fail either:
|
|
65
|
+
# openable: web3_gem -> with the capability OFF, the section's
|
|
66
|
+
# disabled-but-openable preview contract would otherwise keep the card
|
|
67
|
+
# clickable (clickable is !(disabled AND NOT openable)).
|
|
68
|
+
# disabled: web3_card_off -> with the capability ON but no gem, `disabled`
|
|
69
|
+
# alone is false, so the card renders as a NORMAL live card: no badge, no
|
|
70
|
+
# aria-disabled, and clickable whatever `openable` says. That is the worse
|
|
71
|
+
# of the two, and it is the exact scenario the BASE-app test runs in.
|
|
72
|
+
# The other six web3 specimens are engine-owned and registered
|
|
73
|
+
# unconditionally, so they keep the plain capability gate.
|
|
74
|
+
web3_card_off = !web3_on || !web3_gem
|
|
75
|
+
|
|
36
76
|
# Auth method-toggle defaults — mirror the app's Studio.auth_method? config;
|
|
37
77
|
# Solana Wallet also needs the web3 capability, so it defaults OFF on an app
|
|
38
78
|
# (like McRitchie Studio) that ships web3 off.
|
|
@@ -432,17 +472,21 @@
|
|
|
432
472
|
<template x-if="$store.dsModals.current().id === 'cosign-rejected'">
|
|
433
473
|
<div><%= render "style/modals/cosign_rejected" %></div>
|
|
434
474
|
</template>
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
475
|
+
<%# Both web3 sign-in cards render from SOLANA-STUDIO, and both are the
|
|
476
|
+
REAL shipped partials rather than style-guide forks — the same files
|
|
477
|
+
turf-monster renders in production, so what is reviewed here IS the
|
|
478
|
+
card that ships. They left this engine with the two-template split.
|
|
479
|
+
Registered only when the gem resolves (see web3_gem above): an app
|
|
480
|
+
that bundles no solana-studio still lists these specimens, it just
|
|
481
|
+
cannot open them. %>
|
|
482
|
+
<% if web3_gem %>
|
|
483
|
+
<template x-if="$store.dsModals.current().id === 'wallet-connect'">
|
|
484
|
+
<div><%= render "style/modals/wallet_connect" %></div>
|
|
485
|
+
</template>
|
|
486
|
+
<template x-if="$store.dsModals.current().id === 'web3-step-up'">
|
|
487
|
+
<div><%= render "solana_studio/modals/web3_step_up", modal_store: "dsModals" %></div>
|
|
488
|
+
</template>
|
|
489
|
+
<% end %>
|
|
446
490
|
<template x-if="$store.dsModals.current().id === 'onchain-tx'">
|
|
447
491
|
<div><%= render "style/modals/onchain_tx" %></div>
|
|
448
492
|
</template>
|
|
@@ -1053,7 +1097,9 @@
|
|
|
1053
1097
|
|
|
1054
1098
|
<%# ===================================================================== %>
|
|
1055
1099
|
<%# 2. WEB3 — the wallet + on-chain modals, in the order a player meets them. %>
|
|
1056
|
-
<%# Gated by :web3; off = disabled-but-present-yet-openable.
|
|
1100
|
+
<%# Gated by :web3; off = disabled-but-present-yet-openable. The three
|
|
1101
|
+
gem-backed cards carry a second gate, web3_card_off, because a preview
|
|
1102
|
+
you can open needs something registered to open. %>
|
|
1057
1103
|
<%# ===================================================================== %>
|
|
1058
1104
|
<section id="modals-web3" class="space-y-5">
|
|
1059
1105
|
<div class="space-y-1">
|
|
@@ -1070,6 +1116,12 @@
|
|
|
1070
1116
|
<code class="font-mono text-2xs">Studio.feature?(:web3)</code>. Web3 is
|
|
1071
1117
|
<strong><%= web3_on ? "on" : "off" %></strong> here, so these render
|
|
1072
1118
|
<%= web3_on ? "live." : "greyed and badged — but STILL openable as a preview." %>
|
|
1119
|
+
<% unless web3_gem %>
|
|
1120
|
+
<strong>This app bundles no solana-studio</strong>, so the three cards it
|
|
1121
|
+
backs — Connect wallet and the two Sign Wallet states — are
|
|
1122
|
+
listed for reference but <strong>cannot be opened here</strong>. Nothing
|
|
1123
|
+
registers them, so a trigger would open an empty panel.
|
|
1124
|
+
<% end %>
|
|
1073
1125
|
The cards run in the order a player meets them — <strong>get a wallet,
|
|
1074
1126
|
prove it, spend from it</strong>: Connect wallet → Setup Wallet →
|
|
1075
1127
|
the two Sign Wallet states → Processing → success or error →
|
|
@@ -1116,10 +1168,10 @@
|
|
|
1116
1168
|
<%# 1. Connect wallet — the walk's entry; picking a wallet swaps to Processing. %>
|
|
1117
1169
|
<%= render layout: "style/modal_specimen", locals: {
|
|
1118
1170
|
label: "Connect wallet",
|
|
1119
|
-
reference: %(the Web3 "Connect wallet" picker (studio
|
|
1171
|
+
reference: %(the Web3 "Connect wallet" picker (solana-studio solana_studio/modals/_wallet_connect, configured by style/modals/_wallet_connect) — the walk's entry: picking a wallet runs a brief connecting state then swaps to the on-chain Processing modal. Open with $store.dsModals.open('wallet-connect')),
|
|
1120
1172
|
open_expr: "$store.dsModals.open('wallet-connect')",
|
|
1121
1173
|
glow_when: ds_glow.call("wallet-connect"),
|
|
1122
|
-
disabled:
|
|
1174
|
+
disabled: web3_card_off, openable: web3_gem } do %>
|
|
1123
1175
|
<%# A PICKER: brand tiles with names beside them, which is what this modal
|
|
1124
1176
|
actually is. Two empty outlines said "form with two inputs" and were
|
|
1125
1177
|
the least informative sketch in the section — the one card whose
|
|
@@ -1182,10 +1234,10 @@
|
|
|
1182
1234
|
wallet, so no user can ever meet both. %>
|
|
1183
1235
|
<%= render layout: "style/modal_specimen", locals: {
|
|
1184
1236
|
label: "Sign Wallet",
|
|
1185
|
-
reference: %(the Web3 "Sign Wallet" card (studio/modals/_web3_step_up — the REAL shared partial, not a specimen copy) — shown to an account that holds a self-custody wallet but authenticated this session with a web2 credential. One wallet row, glowing, because it is the only thing to press. Open with $store.dsModals.open('web3-step-up', { provider: 'phantom', providerLabel: 'Phantom', walletHint: '7xKp…JZ2Q' })),
|
|
1237
|
+
reference: %(the Web3 "Sign Wallet" card (solana-studio solana_studio/modals/_web3_step_up — the REAL shared partial, not a specimen copy) — shown to an account that holds a self-custody wallet but authenticated this session with a web2 credential. One wallet row, glowing, because it is the only thing to press. Open with $store.dsModals.open('web3-step-up', { provider: 'phantom', providerLabel: 'Phantom', walletHint: '7xKp…JZ2Q' })),
|
|
1186
1238
|
open_expr: "$store.dsModals.open('web3-step-up', { provider: 'phantom', providerLabel: 'Phantom', walletHint: '7xKp…JZ2Q' })",
|
|
1187
1239
|
glow_when: ds_glow.call("web3-step-up", provider: true),
|
|
1188
|
-
disabled:
|
|
1240
|
+
disabled: web3_card_off, openable: web3_gem } do %>
|
|
1189
1241
|
<%# A FILLED row: brand tile, the wallet's name, an Installed badge. The
|
|
1190
1242
|
two step-up thumbnails used to differ by a single border-dashed class
|
|
1191
1243
|
on a 20px row, which is not a difference anyone can see at thumbnail
|
|
@@ -1205,10 +1257,10 @@
|
|
|
1205
1257
|
|
|
1206
1258
|
<%= render layout: "style/modal_specimen", locals: {
|
|
1207
1259
|
label: "Sign Wallet (no remembered brand)",
|
|
1208
|
-
reference: %(the Web3 "Sign Wallet" card with NO remembered brand (studio/modals/_web3_step_up) — every wallet linked before a host recorded brands lands here, so it is a live population and not a defensive branch. The row falls back to the picker rather than dead-ending. Open with $store.dsModals.open('web3-step-up', {})),
|
|
1260
|
+
reference: %(the Web3 "Sign Wallet" card with NO remembered brand (solana-studio solana_studio/modals/_web3_step_up) — every wallet linked before a host recorded brands lands here, so it is a live population and not a defensive branch. The row falls back to the picker rather than dead-ending. Open with $store.dsModals.open('web3-step-up', {})),
|
|
1209
1261
|
open_expr: "$store.dsModals.open('web3-step-up', {})",
|
|
1210
1262
|
glow_when: ds_glow.call("web3-step-up", provider: false),
|
|
1211
|
-
disabled:
|
|
1263
|
+
disabled: web3_card_off, openable: web3_gem } do %>
|
|
1212
1264
|
<%# An EMPTY slot: dashed outline, no brand tile, a "?" where the wallet's
|
|
1213
1265
|
name would be. Reads at a glance as "we do not know which wallet",
|
|
1214
1266
|
which is the whole difference between this card and the one before it. %>
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
<%#
|
|
2
2
|
"Connect Wallet" specimen for the living style guide.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
the apps render, so a drift between the specimen and production is no longer
|
|
4
|
+
This is a THIN CONFIGURATION of the real picker rather than a parallel port of
|
|
5
|
+
it. That is the point: the card on /admin/style demonstrates the SAME code the
|
|
6
|
+
apps render, so a drift between the specimen and production is no longer
|
|
8
7
|
possible. Before this, the picker existed three times — turf-monster's,
|
|
9
8
|
mcritchie-studio's, and this one — and only this one was ever looked at.
|
|
10
9
|
|
|
10
|
+
THE PARTIAL IS NO LONGER THIS ENGINE'S. It moved to solana-studio
|
|
11
|
+
(solana_studio/modals/_wallet_connect) with the two-template split: BASE is
|
|
12
|
+
studio-engine + mcritchie-studio, WEB3 ADD is solana-studio + turf-monster.
|
|
13
|
+
The engine keeps the session half and ships no wallet UI. Nothing renders this
|
|
14
|
+
file unless the gem resolved — style/_modals.html.erb gates the registration
|
|
15
|
+
on web3_gem — so an app with no solana-studio never reaches this render.
|
|
16
|
+
|
|
11
17
|
What the configuration supplies, all of it demo scaffolding:
|
|
12
18
|
store — the guide's page-scoped host ($store.dsModals), not the app
|
|
13
19
|
host, so the specimen never collides with a consuming app's
|
|
@@ -75,7 +81,7 @@
|
|
|
75
81
|
}
|
|
76
82
|
JS
|
|
77
83
|
%>
|
|
78
|
-
<%= render "
|
|
84
|
+
<%= render "solana_studio/modals/wallet_connect",
|
|
79
85
|
store: "dsModals",
|
|
80
86
|
connect_fn: "dsWalletConnectDemo",
|
|
81
87
|
slot: "style/modals/wallet_connect_slot",
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Reference migration for the Studio::KnowledgeDoc model. Like studio_links,
|
|
2
|
+
# each consumer app installs its own copy into db/migrate
|
|
3
|
+
# (`bin/rails studio_engine:install:migrations && bin/rails db:migrate`) so the
|
|
4
|
+
# table is created in the app's database. Do not hand-copy — a hand copy
|
|
5
|
+
# collides with the installed copy on `class CreateStudioKnowledgeDocs`.
|
|
6
|
+
class CreateStudioKnowledgeDocs < ActiveRecord::Migration[7.2]
|
|
7
|
+
def change
|
|
8
|
+
create_table :studio_knowledge_docs do |t|
|
|
9
|
+
t.string :title, null: false
|
|
10
|
+
# Which business the document belongs to (e.g. "commercial-welding-llc").
|
|
11
|
+
# One app can host several entities' knowledge; every read is entity-scoped.
|
|
12
|
+
t.string :entity, null: false
|
|
13
|
+
# Folder path with no leading/trailing slash ("financials/aging-inventory/2026-08").
|
|
14
|
+
# Folders are implicit — they exist because a document claims the path —
|
|
15
|
+
# and the S3 key mirrors it, so the bucket stays human-browsable.
|
|
16
|
+
t.string :path, null: false, default: ""
|
|
17
|
+
t.string :category
|
|
18
|
+
t.string :mime_type
|
|
19
|
+
# The document's own as-of date (an August aging report dated August),
|
|
20
|
+
# distinct from created_at (when it was uploaded).
|
|
21
|
+
t.date :document_date
|
|
22
|
+
# inbox -> filed -> superseded. Uploads land as inbox for agent triage.
|
|
23
|
+
t.string :status, null: false, default: "inbox"
|
|
24
|
+
# Per-agent access map: {"samson" => "full", "dawn" => "aware"}.
|
|
25
|
+
# "aware" = the agent knows the document exists and gets the safe summary,
|
|
26
|
+
# not the contents; absent agents default to "none".
|
|
27
|
+
t.jsonb :access, null: false, default: {}
|
|
28
|
+
t.jsonb :tags, null: false, default: []
|
|
29
|
+
# The safe summary — what an "aware" agent may read and repeat.
|
|
30
|
+
t.text :summary
|
|
31
|
+
# Provenance: who/where the document came from ("broker email 2026-08-30").
|
|
32
|
+
t.string :source_note
|
|
33
|
+
t.string :uploaded_by
|
|
34
|
+
# Object storage pointer (Studio::S3 logical key); nil for a metadata-only
|
|
35
|
+
# record. byte_size is captured at attach time for the index view.
|
|
36
|
+
t.string :s3_key
|
|
37
|
+
t.bigint :byte_size
|
|
38
|
+
# Set on the OLD row when a newer document replaces it.
|
|
39
|
+
t.bigint :superseded_by_id
|
|
40
|
+
|
|
41
|
+
t.timestamps
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
add_index :studio_knowledge_docs, [:entity, :status]
|
|
45
|
+
add_index :studio_knowledge_docs, [:entity, :path]
|
|
46
|
+
add_index :studio_knowledge_docs, :s3_key, unique: true
|
|
47
|
+
add_index :studio_knowledge_docs, :superseded_by_id
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/studio/version.rb
CHANGED
data/lib/studio.rb
CHANGED
|
@@ -554,6 +554,14 @@ module Studio
|
|
|
554
554
|
# A trailing slash is added if you leave it off.
|
|
555
555
|
mattr_accessor :s3_key_prefix, default: nil
|
|
556
556
|
|
|
557
|
+
# Knowledge layer — the S3-backed document store + /admin/knowledge browser
|
|
558
|
+
# (Studio::KnowledgeDoc). Routes are opt-in like every route surface.
|
|
559
|
+
# knowledge_agents is the roster of agent slugs the intake UI offers
|
|
560
|
+
# per-agent access selects for (e.g. %w[samson dawn] on Industries); the
|
|
561
|
+
# access map itself accepts any agent slug regardless.
|
|
562
|
+
mattr_accessor :draw_knowledge_routes, default: false
|
|
563
|
+
mattr_accessor :knowledge_agents, default: []
|
|
564
|
+
|
|
557
565
|
class S3ConfigError < StandardError; end
|
|
558
566
|
|
|
559
567
|
# Whether to validate the host app's User model at boot. See docs/USER_CONTRACT.md.
|
|
@@ -846,6 +854,19 @@ module Studio
|
|
|
846
854
|
constraints: { token: %r{[^/]+} }
|
|
847
855
|
end
|
|
848
856
|
|
|
857
|
+
# Knowledge layer — /admin/knowledge (Studio::KnowledgeDoc): folder/flat
|
|
858
|
+
# document browser, upload-to-inbox intake, per-agent access map, and
|
|
859
|
+
# 15-minute presigned downloads. Opt-in (default off) like every route
|
|
860
|
+
# surface, so no app grows an admin page it never asked for.
|
|
861
|
+
if Studio.draw_knowledge_routes
|
|
862
|
+
get "admin/knowledge", to: "studio/knowledge_docs#index", as: :admin_knowledge
|
|
863
|
+
post "admin/knowledge", to: "studio/knowledge_docs#create"
|
|
864
|
+
get "admin/knowledge/:id", to: "studio/knowledge_docs#show", as: :admin_knowledge_doc
|
|
865
|
+
patch "admin/knowledge/:id", to: "studio/knowledge_docs#update"
|
|
866
|
+
get "admin/knowledge/:id/download", to: "studio/knowledge_docs#download",
|
|
867
|
+
as: :admin_knowledge_doc_download
|
|
868
|
+
end
|
|
869
|
+
|
|
849
870
|
# Solana / Phantom wallet sign-in (nonce challenge + signature verify),
|
|
850
871
|
# plus the MOBILE deep-link callback Phantom redirects back to. The
|
|
851
872
|
# callback used to be listed here as app-specific and is not any more —
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: studio-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.67.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
@@ -328,6 +328,7 @@ files:
|
|
|
328
328
|
- app/controllers/studio/email_images_controller.rb
|
|
329
329
|
- app/controllers/studio/emails_controller.rb
|
|
330
330
|
- app/controllers/studio/geo_settings_controller.rb
|
|
331
|
+
- app/controllers/studio/knowledge_docs_controller.rb
|
|
331
332
|
- app/controllers/studio/links_controller.rb
|
|
332
333
|
- app/controllers/studio/local_emails_controller.rb
|
|
333
334
|
- app/controllers/studio/local_reviews_controller.rb
|
|
@@ -361,6 +362,7 @@ files:
|
|
|
361
362
|
- app/models/studio/email_setting.rb
|
|
362
363
|
- app/models/studio/enumeral.rb
|
|
363
364
|
- app/models/studio/geo_setting.rb
|
|
365
|
+
- app/models/studio/knowledge_doc.rb
|
|
364
366
|
- app/models/studio/link.rb
|
|
365
367
|
- app/models/studio/model_page.rb
|
|
366
368
|
- app/models/theme_setting.rb
|
|
@@ -436,6 +438,12 @@ files:
|
|
|
436
438
|
- app/views/studio/emails/show.html.erb
|
|
437
439
|
- app/views/studio/fields/_date_of_birth.html.erb
|
|
438
440
|
- app/views/studio/geo_settings/edit.html.erb
|
|
441
|
+
- app/views/studio/knowledge_docs/_access_chips.html.erb
|
|
442
|
+
- app/views/studio/knowledge_docs/_browser.html.erb
|
|
443
|
+
- app/views/studio/knowledge_docs/_doc_row.html.erb
|
|
444
|
+
- app/views/studio/knowledge_docs/_upload_form.html.erb
|
|
445
|
+
- app/views/studio/knowledge_docs/index.html.erb
|
|
446
|
+
- app/views/studio/knowledge_docs/show.html.erb
|
|
439
447
|
- app/views/studio/links/confirm.html.erb
|
|
440
448
|
- app/views/studio/local_emails/index.html.erb
|
|
441
449
|
- app/views/studio/mailers/_layered_banner.html.erb
|
|
@@ -445,8 +453,6 @@ files:
|
|
|
445
453
|
- app/views/studio/modals/_load_convention.html.erb
|
|
446
454
|
- app/views/studio/modals/_saving.html.erb
|
|
447
455
|
- app/views/studio/modals/_scoped_host.html.erb
|
|
448
|
-
- app/views/studio/modals/_wallet_connect.html.erb
|
|
449
|
-
- app/views/studio/modals/_web3_step_up.html.erb
|
|
450
456
|
- app/views/studio/modals/auth/_resend_footer.html.erb
|
|
451
457
|
- app/views/studio/modals/blocks/_age_gate.html.erb
|
|
452
458
|
- app/views/studio/modals/blocks/_birthday.html.erb
|
|
@@ -499,8 +505,6 @@ files:
|
|
|
499
505
|
- app/views/studio/profiles/_save_controls.html.erb
|
|
500
506
|
- app/views/studio/profiles/edit.html.erb
|
|
501
507
|
- app/views/studio/profiles/show.html.erb
|
|
502
|
-
- app/views/studio/solana/_deeplink_assets.html.erb
|
|
503
|
-
- app/views/studio/solana/_phantom_deeplink.html.erb
|
|
504
508
|
- app/views/style/_modal_specimen.html.erb
|
|
505
509
|
- app/views/style/_modals.html.erb
|
|
506
510
|
- app/views/style/_specimen.html.erb
|
|
@@ -548,6 +552,7 @@ files:
|
|
|
548
552
|
- db/migrate/20260813010000_add_body_cta_footer_to_studio_email_settings.rb
|
|
549
553
|
- db/migrate/20260813220000_add_standard_user_profile_columns.rb
|
|
550
554
|
- db/migrate/20260818120000_create_studio_geo_settings.rb
|
|
555
|
+
- db/migrate/20260901000001_create_studio_knowledge_docs.rb
|
|
551
556
|
- lib/studio-engine.rb
|
|
552
557
|
- lib/studio.rb
|
|
553
558
|
- lib/studio/cable.rb
|