diff --git a/README.md b/README.md
index 4dcbb94d..19da9086 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
 https://github.com/kawre/leetcode.nvim/assets/69250723/aee6584c-e099-4409-b114-123cb32b7563
 
 > [!CAUTION]
-> This plugin has been exclusively tested with `Java`.
+> This plugin has been primarily tested with `Java`.
 > If you encounter any errors while using other languages,
 > please open an issue to report them.
 
@@ -304,7 +304,7 @@ image_support = false,
 
 - `daily` opens the question of today
 
-- `list` opens a problemlist picker
+- `list` opens a problem list picker
 
 - `open` opens the current question in a default browser
 
@@ -312,6 +312,15 @@ image_support = false,
 
 - `last_submit` retrieve last submitted code for the current question
 
+- `restore` try to restore default question layout
+
+- `session`
+
+  - `create` create a new session
+  - `change` change the current session
+
+  - `update` update the current session in case it went out of sync
+
 - `desc` toggle question description
 
   - `toggle` same as `Leet desc`
diff --git a/README.zh.md b/README.zh.md
index 7f88443a..7f002dfe 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -314,6 +314,16 @@ image_support = false, -- 将此设置为 `true` 将禁用问题描述的换行
 
 - `last_submit` 检索上次提交的代码，用于当前问题
 
+- `restore` 尝试恢复默认问题布局
+
+- `session`
+
+  - `create` 创建一个新的会话
+
+  - `change` 更改当前会话
+
+  - `update` 更新当前会话，以防它失去同步
+
 - `desc` 切换问题描述
 
   - `toggle` 与 `Leet desc` 相同
diff --git a/lua/leetcode-plugins/cn/api.lua b/lua/leetcode-plugins/cn/api.lua
index 1fd6e137..70297828 100644
--- a/lua/leetcode-plugins/cn/api.lua
+++ b/lua/leetcode-plugins/cn/api.lua
@@ -28,6 +28,25 @@ statistics.solved = function(cb) ---@diagnostic disable-line
     })
 end
 
+---@param cb fun(res: lc.Stats.QuestionCount[], err: lc.err)
+statistics.session_progress = function(cb)
+    local variables = {
+        userSlug = config.auth.name,
+    }
+
+    local query = queries.session_progress
+
+    utils.query(query, variables, {
+        callback = function(res, err)
+            if err then return cb(nil, err) end
+
+            local data = res.data
+            local session_progress = data["userProfileUserQuestionProgress"]["numAcceptedQuestions"]
+            cb(session_progress)
+        end,
+    })
+end
+
 statistics.calendar = function(cb) ---@diagnostic disable-line
     local variables = {
         username = config.auth.name,
diff --git a/lua/leetcode-plugins/cn/init.lua b/lua/leetcode-plugins/cn/init.lua
index 2ccd1880..6735a3a9 100644
--- a/lua/leetcode-plugins/cn/init.lua
+++ b/lua/leetcode-plugins/cn/init.lua
@@ -11,6 +11,7 @@ function cn.load()
     config.translator = config.user.cn.translator
     config.domain = "cn"
     config.is_cn = true
+    config.sessions.default = "匿名"
 
     require("leetcode-plugins.cn.urls")
     require("leetcode-plugins.cn.queries")
diff --git a/lua/leetcode-plugins/cn/queries.lua b/lua/leetcode-plugins/cn/queries.lua
index 076207b9..f9d9d1f9 100644
--- a/lua/leetcode-plugins/cn/queries.lua
+++ b/lua/leetcode-plugins/cn/queries.lua
@@ -139,3 +139,24 @@ queries.skills = [[
             }
         }
     ]]
+
+queries.streak = [[
+        query getStreakCounter {
+            streakCounter: problemsetStreakCounter {
+                streakCount
+                daysSkipped
+                todayCompleted
+            }
+        }
+    ]]
+
+queries.session_progress = [[
+        query userSessionProgress($userSlug: String!) {
+            userProfileUserQuestionProgress(userSlug: $userSlug) {
+                numAcceptedQuestions {
+                    difficulty
+                    count
+                }
+            }
+        }
+    ]]
diff --git a/lua/leetcode-plugins/cn/urls.lua b/lua/leetcode-plugins/cn/urls.lua
index 083d32f9..2285fba3 100644
--- a/lua/leetcode-plugins/cn/urls.lua
+++ b/lua/leetcode-plugins/cn/urls.lua
@@ -12,3 +12,4 @@ urls.interpret = "/problems/%s/interpret_solution/"
 urls.submit = "/problems/%s/submit/"
 urls.run = "/problems/%s/interpret_solution/"
 urls.check = "/submissions/detail/%s/check/"
+urls.streak_counter = "/graphql/noj-go/"
diff --git a/lua/leetcode-ui/group/buttons/menu.lua b/lua/leetcode-ui/group/buttons/menu.lua
index a9a62582..9de3710a 100644
--- a/lua/leetcode-ui/group/buttons/menu.lua
+++ b/lua/leetcode-ui/group/buttons/menu.lua
@@ -6,7 +6,7 @@ local MenuButtons = Group:extend("LeetMenuButtons")
 function MenuButtons:init(buttons, opts)
     opts = vim.tbl_deep_extend("force", {
         padding = {
-            bot = 1,
+            bot = 2,
         },
         spacing = 1,
     }, opts or {})
diff --git a/lua/leetcode-ui/group/cases.lua b/lua/leetcode-ui/group/cases.lua
index d7be7fbf..e12d694c 100644
--- a/lua/leetcode-ui/group/cases.lua
+++ b/lua/leetcode-ui/group/cases.lua
@@ -23,7 +23,7 @@ function Cases:make_nav()
         self.console.result:map("n", i, function() self:change(i) end, { clearable = true })
 
         local hl = self:nav_case_hl(case, i)
-        local msg = (" Case (%d) "):format(i)
+        local msg = (" Case (%d) "):format(i)
 
         nav:append(msg, hl)
         if i ~= #self.cases then nav:append(" ") end
diff --git a/lua/leetcode-ui/group/page/cache.lua b/lua/leetcode-ui/group/page/cache.lua
index e5c2da23..c2898ccc 100644
--- a/lua/leetcode-ui/group/page/cache.lua
+++ b/lua/leetcode-ui/group/page/cache.lua
@@ -1,17 +1,17 @@
-local Header = require("leetcode-ui.lines.menu-header")
+local cmd = require("leetcode.command")
+
 local Title = require("leetcode-ui.lines.title")
-local Footer = require("leetcode-ui.lines.footer")
 local Buttons = require("leetcode-ui.group.buttons.menu")
 local Page = require("leetcode-ui.group.page")
-
 local Button = require("leetcode-ui.lines.button.menu")
 local BackButton = require("leetcode-ui.lines.button.menu.back")
 
-local cmd = require("leetcode.command")
+local header = require("leetcode-ui.lines.menu-header")
+local footer = require("leetcode-ui.lines.footer")
 
 local page = Page()
 
-page:insert(Header())
+page:insert(header)
 
 page:insert(Title({ "Menu" }, "Cache"))
 
@@ -28,6 +28,6 @@ page:insert(Buttons({
     back_btn,
 }))
 
-page:insert(Footer())
+page:insert(footer)
 
 return page
diff --git a/lua/leetcode-ui/group/page/cookie.lua b/lua/leetcode-ui/group/page/cookie.lua
index 8c7163da..81cab3da 100644
--- a/lua/leetcode-ui/group/page/cookie.lua
+++ b/lua/leetcode-ui/group/page/cookie.lua
@@ -1,17 +1,17 @@
-local Header = require("leetcode-ui.lines.menu-header")
+local cmd = require("leetcode.command")
+
 local Title = require("leetcode-ui.lines.title")
-local Footer = require("leetcode-ui.lines.footer")
 local Buttons = require("leetcode-ui.group.buttons.menu")
 local Page = require("leetcode-ui.group.page")
-
 local Button = require("leetcode-ui.lines.button.menu")
 local BackButton = require("leetcode-ui.lines.button.menu.back")
 
-local cmd = require("leetcode.command")
+local header = require("leetcode-ui.lines.menu-header")
+local footer = require("leetcode-ui.lines.footer")
 
 local page = Page()
 
-page:insert(Header())
+page:insert(header)
 
 page:insert(Title({ "Menu" }, "Cookie"))
 
@@ -35,6 +35,6 @@ page:insert(Buttons({
     back,
 }))
 
-page:insert(Footer())
+page:insert(footer)
 
 return page
diff --git a/lua/leetcode-ui/group/page/loading.lua b/lua/leetcode-ui/group/page/loading.lua
index c5f3e354..fc63a6b0 100644
--- a/lua/leetcode-ui/group/page/loading.lua
+++ b/lua/leetcode-ui/group/page/loading.lua
@@ -1,14 +1,13 @@
 local Page = require("leetcode-ui.group.page")
