diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/js/tablet_lock.js b/fusion_plating/fusion_plating_shopfloor/static/src/js/tablet_lock.js
index ea6287bb..4909bc2a 100644
--- a/fusion_plating/fusion_plating_shopfloor/static/src/js/tablet_lock.js
+++ b/fusion_plating/fusion_plating_shopfloor/static/src/js/tablet_lock.js
@@ -34,6 +34,9 @@ export class FpTabletLock extends Component {
this.techStore = useService("fp_shopfloor_tech_store");
this.activity = useService("fp_shopfloor_activity");
this.notification = useService("notification");
+ // Phase D: idle + ceiling timer for session_swap mode. Started
+ // once tiles bootstrap shows we're already on a tech session.
+ this.tabletSessionManager = useService("fp_tablet_session_manager");
this.state = useState({
tiles: [],
@@ -46,6 +49,10 @@ export class FpTabletLock extends Component {
clockText: this._formatTime(new Date()),
dateText: this._formatDate(new Date()),
company: null,
+ // Phase D — feature flag + kiosk identity from bootstrap
+ sessionMode: "legacy", // 'legacy' or 'session_swap'
+ kioskUid: null,
+ currentUid: null,
});
onMounted(async () => {
@@ -65,16 +72,33 @@ export class FpTabletLock extends Component {
this.state.clockText = this._formatTime(now);
this.state.dateText = this._formatDate(now);
}, 60000);
+ // Session-swap mode: if we're already on a TECH session (uid
+ // != kiosk), start the idle/ceiling timer immediately. This
+ // handles the case where the page was reloaded after
+ // unlock_session minted the tech's session.
+ if (this.state.sessionMode === "session_swap"
+ && this.state.currentUid
+ && this.state.currentUid !== this.state.kioskUid) {
+ this.tabletSessionManager.beginSession();
+ }
});
onWillUnmount(() => {
if (this._tick) clearInterval(this._tick);
if (this._ping) clearInterval(this._ping);
if (this._clockInterval) clearInterval(this._clockInterval);
+ this.tabletSessionManager.endSession();
});
}
get isLocked() {
+ // SESSION-SWAP MODE: the BROWSER session itself tells us whether
+ // a tech is unlocked — current_uid != kiosk_uid means unlocked.
+ // LEGACY MODE: defer to the techStore client-side flag.
+ if (this.state.sessionMode === "session_swap") {
+ return !this.state.currentUid
+ || this.state.currentUid === this.state.kioskUid;
+ }
return this.techStore.isLocked;
}
@@ -85,6 +109,11 @@ export class FpTabletLock extends Component {
const res = await rpc("/fp/tablet/tiles", { station_id: stationId });
if (res && res.ok) {
this.state.company = res.company || null;
+ // Phase D — capture session_mode + kiosk/current uids so
+ // unlock() / isLocked / handOff can branch on mode.
+ this.state.sessionMode = res.tablet_session_mode || "legacy";
+ this.state.kioskUid = res.kiosk_uid || null;
+ this.state.currentUid = res.current_uid || null;
// Decorate each tile with an animation-delay (50ms staggered,
// capped at 300ms so the screen doesn't take 3s to settle on
// shops with 20+ operators).
@@ -127,6 +156,25 @@ export class FpTabletLock extends Component {
async unlock(pin) {
try {
+ // SESSION-SWAP MODE: call the new endpoint, then reload the
+ // page so the browser re-bootstraps under the tech's session.
+ if (this.state.sessionMode === "session_swap") {
+ const res = await rpc("/fp/tablet/unlock_session", {
+ user_id: this.state.selectedTileUserId,
+ pin,
+ });
+ if (res && res.ok) {
+ // Cookie has swapped. Reload so OWL/services re-init
+ // under the new (tech) session. The session manager
+ // (Task D1) picks up on the next page load.
+ window.location.reload();
+ // Return a pending state so the caller doesn't try to
+ // navigate while we're tearing down.
+ return { ok: true, reloading: true };
+ }
+ return { ok: false, error: (res && res.error) || "Unlock failed" };
+ }
+ // LEGACY MODE: existing /fp/tablet/unlock path
const res = await rpc("/fp/tablet/unlock", {
user_id: this.state.selectedTileUserId,
pin,
@@ -148,6 +196,13 @@ export class FpTabletLock extends Component {
}
handOff() {
+ // SESSION-SWAP MODE: the server destroys the tech session, then
+ // we reload to re-bootstrap as the kiosk.
+ if (this.state.sessionMode === "session_swap") {
+ this.tabletSessionManager.lockBack("manual");
+ return;
+ }
+ // LEGACY MODE: client-side state flip only.
this.techStore.lock();
this.state.selectedTileUserId = null;
this.state.idleSecondsRemaining = null;
diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/xml/tablet_lock.xml b/fusion_plating/fusion_plating_shopfloor/static/src/xml/tablet_lock.xml
index e9626953..12d3c549 100644
--- a/fusion_plating/fusion_plating_shopfloor/static/src/xml/tablet_lock.xml
+++ b/fusion_plating/fusion_plating_shopfloor/static/src/xml/tablet_lock.xml
@@ -75,6 +75,20 @@
+
+