-
-local Header = require("leetcode-ui.lines.menu-header")
 local Buttons = require("leetcode-ui.group.buttons.menu")
-
 local ExitButton = require("leetcode-ui.lines.button.menu.exit")
 local Title = require("leetcode-ui.lines.title")
 
+local header = require("leetcode-ui.lines.menu-header")
+
 local page = Page()
 
-page:insert(Header())
+page:insert(header)
 
 page:insert(Title({}, "Loading..."))
 
diff --git a/lua/leetcode-ui/group/page/menu.lua b/lua/leetcode-ui/group/page/menu.lua
index d61e6be6..1ae2199c 100644
--- a/lua/leetcode-ui/group/page/menu.lua
+++ b/lua/leetcode-ui/group/page/menu.lua
@@ -1,45 +1,45 @@
+local cmd = require("leetcode.command")
+
 local Page = require("leetcode-ui.group.page")
 local Title = require("leetcode-ui.lines.title")
 local Buttons = require("leetcode-ui.group.buttons.menu")
-local Header = require("leetcode-ui.lines.menu-header")
-local Footer = require("leetcode-ui.lines.footer")
-
 local Button = require("leetcode-ui.lines.button.menu")
 local ExitButton = require("leetcode-ui.lines.button.menu.exit")
 
-local cmd = require("leetcode.command")
+local header = require("leetcode-ui.lines.menu-header")
+local footer = require("leetcode-ui.lines.footer")
 
 local page = Page()
 
-page:insert(Header())
+page:insert(header)
 
 page:insert(Title({}, "Menu"))
 
 local problems = Button("Problems", {
     icon = "",
     sc = "p",
-    on_press = function() cmd.menu_layout("problems") end,
+    on_press = function() cmd.set_menu_page("problems") end,
     expandable = true,
 })
 
 local statistics = Button("Statistics", {
     icon = "󰄪",
     sc = "s",
-    on_press = function() cmd.menu_layout("stats") end,
+    on_press = function() cmd.set_menu_page("stats") end,
     expandable = true,
 })
 
 local cookie = Button("Cookie", {
     icon = "󰆘",
     sc = "i",
-    on_press = function() cmd.menu_layout("cookie") end,
+    on_press = function() cmd.set_menu_page("cookie") end,
     expandable = true,
 })
 
 local cache = Button("Cache", {
     icon = "",
     sc = "c",
-    on_press = function() cmd.menu_layout("cache") end,
+    on_press = function() cmd.set_menu_page("cache") end,
     expandable = true,
 })
 
@@ -53,6 +53,6 @@ page:insert(Buttons({
     exit,
 }))
 
-page:insert(Footer())
+page:insert(footer)
 
 return page
diff --git a/lua/leetcode-ui/group/page/problems.lua b/lua/leetcode-ui/group/page/problems.lua
index 470c6192..8e0cfcdc 100644
--- a/lua/leetcode-ui/group/page/problems.lua
+++ b/lua/leetcode-ui/group/page/problems.lua
@@ -1,17 +1,17 @@
 local cmd = require("leetcode.command")
 
 local Title = require("leetcode-ui.lines.title")
-local Footer = require("leetcode-ui.lines.footer")
-local Header = require("leetcode-ui.lines.menu-header")
-
 local Button = require("leetcode-ui.lines.button.menu")
 local BackButton = require("leetcode-ui.lines.button.menu.back")
 local Buttons = require("leetcode-ui.group.buttons.menu")
 local Page = require("leetcode-ui.group.page")
 
+local footer = require("leetcode-ui.lines.footer")
+local header = require("leetcode-ui.lines.menu-header")
+
 local page = Page()
 
-page:insert(Header())
+page:insert(header)
 
 page:insert(Title({ "Menu" }, "Problems"))
 
@@ -42,6 +42,6 @@ page:insert(Buttons({
     back,
 }))
 
-page:insert(Footer())
+page:insert(footer)
 
 return page
diff --git a/lua/leetcode-ui/group/page/signin.lua b/lua/leetcode-ui/group/page/signin.lua
index 4927e2ab..0ad0a4bc 100644
--- a/lua/leetcode-ui/group/page/signin.lua
+++ b/lua/leetcode-ui/group/page/signin.lua
@@ -1,18 +1,18 @@
+local cmd = require("leetcode.command")
+local config = require("leetcode.config")
+
 local Page = require("leetcode-ui.group.page")
 local Title = require("leetcode-ui.lines.title")
 local Buttons = require("leetcode-ui.group.buttons.menu")
-local Header = require("leetcode-ui.lines.menu-header")
-local Footer = require("leetcode-ui.lines.footer")
-
+local Group = require("leetcode-ui.group")
 local Button = require("leetcode-ui.lines.button.menu")
 local ExitButton = require("leetcode-ui.lines.button.menu.exit")
 
-local cmd = require("leetcode.command")
-local config = require("leetcode.config")
+local header = require("leetcode-ui.lines.menu-header")
 
 local page = Page()
 
-page:insert(Header())
+page:insert(header)
 
 page:insert(Title({}, "Sign in"))
 
@@ -29,7 +29,9 @@ page:insert(Buttons({
     exit,
 }))
 
-local footer = Footer()
+local footer = Group({}, {
+    hl = "Number",
+})
 footer:append("leetcode." .. config.domain)
 page:insert(footer)
 
diff --git a/lua/leetcode-ui/group/page/stats.lua b/lua/leetcode-ui/group/page/stats.lua
index 0d182bb1..4eab90ef 100644
--- a/lua/leetcode-ui/group/page/stats.lua
+++ b/lua/leetcode-ui/group/page/stats.lua
@@ -1,15 +1,16 @@
+local cmd = require("leetcode.command")
+local config = require("leetcode.config")
+
 local Solved = require("leetcode-ui.lines.solved")
 local Calendar = require("leetcode-ui.lines.calendar")
 local Group = require("leetcode-ui.group")
-local Footer = require("leetcode-ui.lines.footer")
 local Page = require("leetcode-ui.group.page")
 local Buttons = require("leetcode-ui.group.buttons.menu")
 local Button = require("leetcode-ui.lines.button.menu")
 local BackButton = require("leetcode-ui.lines.button.menu.back")
 local Title = require("leetcode-ui.lines.title")
 
-local cmd = require("leetcode.command")
-local config = require("leetcode.config")
+local footer = require("leetcode-ui.lines.footer")
 
 local page = Page()
 
@@ -52,6 +53,7 @@ local update = Button("Update", {
     on_press = function()
         calendar:update()
         solved:update()
+        config.stats.update()
     end,
 })
 buttons:insert(update)
@@ -61,6 +63,6 @@ buttons:insert(back)
 
 page:insert(buttons)
 
-page:insert(Footer())
+page:insert(footer)
 
 return page
diff --git a/lua/leetcode-ui/group/pre/init.lua b/lua/leetcode-ui/group/pre/init.lua
index 378864e2..1b219950 100644
--- a/lua/leetcode-ui/group/pre/init.lua
+++ b/lua/leetcode-ui/group/pre/init.lua
@@ -1,5 +1,6 @@
 local NuiText = require("nui.text")
 local Group = require("leetcode-ui.group")
+local config = require("leetcode.config")
 
 local log = require("leetcode.logger")
 
@@ -8,7 +9,7 @@ local Pre = Group:extend("LeetPre")
 
 function Pre:add_margin(item)
     if item.class.name == "LeetLine" then
-        table.insert(item._texts, 1, NuiText("\t▎ ", "leetcode_indent"))
+        table.insert(item._texts, 1, NuiText(config.icons.indent, "leetcode_indent"))
         return
     end
 
diff --git a/lua/leetcode-ui/group/similar-questions.lua b/lua/leetcode-ui/group/similar-questions.lua
index 3cba9047..9bcf45f7 100644
--- a/lua/leetcode-ui/group/similar-questions.lua
+++ b/lua/leetcode-ui/group/similar-questions.lua
@@ -32,12 +32,20 @@ function SimilarQuestions:init(questions)
             local fid = p.frontend_id .. "."
             fid = fid .. (" "):rep(5 - vim.api.nvim_strwidth(fid))
 
-            button:append("󱓻 ", ui_utils.diff_to_hl(p.difficulty))
+            button:append(config.icons.square .. " ", ui_utils.diff_to_hl(p.difficulty))
             button:append(fid .. " ", "leetcode_normal")
             button:append(utils.translate(p.title, p.title_cn))
 
-            if not config.auth.is_premium and q.paid_only then
-                button:append("  " .. t("Premium"), "leetcode_medium")
+            if q.paid_only then
+                local txt
+
+                if config.auth.is_premium then
+                    txt = " " .. config.icons.unlock
+                else
+                    txt = (" %s "):format(config.icons.lock) .. t("Premium")
+                end
+
+                button:append(txt, "leetcode_medium")
             end
 
             self:insert(button)
diff --git a/lua/leetcode-ui/group/tag/init.lua b/lua/leetcode-ui/group/tag/init.lua
index bba4df38..6956f751 100644
--- a/lua/leetcode-ui/group/tag/init.lua
+++ b/lua/leetcode-ui/group/tag/init.lua
@@ -1,12 +1,9 @@
-local theme = require("leetcode.theme")
-local u = require("leetcode-ui.utils")
-local ts = vim.treesitter
-
-local utils = require("leetcode.parser.utils")
 local Group = require("leetcode-ui.group")
 local Indent = require("nui.text")
 local Normalizer = require("leetcode.parser.normalizer")
 
+local ts = vim.treesitter
+local utils = require("leetcode.parser.utils")
 local log = require("leetcode.logger")
 
 ---@class lc.ui.Tag : lc.ui.Group
diff --git a/lua/leetcode-ui/group/tag/pre.lua b/lua/leetcode-ui/group/tag/pre.lua
index 866f99c9..ee616e15 100644
--- a/lua/leetcode-ui/group/tag/pre.lua
+++ b/lua/leetcode-ui/group/tag/pre.lua
@@ -1,6 +1,5 @@
 local Tag = require("leetcode-ui.group.tag")
-local Lines = require("leetcode-ui.lines")
-local Line = require("nui.line")
+local config = require("leetcode.config")
 
 local log = require("leetcode.logger")
 
@@ -11,7 +10,7 @@ function Pre:contents()
     local items = Pre.super.contents(self)
 
     for _, item in ipairs(items) do
-        self:add_indent(item, "\t▎ ")
+        self:add_indent(item, config.icons.indent)
     end
 
     return items
diff --git a/lua/leetcode-ui/lines/button/menu/back.lua b/lua/leetcode-ui/lines/button/menu/back.lua
index c9d09644..2ff94951 100644
--- a/lua/leetcode-ui/lines/button/menu/back.lua
+++ b/lua/leetcode-ui/lines/button/menu/back.lua
@@ -9,7 +9,7 @@ function MenuBackButton:init(page)
     MenuBackButton.super.init(self, "Back", {
         icon = "",
         sc = "q",
-        on_press = function() cmd.menu_layout(page) end,
+        on_press = function() cmd.set_menu_page(page) end,
     })
 end
 
diff --git a/lua/leetcode-ui/lines/calendar.lua b/lua/leetcode-ui/lines/calendar.lua
index 2509b13c..86ee7345 100644
--- a/lua/leetcode-ui/lines/calendar.lua
+++ b/lua/leetcode-ui/lines/calendar.lua
@@ -83,7 +83,7 @@ function Calendar:handle_res(res)
         self:append(line):endl()
     end
 
-    _Lc_Menu:draw()
+    _Lc_menu:draw()
 end
 
 function Calendar:handle_submissions()
diff --git a/lua/leetcode-ui/lines/footer.lua b/lua/leetcode-ui/lines/footer.lua
index 38160edb..3bea33bd 100644
--- a/lua/leetcode-ui/lines/footer.lua
+++ b/lua/leetcode-ui/lines/footer.lua
@@ -1,24 +1,32 @@
-local Lines = require("leetcode-ui.lines")
-local config = require("leetcode.config")
 local t = require("leetcode.translator")
+local Group = require("leetcode-ui.group")
+
+local config = require("leetcode.config")
+local stats = require("leetcode-ui.lines.stats")
 
----@class lc.ui.menu.Footer : lc.ui.Lines
-local Footer = Lines:extend("LeetFooter")
+---@class lc.ui.menu.Footer : lc.ui.Group
+local Footer = Group:extend("LeetFooter")
+
+function Footer:contents()
+    self:clear()
 
-function Footer:draw(layout, opts)
     if config.auth.is_signed_in then
-        self:clear()
+        self:append(stats)
+        self:endgrp()
+
         self:append(t("Signed in as") .. ": ", "leetcode_alt")
+        if config.auth.is_premium then self:append(config.icons.star .. " ", "leetcode_medium") end
         self:append(config.auth.name):endl()
     end
 
-    Footer.super.draw(self, layout, opts)
+    return Footer.super.contents(self)
 end
 
 ---@param opts? any
 function Footer:init(opts)
     opts = vim.tbl_deep_extend("force", {
         hl = "Number",
+        spacing = 1,
     }, opts or {})
 
     Footer.super.init(self, {}, opts)
@@ -27,4 +35,4 @@ end
 ---@type fun(): lc.ui.menu.Footer
 local LeetFooter = Footer
 
-return LeetFooter
+return LeetFooter()
diff --git a/lua/leetcode-ui/lines/menu-header.lua b/lua/leetcode-ui/lines/menu-header.lua
index 8c66a709..bf51212d 100644
--- a/lua/leetcode-ui/lines/menu-header.lua
+++ b/lua/leetcode-ui/lines/menu-header.lua
@@ -27,4 +27,4 @@ end
 ---@type fun(): lc.ui.menu.Header
 local LeetMenuHeader = MenuHeader
 
-return LeetMenuHeader
+return LeetMenuHeader()
diff --git a/lua/leetcode-ui/lines/solved.lua b/lua/leetcode-ui/lines/solved.lua
index 6f572edc..17c6571c 100644
--- a/lua/leetcode-ui/lines/solved.lua
+++ b/lua/leetcode-ui/lines/solved.lua
@@ -76,7 +76,7 @@ function Solved:handle_res(res)
         self:endl()
     end
 
-    _Lc_Menu:draw()
+    _Lc_menu:draw()
 end
 
 function Solved:update()
diff --git a/lua/leetcode-ui/lines/stats.lua b/lua/leetcode-ui/lines/stats.lua
new file mode 100644
index 00000000..1fd089f3
--- /dev/null
+++ b/lua/leetcode-ui/lines/stats.lua
@@ -0,0 +1,46 @@
+local Lines = require("leetcode-ui.lines")
+local t = require("leetcode.translator")
+local log = require("leetcode.logger")
+local cmd = require("leetcode.command")
+local config = require("leetcode.config")
+
+---@class lc.ui.menu.Stats : lc.ui.Lines
+local Stats = Lines:extend("LeetMenuTitle")
+
+function Stats:contents()
+    self:clear()
+
+    local stats = config.stats
+    local daily, progress = stats.daily, stats.progress
+
+    local hl = daily.today_completed and "leetcode_hard" or "leetcode_alt"
+    self:append("󰈸 ", hl)
+    self:append(daily.streak and tostring(daily.streak) or "-")
+
+    self:append((" %s "):format(config.icons.bar))
+
+    self:append(t("session") .. ": ", "leetcode_alt")
+    local session = cmd.get_active_session()
+    local session_name = session
+            and (session.name == "" and config.sessions.default or session.name)
+        or "-"
+    self:append(session_name)
+
+    local icon = ("  %s "):format(config.icons.square)
+    local function create_progress(key)
+        self:append(icon, "leetcode_" .. key)
+        local count = progress[key] and tostring(progress[key].count) or "-"
+        self:append(count)
+    end
+
+    create_progress("easy")
+    create_progress("medium")
+    create_progress("hard")
+
+    return Stats.super.contents(self)
+end
+
+---@type fun(): lc.ui.menu.Stats
+local LeetMenuStats = Stats
+
+return LeetMenuStats()
diff --git a/lua/leetcode-ui/popup/console/result.lua b/lua/leetcode-ui/popup/console/result.lua
index 7a5a9bbf..985826a8 100644
--- a/lua/leetcode-ui/popup/console/result.lua
+++ b/lua/leetcode-ui/popup/console/result.lua
@@ -4,6 +4,7 @@ local t = require("leetcode.translator")
 
 local problemlist = require("leetcode.cache.problemlist")
 local log = require("leetcode.logger")
+local config = require("leetcode.config")
 
 ---@class lc.ui.Console.ResultPopup : lc.ui.Console.Popup
 ---@field renderer lc.ui.Result
@@ -20,6 +21,7 @@ function ResultPopup:handle(item)
     if item._.submission then
         local status = item.status_code == 10 and "ac" or "notac"
         problemlist.change_status(self.console.question.q.title_slug, status)
+        if status == "ac" then config.stats.update_streak() end
     end
 
     self:draw()
diff --git a/lua/leetcode-ui/popup/console/testcase.lua b/lua/leetcode-ui/popup/console/testcase.lua
index 809c0e87..c403b24b 100644
--- a/lua/leetcode-ui/popup/console/testcase.lua
+++ b/lua/leetcode-ui/popup/console/testcase.lua
@@ -127,10 +127,12 @@ function Testcase:append(input)
 end
 
 function Testcase:autocmds()
-    self:on(
-        { "TextChanged", "TextChangedI", "TextChangedP", "TextChangedT" },
-        function() self:draw_extmarks() end
-    )
+    self:on({
+        "TextChanged",
+        "TextChangedI",
+        "TextChangedP",
+        "InsertLeave",
+    }, function() self:draw_extmarks() end)
 end
 
 function Testcase:mount()
diff --git a/lua/leetcode-ui/popup/skills.lua b/lua/leetcode-ui/popup/skills.lua
index 49e12fbb..1b5e7994 100644
--- a/lua/leetcode-ui/popup/skills.lua
+++ b/lua/leetcode-ui/popup/skills.lua
@@ -17,7 +17,7 @@ function Skills:handle(name, skills)
     local lines = Lines()
 
     table.sort(skills, function(a, b) return a.problems_solved > b.problems_solved end)
-    lines:append("󱓻", utils.diff_to_hl(name))
+    lines:append(config.icons.square, utils.diff_to_hl(name))
     lines:append(" " .. name)
 
     for _, skill in ipairs(skills) do
diff --git a/lua/leetcode-ui/question.lua b/lua/leetcode-ui/question.lua
index ba28df64..220fc640 100644
--- a/lua/leetcode-ui/question.lua
+++ b/lua/leetcode-ui/question.lua
@@ -18,12 +18,14 @@ local log = require("leetcode.logger")
 ---@field cache lc.cache.Question
 local Question = Object("LeetQuestion")
 
-function Question:get_snippet()
+---@param raw? boolean
+function Question:get_snippet(raw)
     local snippets = self.q.code_snippets ~= vim.NIL and self.q.code_snippets or {}
     local snip = vim.tbl_filter(function(snip) return snip.lang_slug == self.lang end, snippets)[1]
     if not snip then return end
 
-    return self:injector(snip.code or "")
+    local code = snip.code:gsub("\r\n", "\n")
+    return raw and code or self:injector(code)
 end
 
 function Question:create_file()
@@ -51,12 +53,8 @@ end
 
 ---@param code string
 function Question:injector(code)
-    code = code:gsub("\r\n", "\n")
-
     local injector = config.user.injector
-
-    local inject = injector[self.lang]
-    if not inject or vim.tbl_isempty(inject) then return code end
+    local inject = injector[self.lang] or {}
 
     ---@param inj? string|string[]
     ---@param before boolean
@@ -84,17 +82,18 @@ function Question:injector(code)
         .. norm_inject(inject.after, false)
 end
 
-function Question:_unmount()
+---@param pre? boolean
+function Question:_unmount(pre)
     self.info:unmount()
     self.console:unmount()
     self.description:unmount()
 
-    if vim.api.nvim_buf_is_valid(self.bufnr) then
-        pcall(vim.api.nvim_buf_delete, self.bufnr, { force = true })
+    if not pre and vim.api.nvim_buf_is_valid(self.bufnr) then
+        vim.api.nvim_buf_delete(self.bufnr, { force = true })
     end
 
     if vim.api.nvim_win_is_valid(self.winid) then --
-        pcall(vim.api.nvim_win_close, self.winid, true)
+        vim.api.nvim_win_close(self.winid, true)
     end
 
     _Lc_questions = vim.tbl_filter(function(q) return q.bufnr ~= self.bufnr end, _Lc_questions)
@@ -103,7 +102,8 @@ function Question:_unmount()
 end
 
 ---@param self lc.ui.Question
-Question.unmount = vim.schedule_wrap(function(self) self:_unmount() end)
+---@param pre? boolean
+Question.unmount = vim.schedule_wrap(function(self, pre) self:_unmount(pre) end)
 
 function Question:handle_mount()
     vim.cmd("$tabe " .. self:create_file())
@@ -119,7 +119,7 @@ function Question:handle_mount()
 
     vim.api.nvim_create_autocmd("QuitPre", {
         buffer = self.bufnr,
-        callback = function() self:unmount() end,
+        callback = function() self:unmount(true) end,
     })
 
     self.description = Description(self):mount()
diff --git a/lua/leetcode-ui/renderer/init.lua b/lua/leetcode-ui/renderer/init.lua
index 6f424960..ac2cf489 100644
--- a/lua/leetcode-ui/renderer/init.lua
+++ b/lua/leetcode-ui/renderer/init.lua
@@ -75,8 +75,7 @@ function Renderer:map(mode, key, handler, opts) --
 end
 
 function Renderer:unmap(mode, key) --
-    local ok, err = pcall(vim.api.nvim_buf_del_keymap, self.bufnr, mode, key)
-    if not ok then log.error(err) end
+    pcall(vim.api.nvim_buf_del_keymap, self.bufnr, mode, key)
 end
 
 function Renderer:clear_keymaps()
diff --git a/lua/leetcode-ui/renderer/menu.lua b/lua/leetcode-ui/renderer/menu.lua
index f5f2cfcf..087cb36c 100644
--- a/lua/leetcode-ui/renderer/menu.lua
+++ b/lua/leetcode-ui/renderer/menu.lua
@@ -3,6 +3,7 @@ local cookie = require("leetcode.cache.cookie")
 local config = require("leetcode.config")
 local utils = require("leetcode-ui.utils")
 local Renderer = require("leetcode-ui.renderer")
+local api = vim.api
 
 ---@class lc.ui.Menu : lc.ui.Renderer
 ---@field cursor lc-menu.cursor
@@ -23,43 +24,71 @@ end
 
 ---@private
 function Menu:autocmds()
-    local group_id = vim.api.nvim_create_augroup("leetcode_menu", { clear = true })
+    local group_id = api.nvim_create_augroup("leetcode_menu", { clear = true })
 
-    vim.api.nvim_create_autocmd("WinResized", {
+    api.nvim_create_autocmd("WinResized", {
         group = group_id,
         buffer = self.bufnr,
         callback = function() self:draw() end,
     })
 
-    vim.api.nvim_create_autocmd("CursorMoved", {
+    api.nvim_create_autocmd("CursorMoved", {
         group = group_id,
         buffer = self.bufnr,
         callback = function() self:cursor_move() end,
     })
+
+    api.nvim_create_autocmd("QuitPre", {
+        group = group_id,
+        buffer = self.bufnr,
+        callback = function()
+            self.winid = nil
+            self.bufnr = nil
+            self:clear_keymaps()
+        end,
+    })
 end
 
 function Menu:cursor_move()
-    if not self.winid or not vim.api.nvim_win_is_valid(self.winid) then return end
+    if not self.winid or not api.nvim_win_is_valid(self.winid) then return end
 
-    local curr = vim.api.nvim_win_get_cursor(self.winid)
+    local curr = api.nvim_win_get_cursor(self.winid)
     local prev = self.cursor.prev
 
     local keys = tbl_keys(self._.buttons)
     if not keys then return end
 
+    local function find_nearest(l, r)
+        while l < r do
+            local m = math.floor((l + r) / 2)
+
+            if keys[m] < curr[1] then
+                l = m + 1
+            else
+                r = m
+            end
+        end
+
+        return math.max(r, 1)
+    end
+
     if prev then
-        if curr[1] > prev[1] then
-            self.cursor.idx = math.min(self.cursor.idx + 1, #keys)
-        elseif curr[1] < prev[1] then
-            self.cursor.idx = math.max(self.cursor.idx - 1, 1)
+        local next_idx = self.cursor.idx
+
+        if curr[1] < prev[1] then
+            next_idx = find_nearest(1, self.cursor.idx - 1)
+        elseif curr[1] > prev[1] then
+            next_idx = find_nearest(self.cursor.idx + 1, #keys)
         end
+
+        self.cursor.idx = next_idx
     end
 
     local row = keys[self.cursor.idx]
     local col = #vim.fn.getline(row):match("^%s*")
 
     self.cursor.prev = { row, col }
-    vim.api.nvim_win_set_cursor(self.winid, self.cursor.prev)
+    api.nvim_win_set_cursor(self.winid, self.cursor.prev)
 end
 
 function Menu:cursor_reset()
@@ -82,7 +111,7 @@ function Menu:set_page(name)
 end
 
 function Menu:apply_options()
-    vim.api.nvim_buf_set_name(self.bufnr, "")
+    api.nvim_buf_set_name(self.bufnr, "")
     pcall(vim.diagnostic.disable, self.bufnr)
 
     utils.set_buf_opts(self.bufnr, {
@@ -109,6 +138,21 @@ function Menu:apply_options()
     })
 end
 
+function Menu:remount()
+    if self.winid and api.nvim_win_is_valid(self.winid) then --
+        api.nvim_win_close(self.winid, true)
+    end
+    if self.bufnr and api.nvim_buf_is_valid(self.bufnr) then --
+        api.nvim_buf_delete(self.bufnr, { force = true })
+    end
+
+    vim.cmd.tabe()
+    self.bufnr = api.nvim_get_current_buf()
+    self.winid = api.nvim_get_current_win()
+
+    self:_mount()
+end
+
 function Menu:mount()
     if cookie.get() then
         self:set_page("loading")
@@ -119,18 +163,23 @@ function Menu:mount()
                 self:set_page("signin")
                 log.err(err)
             else
-                self:set_page("menu")
+                local cmd = require("leetcode.command")
+                cmd.start_user_session()
             end
         end)
     else
         self:set_page("signin")
     end
 
+    self:_mount()
+
+    return self
+end
+
+function Menu:_mount()
     self:apply_options()
     self:autocmds()
     self:draw()
-
-    return self
 end
 
 function Menu:init()
@@ -144,10 +193,10 @@ function Menu:init()
     }
     self.maps = {}
 
-    self.bufnr = vim.api.nvim_get_current_buf()
-    self.winid = vim.api.nvim_get_current_win()
+    self.bufnr = api.nvim_get_current_buf()
+    self.winid = api.nvim_get_current_win()
 
-    _Lc_Menu = self
+    _Lc_menu = self
 end
 
 ---@type fun(): lc.ui.Menu
diff --git a/lua/leetcode-ui/split/description.lua b/lua/leetcode-ui/split/description.lua
index 25887b7d..edb20dd5 100644
--- a/lua/leetcode-ui/split/description.lua
+++ b/lua/leetcode-ui/split/description.lua
@@ -119,6 +119,7 @@ function Description:populate()
 
     header:append(q.frontend_id .. ". ", "leetcode_normal")
     header:append(utils.translate(q.title, q.translated_title))
+    if q.is_paid_only then header:append(" " .. t("Premium"), "leetcode_medium") end
     header:endgrp()
 
     local show_stats = self.show_stats
@@ -129,17 +130,12 @@ function Description:populate()
         header:append("????", "leetcode_list")
     end
 
-    local user_status = {
-        ac = { "", "leetcode_easy" },
-        notac = { "󱎖", "leetcode_medium" },
-        todo = { "", "leetcode_alt" },
-    }
-    if user_status[self.question.cache.status] then
-        local s = user_status[self.question.cache.status]
+    if config.icons.hl.status[self.question.cache.status] then
+        local s = config.icons.hl.status[self.question.cache.status]
         header:append(" "):append(s[1], s[2])
     end
 
-    header:append(" | ")
+    header:append((" %s "):format(config.icons.bar))
 
     local likes = show_stats and q.likes or "___"
     header:append(likes .. " ", "leetcode_alt")
@@ -147,13 +143,13 @@ function Description:populate()
     local dislikes = show_stats and q.dislikes or "___"
     if not config.is_cn then header:append((" %s "):format(dislikes), "leetcode_alt") end
 
-    header:append(" | ")
+    header:append((" %s "):format(config.icons.bar))
 
     local ac_rate = show_stats and q.stats.acRate or "__%"
     local total_sub = show_stats and q.stats.totalSubmission or "__"
     header:append(("%s %s %s"):format(ac_rate, t("of"), total_sub), "leetcode_alt")
     if not vim.tbl_isempty(q.hints) then
-        header:append(" | ")
+        header:append((" %s "):format(config.icons.bar))
         header:append("󰛨 " .. t("Hints"), "leetcode_hint")
     end
     header:endgrp()
diff --git a/lua/leetcode-ui/utils.lua b/lua/leetcode-ui/utils.lua
index 29b15fcf..1b1be955 100644
--- a/lua/leetcode-ui/utils.lua
+++ b/lua/leetcode-ui/utils.lua
@@ -1,5 +1,6 @@
 local O = require("nui.object")
 local log = require("leetcode.logger")
+local config = require("leetcode.config")
 
 ---@class lc-ui.Utils
 local utils = {}
@@ -48,13 +49,7 @@ end
 function utils.status_to_hl(status)
     if not status then return end
 
-    local user_status = {
-        ac = { "", "leetcode_easy" },
-        notac = { "󱎖", "leetcode_medium" },
-        todo = { "", "leetcode_alt" },
-    }
-
-    return table.unpack(user_status[status])
+    return table.unpack(config.icons.hl.status[status])
 end
 
 ---@param layout lc.ui.Renderer
diff --git a/lua/leetcode/api/headers.lua b/lua/leetcode/api/headers.lua
index 2d9f6b71..55e57d4d 100644
--- a/lua/leetcode/api/headers.lua
+++ b/lua/leetcode/api/headers.lua
@@ -9,9 +9,10 @@ function headers.get()
     return vim.tbl_extend("force", {
         ["Referer"] = ("https://leetcode.%s"):format(config.domain),
         ["Origin"] = ("https://leetcode.%s/"):format(config.domain),
-        ["content-type"] = "application/json",
+        ["Content-Type"] = "application/json",
         ["Accept"] = "application/json",
         ["Host"] = ("leetcode.%s"):format(config.domain),
+        ["X-Requested-With"] = "XMLHttpRequest",
     }, cookie and {
         ["Cookie"] = cookie.str,
         ["x-csrftoken"] = cookie.csrftoken,
diff --git a/lua/leetcode/api/interpreter.lua b/lua/leetcode/api/interpreter.lua
index 10ad8076..77b326e4 100644
--- a/lua/leetcode/api/interpreter.lua
+++ b/lua/leetcode/api/interpreter.lua
@@ -1,21 +1,14 @@
 local log = require("leetcode.logger")
 local urls = require("leetcode.api.urls")
 
+local config = require("leetcode.config")
 local utils = require("leetcode.api.utils")
-local spinner = require("leetcode.logger.spinner")
 
 local t = require("leetcode.translator")
 
 ---@class lc.Interpreter
 local interpreter = {}
 
-local check_state = {
-    ["PENDING"] = "Pending…",
-    ["STARTED"] = "Judging…",
-    ["SUCCESS"] = "Finished",
-    ["FAILURE"] = "Failed", -- CODE: 16
-}
-
 ---@param item lc.interpreter_response
 ---
 ---@return lc.interpreter_response
@@ -49,25 +42,18 @@ end
 ---@param id string
 ---@param callback function
 function interpreter.listener(id, callback)
-    local noti = spinner:init(check_state["PENDING"], "points")
-
     local function listen()
         interpreter.check(id, function(item, err)
-            if err then return noti:stop(err.msg, false) end
-
-            if item.status_code then
+            if err then -- error
+                callback(nil, nil, err)
+            elseif item.status_code then -- got results
                 item = interpreter:handle_item(item)
-                noti:stop(item.status_msg, item._.success)
-                return callback(item)
-            else
-                noti:update(check_state[item.state])
-                if item.state == "PENDING" then
-                    noti:change("points")
-                elseif item.state == "STARTED" then
-                    noti:change("dot")
-                end
-
-                vim.defer_fn(listen, 500)
+                callback(item)
+            else -- still judging
+                local intervals = config.auth.is_premium and { 500, 500 } or { 500, 1000 }
+                local interval = item.sate == "STARTED" and intervals[2] or intervals[1]
+                callback(nil, item.state)
+                vim.defer_fn(listen, interval)
             end
         end)
     end
@@ -80,32 +66,27 @@ end
 ---@field typed_code string
 ---@field data_input string
 
----@param title_slug string
----@param body lc.Interpret.body|string
----@param callback function
-function interpreter.interpret_solution(title_slug, body, callback)
-    local url = urls.interpret:format(title_slug)
-
-    interpreter.fetch(url, {
-        body = body,
-        callback = function(res, success)
-            callback(success)
-            if success then interpreter.listener(res.interpret_id, callback) end
-        end,
-    })
-end
-
----@param title_slug string
+---@param submit boolean
+---@param q lc.ui.Question
 ---@param body lc.Interpret.body|string
 ---@param callback function
-function interpreter.submit(title_slug, body, callback)
-    local url = urls.submit:format(title_slug)
+function interpreter.run(submit, q, body, callback)
+    local url = (submit and urls.submit or urls.interpret):format(q.q.title_slug)
 
     interpreter.fetch(url, {
         body = body,
-        callback = function(res, success)
-            callback(success)
-            if success then interpreter.listener(res.submission_id, callback) end
+        callback = function(res, err)
+            if err then
+                if err.status == 429 then
+                    err.msg = "You have attempted to run code too soon"
+                    err.lvl = vim.log.levels.WARN
+                end
+                callback(nil, nil, err)
+            else
+                q.console.result:clear()
+                local id = submit and res.submission_id or res.interpret_id
+                interpreter.listener(id, callback)
+            end
         end,
     })
 end
@@ -122,17 +103,7 @@ end
 function interpreter.fetch(url, opts)
     utils.post(url, {
         body = opts.body,
-        callback = function(res, err)
-            if err then
-                if err.status == 429 then
-                    err.msg = "You have attempted to run code too soon"
-                    err.lvl = vim.log.levels.WARN
-                end
-                opts.callback(log.err(err), false)
-            else
-                opts.callback(res, true)
-            end
-        end,
+        callback = opts.callback,
     })
 end
 
diff --git a/lua/leetcode/api/queries.lua b/lua/leetcode/api/queries.lua
index ebf222a9..b8356208 100644
--- a/lua/leetcode/api/queries.lua
+++ b/lua/leetcode/api/queries.lua
@@ -9,6 +9,7 @@ queries.auth = [[
                 is_signed_in: isSignedIn
                 is_premium: isPremium
                 is_verified: isVerified
+                session_id: activeSessionId
             }
         }
     ]]
@@ -155,4 +156,27 @@ queries.languages = [[
         }
     ]]
 
+queries.streak = [[
+        query getStreakCounter {
+            streakCounter {
+                streakCount
+                daysSkipped
+                todayCompleted: currentDayCompleted
+            }
+        }
+    ]]
+
+queries.session_progress = [[
+        query userSessionProgress($username: String!) {
+            matchedUser(username: $username) {
+                submitStats {
+                    acSubmissionNum {
+                        difficulty
+                        count
+                    }
+                }
+            }
+        }
+    ]]
+
 return queries
diff --git a/lua/leetcode/api/statistics.lua b/lua/leetcode/api/statistics.lua
index 3e7f0ec9..d65e3d3d 100644
--- a/lua/leetcode/api/statistics.lua
+++ b/lua/leetcode/api/statistics.lua
@@ -4,7 +4,7 @@ local log = require("leetcode.logger")
 local urls = require("leetcode.api.urls")
 local queries = require("leetcode.api.queries")
 
----@lc.api.statistics
+---@class lc.api.statistics
 local statistics = {}
 
 function statistics.calendar(cb)
@@ -32,6 +32,25 @@ function statistics.calendar(cb)
     })
 end
 
+---@param cb fun(res: lc.Stats.QuestionCount[], err: lc.err)
+function statistics.session_progress(cb)
+    local variables = {
+        username = config.auth.name,
+    }
+
+    local query = queries.session_progress
+
+    utils.query(query, variables, {
+        callback = function(res, err)
+            if err then return cb(nil, err) end
+
+            local data = res.data
+            local session_progress = data["matchedUser"]["submitStats"]["acSubmissionNum"]
+            cb(session_progress)
+        end,
+    })
+end
+
 ---@param cb fun(res: lc.Stats.Res, err: lc.err)
 function statistics.solved(cb)
     local variables = {
@@ -95,4 +114,70 @@ function statistics.languages(cb)
     })
 end
 
+function statistics.streak(cb)
+    local variables = vim.empty_dict()
+
+    local query = queries.streak
+
+    utils.query(query, variables, {
+        endpoint = urls.streak_counter,
+        callback = function(res, err)
+            if err then return cb(nil, err) end
+            local data = res.data
+            local streak = data["streakCounter"]
+            cb(streak)
+        end,
+    })
+end
+
+---@param cb fun(res: lc.res.session[], err: lc.err)
+function statistics.sessions(cb)
+    local url = urls.session
+
+    utils.post(url, {
+        body = vim.empty_dict(),
+        callback = function(res, err)
+            if err then return cb(nil, err) end
+            config.sessions.update(res.sessions)
+            cb(res.sessions)
+        end,
+    })
+end
+
+function statistics.change_session(id, cb)
+    local body = {
+        func = "activate",
+        target = id,
+    }
+
+    local url = urls.session
+
+    utils.put(url, {
+        body = body,
+        callback = function(res, err)
+            if err then return cb(nil, err) end
+            config.sessions.update(res.sessions)
+            cb(res.sessions)
+        end,
+    })
+end
+
+function statistics.create_session(name, cb)
+    local body = {
+        func = "create",
+        name = name,
+    }
+
+    local url = urls.session
+
+    utils.put(url, {
+        body = body,
+        callback = function(res, err)
+            if err then return cb(nil, err) end
+            config.sessions.update(res.sessions)
+            cb(res.sessions)
+        end,
+    })
+end
+
 return statistics
diff --git a/lua/leetcode/api/types.lua b/lua/leetcode/api/types.lua
index d6b95103..7cd2f84d 100644
--- a/lua/leetcode/api/types.lua
+++ b/lua/leetcode/api/types.lua
@@ -332,6 +332,7 @@
 ---@field is_premium boolean
 ---@field is_verified boolean
 ---@field id integer
+---@field session_id integer
 
 --------------------------------------------
 --- Statistics
@@ -392,3 +393,15 @@
 ---@field lvl integer
 
 ---@alias lc.err lc.Api.err|nil
+
+--------------------------------------------
+--- Sessions
+--------------------------------------------
+---@class lc.res.session
+---@field ac_questions integer
+---@field id integer
+---@field is_active boolean
+---@field name string
+---@field submitted_questions integer
+---@field total_acs integer
+---@field total_submitted integer
diff --git a/lua/leetcode/api/urls.lua b/lua/leetcode/api/urls.lua
index 2c16cce7..ea7de617 100644
--- a/lua/leetcode/api/urls.lua
+++ b/lua/leetcode/api/urls.lua
@@ -14,5 +14,7 @@ urls.submit = "/problems/%s/submit/"
 urls.run = "/problems/%s/interpret_solution/"
 urls.check = "/submissions/detail/%s/check/"
 urls.latest_submission = "/submissions/latest/?qid=%s&lang=%s"
+urls.streak_counter = "/graphql/"
+urls.session = "/session/"
 
 return urls
diff --git a/lua/leetcode/api/utils.lua b/lua/leetcode/api/utils.lua
index 371baf35..cb3a9208 100644
--- a/lua/leetcode/api/utils.lua
+++ b/lua/leetcode/api/utils.lua
@@ -1,13 +1,21 @@
 local curl = require("plenary.curl")
 local log = require("leetcode.logger")
 local config = require("leetcode.config")
-local path = require("plenary.path")
 local headers = require("leetcode.api.headers")
 local urls = require("leetcode.api.urls")
 
 ---@class lc.Api.Utils
 local utils = {}
 
+---@param endpoint string
+function utils.put(endpoint, opts)
+    local options = vim.tbl_deep_extend("force", {
+        endpoint = endpoint,
+    }, opts or {})
+
+    return utils.curl("put", options)
+end
+
 ---@param endpoint string
 function utils.post(endpoint, opts)
     local options = vim.tbl_deep_extend("force", {
diff --git a/lua/leetcode/command/arguments.lua b/lua/leetcode/command/arguments.lua
index e147e52e..f065be70 100644
--- a/lua/leetcode/command/arguments.lua
+++ b/lua/leetcode/command/arguments.lua
@@ -1,3 +1,5 @@
+local config = require("leetcode.config")
+
 local arguments = {}
 
 local topics = {
@@ -85,4 +87,12 @@ arguments.random = {
     tags = topics,
 }
 
+arguments.session_change = {
+    name = config.sessions.names,
+}
+
+arguments.session_create = {
+    name = {},
+}
+
 return arguments
diff --git a/lua/leetcode/command/init.lua b/lua/leetcode/command/init.lua
index 4ece08cc..4b571d49 100644
--- a/lua/leetcode/command/init.lua
+++ b/lua/leetcode/command/init.lua
@@ -2,7 +2,7 @@ local log = require("leetcode.logger")
 local arguments = require("leetcode.command.arguments")
 local config = require("leetcode.config")
 local event = require("nui.utils.autocmd").event
-local keys = config.user.keys
+local api = vim.api
 
 local t = require("leetcode.translator")
 
@@ -60,17 +60,18 @@ function cmd.cookie_prompt(cb)
 
             if not err then
                 log.info("Sign-in successful")
-                cmd.menu_layout("menu")
-                pcall(cb, true)
+                cmd.start_user_session()
             else
                 log.error("Sign-in failed: " .. err)
-                pcall(cb, false)
             end
+
+            pcall(cb, not err and true or false)
         end,
     })
 
     input:mount()
 
+    local keys = config.user.keys
     input:map("n", keys.toggle, function() input:unmount() end)
     input:on(event.BufLeave, function() input:unmount() end)
 end
@@ -78,7 +79,7 @@ end
 function cmd.sign_out()
     log.warn("You're now signed out")
     cmd.delete_cookie()
-    cmd.menu_layout("signin")
+    cmd.set_menu_page("signin")
     cmd.q_close_all()
 end
 
@@ -99,16 +100,16 @@ cmd.q_close_all = vim.schedule_wrap(function()
 end)
 
 cmd.expire = vim.schedule_wrap(function()
-    local tabp = vim.api.nvim_get_current_tabpage()
+    local tabp = api.nvim_get_current_tabpage()
     cmd.menu()
 
     cmd.cookie_prompt(function(success)
         if success then
-            if vim.api.nvim_tabpage_is_valid(tabp) then vim.api.nvim_set_current_tabpage(tabp) end
+            if api.nvim_tabpage_is_valid(tabp) then api.nvim_set_current_tabpage(tabp) end
             log.info("Successful re-login")
         else
             cmd.delete_cookie()
-            cmd.menu_layout("signin")
+            cmd.set_menu_page("signin")
             cmd.q_close_all()
         end
     end)
@@ -159,11 +160,12 @@ function cmd.start_with_cmd()
 end
 
 function cmd.menu()
-    local ok, tabp = pcall(vim.api.nvim_win_get_tabpage, _Lc_Menu.winid)
+    local ok, tabp = pcall(api.nvim_win_get_tabpage, _Lc_menu.winid)
+
     if ok then
-        vim.api.nvim_set_current_tabpage(tabp)
+        api.nvim_set_current_tabpage(tabp)
     else
-        log.error(tabp)
+        _Lc_menu:remount()
     end
 end
 
@@ -172,9 +174,9 @@ function cmd.yank()
     local q = utils.curr_question()
     if not q then return end
 
-    if vim.api.nvim_buf_is_valid(q.bufnr) and vim.api.nvim_win_is_valid(q.winid) then
-        vim.api.nvim_set_current_win(q.winid)
-        vim.api.nvim_set_current_buf(q.bufnr)
+    if api.nvim_buf_is_valid(q.bufnr) and api.nvim_win_is_valid(q.winid) then
+        api.nvim_set_current_win(q.winid)
+        api.nvim_set_current_buf(q.bufnr)
 
         local start_i, end_i = q:range()
         vim.cmd(("%d,%dyank"):format(start_i, end_i))
@@ -182,7 +184,12 @@ function cmd.yank()
 end
 
 ---@param page lc-menu.page
-function cmd.menu_layout(page) _Lc_Menu:set_page(page) end
+function cmd.set_menu_page(page) _Lc_menu:set_page(page) end
+
+function cmd.start_user_session() --
+    cmd.set_menu_page("menu")
+    config.stats.update()
+end
 
 function cmd.question_tabs() require("leetcode.pickers.question-tabs").pick() end
 
@@ -274,10 +281,8 @@ function cmd.reset()
     local q = utils.curr_question()
     if not q then return end
 
-    local snip = q:get_snippet()
-    if snip and vim.api.nvim_buf_is_valid(q.bufnr) then
-        vim.api.nvim_buf_set_lines(q.bufnr, 0, -1, false, vim.split(snip, "\n"))
-    end
+    local snip = q:get_snippet(true)
+    utils.set_question_lines(q, snip)
 end
 
 function cmd.last_submit()
@@ -298,15 +303,78 @@ function cmd.last_submit()
             return
         end
 
-        if type(res) == "table" and res.code and vim.api.nvim_buf_is_valid(q.bufnr) then
-            local code = q:injector(res.code)
-            vim.api.nvim_buf_set_lines(q.bufnr, 0, -1, false, vim.split(code, "\n"))
+        if type(res) == "table" and res.code and api.nvim_buf_is_valid(q.bufnr) then
+            utils.set_question_lines(q, res.code)
         else
             log.error("Something went wrong")
         end
     end)
 end
 
+function cmd.restore()
+    local utils = require("leetcode.utils")
+    utils.auth_guard()
+    local q = utils.curr_question()
+    if not q then return end
+
+    if
+        (q.winid and api.nvim_win_is_valid(q.winid))
+        and (q.bufnr and api.nvim_buf_is_valid(q.bufnr))
+    then
+        api.nvim_win_set_buf(q.winid, q.bufnr)
+    end
+
+    q.description:show()
+    local winid, bufnr = q.description.winid, q.description.bufnr
+
+    if
+        (winid and api.nvim_win_is_valid(winid)) --
+        and (bufnr and api.nvim_buf_is_valid(bufnr))
+    then
+        api.nvim_win_set_buf(winid, bufnr)
+    end
+end
+
+function cmd.get_active_session()
+    local sessions = config.sessions.all
+    return vim.tbl_filter(function(s) return s.is_active end, sessions)[1]
+end
+
+function cmd.get_session_by_name(name)
+    local sessions = config.sessions.all
+
+    name = name:lower()
+    if name == config.sessions.default then name = "" end
+    return vim.tbl_filter(function(s) return s.name:lower() == name end, sessions)[1]
+end
+
+function cmd.change_session(opts)
+    local name = opts.name[1] or config.sessions.default
+
+    local session = cmd.get_session_by_name(name)
+    if not session then return log.error("Session not found") end
+
+    local stats_api = require("leetcode.api.statistics")
+    stats_api.change_session(session.id, function(_, err)
+        if err then return log.err(err) end
+        log.info(("Session changed to `%s`"):format(name))
+        config.stats.update()
+    end)
+end
+
+function cmd.create_session(opts)
+    local name = opts.name[1]
+    if not name then return log.error("Session name not provided") end
+
+    local stats_api = require("leetcode.api.statistics")
+    stats_api.create_session(name, function(_, err)
+        if err then return log.err(err) end
+        log.info(("session `%s` created"):format(name))
+    end)
+end
+
+function cmd.update_sessions() config.stats.update_sessions() end
+
 function cmd.fix()
     require("leetcode.cache.cookie").delete()
     require("leetcode.cache.problemlist").delete()
@@ -327,14 +395,15 @@ function cmd.parse(args)
     return parts, options
 end
 
----@param t table
-local function cmds_keys(t)
+---@param tbl table
+local function cmds_keys(tbl)
     return vim.tbl_filter(function(key)
         if type(key) ~= "string" then return false end
         if key:sub(1, 1) == "_" then return false end
+        if tbl[key]._private then return false end
 
         return true
-    end, vim.tbl_keys(t))
+    end, vim.tbl_keys(tbl))
 end
 
 ---@param _ string
@@ -410,7 +479,7 @@ function cmd.exec(args)
 end
 
 function cmd.setup()
-    vim.api.nvim_create_user_command("Leet", cmd.exec, {
+    api.nvim_create_user_command("Leet", cmd.exec, {
         bar = true,
         bang = true,
         nargs = "?",
@@ -432,11 +501,27 @@ cmd.commands = {
     test = { cmd.q_run },
     submit = { cmd.q_submit },
     daily = { cmd.qot },
-    fix = { cmd.fix },
     yank = { cmd.yank },
     open = { cmd.open },
     reset = { cmd.reset },
     last_submit = { cmd.last_submit },
+    restore = { cmd.restore },
+
+    session = {
+        change = {
+            cmd.change_session,
+
+            _args = arguments.session_change,
+        },
+
+        create = {
+            cmd.create_session,
+
+            _args = arguments.session_create,
+        },
+
+        update = { cmd.update_sessions },
+    },
 
     list = {
         cmd.problems,
@@ -466,6 +551,12 @@ cmd.commands = {
     cache = {
         update = { cmd.cache_update },
     },
+
+    fix = {
+        cmd.fix,
+
+        _private = true,
+    },
 }
 
 return cmd
diff --git a/lua/leetcode/config/icons.lua b/lua/leetcode/config/icons.lua
index bf85c0db..79699a26 100644
--- a/lua/leetcode/config/icons.lua
+++ b/lua/leetcode/config/icons.lua
@@ -1,4 +1,27 @@
-return {
-    current = "",
-    dot = "",
+local icons = {
+    bar = "│",
+    circle = "",
+    square = "󱓻",
+    lock = "",
+    unlock = "",
+    star = "",
+    status = {
+        ac = "",
+        notac = "󱎖",
+        todo = "",
+    },
 }
+
+icons.hl = {
+    status = {
+        ac = { icons.status.ac, "leetcode_easy" },
+        notac = { icons.status.notac, "leetcode_medium" },
+        todo = { icons.status.todo, "leetcode_alt" },
+    },
+    lock = { icons.lock, "leetcode_medium" },
+    unlock = { icons.unlock, "leetcode_medium" },
+}
+
+icons.indent = ("\t%s "):format(icons.bar)
+
+return icons
diff --git a/lua/leetcode/config/init.lua b/lua/leetcode/config/init.lua
index 2516cb0f..f13f7033 100644
--- a/lua/leetcode/config/init.lua
+++ b/lua/leetcode/config/init.lua
@@ -5,7 +5,7 @@ local P = require("plenary.path")
 _Lc_questions = {}
 
 ---@type lc.ui.Menu
-_Lc_Menu = {} ---@diagnostic disable-line
+_Lc_menu = {} ---@diagnostic disable-line
 
 local lazy_plugs = {}
 
@@ -26,6 +26,8 @@ local config = {
 
     langs = require("leetcode.config.langs"),
     icons = require("leetcode.config.icons"),
+    sessions = require("leetcode.config.sessions"),
+    stats = require("leetcode.config.stats"),
 
     ---@type lc.UserStatus
     auth = {}, ---@diagnostic disable-line
diff --git a/lua/leetcode/config/sessions.lua b/lua/leetcode/config/sessions.lua
new file mode 100644
index 00000000..30fe6e83
--- /dev/null
+++ b/lua/leetcode/config/sessions.lua
@@ -0,0 +1,22 @@
+---@class lc.Sessions
+local Sessions = {}
+
+Sessions.default = "anonymous"
+Sessions.names = {}
+Sessions.all = {}
+
+---@param sessions lc.res.session[]
+function Sessions.update(sessions) --
+    Sessions.all = sessions
+
+    while #Sessions.names > 0 do
+        table.remove(Sessions.names)
+    end
+
+    for _, session in ipairs(sessions) do
+        local name = session.name == "" and Sessions.default or session.name
+        table.insert(Sessions.names, name:lower())
+    end
+end
+
+return Sessions
diff --git a/lua/leetcode/config/stats.lua b/lua/leetcode/config/stats.lua
new file mode 100644
index 00000000..4f141c66
--- /dev/null
+++ b/lua/leetcode/config/stats.lua
@@ -0,0 +1,53 @@
+---@class lc.Stats
+local Stats = {}
+
+Stats.daily = {} ---@type { streak?: number, today_completed?: boolean }
+Stats.progress = {} ---@type table<string, lc.Stats.QuestionCount>
+
+function Stats.update_streak()
+    local stats_api = require("leetcode.api.statistics")
+    local log = require("leetcode.logger")
+
+    stats_api.streak(function(res, err)
+        if err then return log.err(err) end
+
+        Stats.daily.streak = res.streakCount
+        Stats.daily.today_completed = res.todayCompleted
+
+        if _Lc_menu then _Lc_menu:draw() end
+    end)
+end
+
+function Stats.update_sessions()
+    local stats_api = require("leetcode.api.statistics")
+    local log = require("leetcode.logger")
+
+    Stats.progress = {}
+
+    stats_api.sessions(function(_, err)
+        if err then return log.err(err) end
+        if _Lc_menu then _Lc_menu:draw() end
+    end)
+
+    stats_api.session_progress(function(res, err)
+        if err then return log.err(err) end
+
+        Stats.progress = {}
+
+        local progress = res
+        for _, p in ipairs(progress) do
+            Stats.progress[p.difficulty:lower()] = p
+        end
+
+        if _Lc_menu then _Lc_menu:draw() end
+    end)
+end
+
+function Stats.update()
+    Stats.update_streak()
+    Stats.update_sessions()
+
+    if _Lc_menu then _Lc_menu:draw() end
+end
+
+return Stats
diff --git a/lua/leetcode/logger/spinner.lua b/lua/leetcode/logger/spinner/init.lua
similarity index 100%
rename from lua/leetcode/logger/spinner.lua
rename to lua/leetcode/logger/spinner/init.lua
diff --git a/lua/leetcode/logger/spinner/judge.lua b/lua/leetcode/logger/spinner/judge.lua
new file mode 100644
index 00000000..de3d1a48
--- /dev/null
+++ b/lua/leetcode/logger/spinner/judge.lua
@@ -0,0 +1,30 @@
+local Spinner = require("leetcode.logger.spinner")
+
+---@class lc.Judge : lc.Spinner
+local Judge = {}
+Judge.__index = Judge
+setmetatable(Judge, Spinner)
+
+local check_state = {
+    ["PENDING"] = "Pending…",
+    ["STARTED"] = "Judging…",
+    ["SUCCESS"] = "Finished",
+    ["FAILURE"] = "Failed", -- CODE: 16
+}
+
+function Judge:from_state(state)
+    self:update(check_state[state])
+
+    if state == "PENDING" then
+        self:change("points")
+    else
+        self:change("dot")
+    end
+end
+
+function Judge:init()
+    local spinner = Spinner:init(check_state["PENDING"], "points")
+    return setmetatable(spinner, Judge)
+end
+
+return Judge
diff --git a/lua/leetcode/pickers/question.lua b/lua/leetcode/pickers/question.lua
index 577576c5..313d2bff 100644
--- a/lua/leetcode/pickers/question.lua
+++ b/lua/leetcode/pickers/question.lua
@@ -29,23 +29,17 @@ end
 ---@param question lc.cache.Question
 local function display_difficulty(question)
     local hl = ui_utils.diff_to_hl(question.difficulty)
-    return { "󱓻", hl }
+    return { config.icons.square, hl }
 end
 
 ---@param question lc.cache.Question
 local function display_user_status(question)
-    if question.paid_only and not config.auth.is_premium then
-        return { "", "leetcode_medium" }
+    if question.paid_only then
+        return config.auth.is_premium and config.icons.hl.unlock or config.icons.hl.lock
     end
 
-    local user_status = {
-        ac = { "", "leetcode_easy" },
-        notac = { "󱎖", "leetcode_medium" },
-        todo = { "", "leetcode_alt" },
-    }
-
     if question.status == vim.NIL then return { "" } end
-    return user_status[question.status] or { "" }
+    return config.icons.hl.status[question.status] or { "" }
 end
 
 ---@param question lc.cache.Question
diff --git a/lua/leetcode/runner/init.lua b/lua/leetcode/runner/init.lua
index 7b6e17e9..465312a4 100644
--- a/lua/leetcode/runner/init.lua
+++ b/lua/leetcode/runner/init.lua
@@ -1,6 +1,7 @@
 local log = require("leetcode.logger")
 local interpreter = require("leetcode.api.interpreter")
 local config = require("leetcode.config")
+local Judge = require("leetcode.logger.spinner.judge")
 
 ---@type Path
 local leetbody = config.storage.cache:joinpath("body")
@@ -20,11 +21,13 @@ Runner.run = vim.schedule_wrap(function(self, submit)
 
     local ok, err = pcall(Runner.handle, self, submit)
     if not ok then
-        Runner.running = false
+        self:stop()
         log.error(err)
     end
 end)
 
+Runner.stop = function() Runner.running = false end
+
 function Runner:handle(submit)
     Runner.running = true
     local question = self.question
@@ -36,30 +39,23 @@ function Runner:handle(submit)
         data_input = not submit and table.concat(question.console.testcase:content(), "\n") or nil,
     }
 
-    local function callback(item)
-        if type(item) == "boolean" then
-            if item then
-                question.console.result:clear()
-            else
-                Runner.running = false
-            end
-        else
-            self:callback(item)
+    local judge = Judge:init()
+    local function callback(item, state, err)
+        if err or item then self:stop() end
+
+        if item then
+            judge:stop(item.status_msg, item._.success)
+        elseif state then
+            judge:from_state(state)
+        elseif err then
+            judge:stop(err.msg or "Something went wrong", false)
         end
-    end
 
-    leetbody:write(vim.json.encode(body), "w")
-    if not submit then
-        interpreter.interpret_solution(question.q.title_slug, leetbody:absolute(), callback)
-    else
-        interpreter.submit(question.q.title_slug, leetbody:absolute(), callback)
+        if item then question.console.result:handle(item) end
     end
-end
 
----@param item lc.interpreter_response
-function Runner:callback(item)
-    Runner.running = false
-    self.question.console.result:handle(item)
+    leetbody:write(vim.json.encode(body), "w")
+    interpreter.run(submit, question, leetbody:absolute(), callback)
 end
 
 ---@param question lc.ui.Question
diff --git a/lua/leetcode/translator.lua b/lua/leetcode/translator.lua
index aca8ef1b..5629c380 100644
--- a/lua/leetcode/translator.lua
+++ b/lua/leetcode/translator.lua
@@ -63,6 +63,7 @@ local translate = {
     ["active days"] = "累计提交天数",
     ["max streak"] = "连续提交",
     ["more challenges"] = "更多挑战",
+    ["session"] = "进度",
 
     -- difficulty
     ["all"] = "所有",
diff --git a/lua/leetcode/utils.lua b/lua/leetcode/utils.lua
index 8afbb822..6e366cc2 100644
--- a/lua/leetcode/utils.lua
+++ b/lua/leetcode/utils.lua
@@ -103,4 +103,11 @@ function utils.norm_ins(str)
     return ins:sub(2, #ins - 1)
 end
 
+function utils.set_question_lines(q, code)
+    if not vim.api.nvim_buf_is_valid(q.bufnr) then return end
+
+    local s_i, e_i = q:range()
+    vim.api.nvim_buf_set_lines(q.bufnr, s_i - 1, e_i, false, vim.split(code, "\n"))
+end
+
 return utils
