diff --git a/Panda.hs b/Panda.hs
deleted file mode 100644
--- a/Panda.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-module Panda
-  (
-    module Panda.Controller.Application,
-    panda
-  ) where
-
-import Panda.Controller.Application
-import Kibro
-
-panda = startKibro pages
diff --git a/Panda/Config/Global.hs b/Panda/Config/Global.hs
deleted file mode 100644
--- a/Panda/Config/Global.hs
+++ /dev/null
@@ -1,95 +0,0 @@
-module Panda.Config.Global where
-
-import Panda.Helper.Helper
-import MPS
-import Prelude hiding ((.), (/), (^), id, readFile)
-
-import System.FilePath hiding ((<.>))
-import Control.Arrow ((>>>), (&&&), (***))
-import Data.Maybe
-import System.Directory
-
-import Panda.Type.Reader
-import Panda.Type.Sidebar
-import Panda.Type.Theme
-
-db_id       = "db"
-flat_id     = "."
-post_id     = "blog"
-config_id   = "config"
-tag_id      = "tag"
-comment_id  = "comment"
-sidebar_id  = "sidebar"
-theme_id    = "theme"
-
-db_uri      = db_id
-flat_uri    = db_uri / flat_id
-
-config_uri  = flat_uri / config_id / "site.txt"
-sidebar_uri = flat_uri / config_id / sidebar_id
-post_uri    = flat_uri / post_id
-tag_uri     = flat_uri / tag_id
-comment_uri = flat_uri / comment_id
-theme_uri   = flat_uri / config_id / theme_id
-
-
--- unsafe, must restart server after changing config file, sorry about that ...
--- but these configs are read only, and keep the view pure, so no monad headaches involved.
-
-user_config = parse_config config_uri
-
-config_for' s d = user_config.lookup s .fromMaybe d
-config_for s    = config_for' s s
-
-blog_title      = config_for "blog_title"
-blog_subtitle   = config_for "blog_subtitle"
-host_name       = config_for "host_name"
-author_email    = config_for "author_email" 
-
-per_page        = config_for' "per_page" "7" .read :: Int
-navigation      = config_for' "navigation" "About" .parse_list.("Home" :)
-
-panda_url       = "http://github.com/nfjinjing/panda"
-root            
-  | user_root.belongs_to ["/", ""]  = "/"
-  | otherwise                       =  user_root.("/" /).remove_trailing_slash
-  where user_root = config_for' "root" "/"
-
-default_reader  = Markdown
-
-sidebar = config_for "sidebar" .parse_list .map (sidebar_uri /).select (to_utf8 >>> doesFileExist >>> purify )
-  .map (read_sidebar_item default_reader >>> purify)
-  
-favicon = config_for' "favicon" "/favicon.ico"
-
-
--- extensions
-analytics_account_id = config_for "analytics_account_id"
-
--- theme
-default_theme = blank_theme
-user_theme_name = config_for "theme"
-user_theme_uri = (theme_uri / user_theme_name) ++ ".txt"
-
-theme = if user_theme_uri.doesFileExist.purify
-  then parse_config user_theme_uri .(("name", user_theme_name) : ) .to_theme
-  else default_theme
-  
--- custom
-as_l s                   = "[" ++ s ++ "]"
-post_date_format         = config_for' "post_date_format" "%y-%m-%d"
-comment_date_format      = config_for' "comment_date_format" "%y-%m-%d %T"
-url_date_format          = config_for' "url_date_format" "%y-%m-%d"
-url_date_matcher         = config_for' "url_date_matcher" "\\d{2}-\\d{2}-\\d{2}"
-url_title_subs           = config_for' "url_title_subs" "" .as_l .read :: [(String, String)]
-url_date_title_seperator = config_for' "url_date_title_seperator" " "
-
--- summary
-cut              = config_for' "cut" "✂-----" .to_utf8
-parse_boolean    = belongs_to ["true", "1", "y", "yes", "yeah"]
-summary_for_root = config_for "summary_for_root" .parse_boolean
-summary_for_tag  = config_for "summary_for_tag"  .parse_boolean
-summary_for_rss  = config_for "summary_for_rss"  .parse_boolean
-
-
-
diff --git a/Panda/Controller/Application.hs b/Panda/Controller/Application.hs
deleted file mode 100644
--- a/Panda/Controller/Application.hs
+++ /dev/null
@@ -1,172 +0,0 @@
--- the controller is the most complex module for a reason:
--- models are made as simple and small as possible
--- views are pure functions.
-
--- a note on encoding:
--- IOs are in utf-8 filter, internally use bytes
-
-{-# OPTIONS -fno-monomorphism-restriction #-}
-
-module Panda.Controller.Application where
-
-import qualified Data.Map as Map
-
--- env
-import Panda.Helper.Env hiding (tag, uri)
-import Panda.Helper.StateHelper
-import Prelude hiding ((.), (/), (^), id)
-import qualified Panda.Type.State as S
-import qualified Panda.Config.Global as G
-import qualified Panda.Type.Pager as Pager
-
--- model
-import qualified Panda.Model.Post as Post
-import qualified Panda.Model.Static as Static
-import qualified Panda.Model.Tag as Tag
-import qualified Panda.Model.Comment as Comment
-
--- view
-import qualified Panda.View.Control.Post as PostV
-import qualified Panda.View.Widget.Template as T
-import qualified Panda.View.Control.Static as StaticV
-import qualified Panda.View.Control.Tag as TagV
-import qualified Panda.View.Control.Search as SearchV
-import qualified Panda.View.Widget.RSS as RSSV
-
--- helpers
--- liftIO: move to an (u)pper level monad
-u = liftIO
-blog_regex = G.url_date_matcher
-
-init_post tags x = do
-  x.Tag.fill_tag tags.Comment.fill_comment_size.u
-
-tag_list = Tag.list.u
-
-
--- main controller
-
-pages = 
-  [ ("$"                  ,index              )
-  , ("(\\?.+)?$"          ,index              )
-  , ("rss.xml$"           ,index_feed         )
-  , (blog_regex           ,blog               )
-  , ("static/."           ,static             )
-  , ("tag/.*/rss.xml$"    ,tag_feed           )
-  , ("tag/."              ,tag                )
-  , ("search"             ,search             )
-  , ("comment/create"     ,comment_create     )
-  ] .map_fst ((G.root /) >>> ("^" ++))
-  ++
-  [ ("^" ++ G.root ++ "$" ,index              )
-  , ("^" ++ G.root ++ "?" ,index              )
-  ]
-  
-
-
-index = do
-  posts <- Post.list.u
-  p <- paginate posts
-  tags <- tag_list
-  let nav = if p.Pager.current == 1 then home_nav else no_navigation
-  let state = S.empty { S.uid = G.post_id, S.pager = p, S.tags = tags, S.nav_location = nav }
-  
-  posts.mapM (init_post tags) ^ PostV.list state >>= output_html
-
-index_feed = do
-  posts <- Post.list.u
-  posts.RSSV.rss "" "" .output
-
-blog = do
-  id <- uri ^ Post.uri_to_id
-  blog <- Post.get id .u
-  comments <- Comment.list_for id .u
-  tags <- tag_list
-  
-  test_data <- S.mk_human_test .u
-  let state = S.empty { S.uid = id, S.tags = tags, S.resource_title = blog.resource_title, S.human_test_data = test_data }
-  blog.(init_post tags) ^ PostV.view state comments >>= output_html
-
-
-static = do
-  id <- uri
-  static_page <- Static.get id .u
-  tags <- tag_list
-  
-  let nav_id = static_page.Static.uid.id_to_resource
-  let nav   = if nav_id.belongs_to G.navigation then nav_id else no_navigation
-  let state = S.empty { S.uid = id, S.tags = tags, S.nav_location = nav, S.resource_title = static_page.resource_title }
-  StaticV.view state static_page .output_html
-
-tag = do
-  id <- uri
-  tags <- tag_list
-  let tag_name = Tag.get_name id
-  case Tag.tag_map' tags .Map.lookup tag_name of
-    Nothing -> not_found
-    Just post_set -> do
-      posts <- post_set.to_list.rsort.mapM (Post.get) .u >>= mapM (init_post tags)
-      p <- paginate posts
-      let state = S.empty { S.uid = id, S.pager = p, S.tags = tags, S.resource_title = tag_name.Tag.resource_title_from_name }
-      posts.TagV.view state.output_html
-
-tag_feed = do
-  id <- uri ^ (split "/" >>> init >>> join "/")
-  tags <- tag_list
-  let tag_name = Tag.get_name id
-  case Tag.tag_map' tags .Map.lookup tag_name of
-    Nothing -> not_found
-    Just post_set -> do
-      posts <- post_set.to_list.rsort.mapM (Post.get) .u ^ map (Tag.fill_tag tags)
-      posts.RSSV.rss G.tag_id tag_name.output
-
-search = do
-  s <- param_with_default "s" ""
-  posts <- Post.search s .u
-
-  p <- paginate posts
-  tags <- tag_list
-  query <- uri
-  let state = S.empty { S.uid = query, S.pager = p, S.tags = tags, S.resource_title = query }
-
-  posts.mapM (init_post tags) >>= \x -> x.SearchV.view state s.output_html
-
-comment_create = do
-  post_id <- input_with_default (show_data Comment.PostId) (G.post_id / "nothing")
-  exists <- (G.flat_uri / post_id.to_utf8) .doesFileExist .u
-  let valid_path = equalFilePath G.post_id (takeDirectory post_id)
-  checked <- check_create
-  
-  if [checked, valid_path, exists].and
-    then
-      inputs >>= (Comment.create >>> u)
-    else
-      return ()
-  redirect $ (post_id.Post.id_to_uri.to_utf8).urlEncode
-
-get_input_data = show_data >>> get_input
-
-check_human = do
-  x <- [Comment.LeftNumber, Comment.RightNumber, Comment.Operator, Comment.HumanHack].mapM get_input_data ^ map fromJust
-  let [l, r, op, h] = x
-  return $ S.simple_eval (l.read) (r.read) (op.S.read_op) .is (h.read)
-
--- create helper, should be refactored to some common aspect
-check_create =
-  [ validate Comment.Author     ( length >>> (> 0))
-  , validate Comment.AuthorLink ( const True)
-  , validate Comment.Body       ( length >>> (> 0))
-  , validate Comment.EmptyField ( empty )
-  , validate Comment.LeftNumber ( belongs_to (S.nums.map show))
-  , validate Comment.RightNumber ( belongs_to (S.nums.map show))
-  , validate Comment.Operator ( belongs_to (S.ops.map S.display_op))
-  , validate Comment.HumanHack ( belongs_to (S.nums.map show))
-  , check_human
-  ] .sequence ^ and
-
-
-validate s f = do
-  maybe_s <- get_input_data s
-  case maybe_s of
-    Nothing -> return False
-    Just v -> return $ f v
diff --git a/Panda/Extension/Analytics/T.hs b/Panda/Extension/Analytics/T.hs
deleted file mode 100644
--- a/Panda/Extension/Analytics/T.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Panda.Extension.Analytics.T where
-
-import Panda.Helper.Env
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Config.Global as G
-
-analytics_snippet = 
-  [ "<script type=\"text/javascript\">                                                                                                  "
-  , "var gaJsHost = ((\"https:\" == document.location.protocol) ? \"https://ssl.\" : \"http://www.\");                                  "
-  , "document.write(unescape(\"%3Cscript src='\" + gaJsHost + \"google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\")); "
-  , "</script>                                                                                                                          "
-  , "<script type=\"text/javascript\">                                                                                                  "
-  , "var pageTracker = _gat._getTracker(\"UA-xxxxxx-x\");                                                                               "
-  , "pageTracker._trackPageview();                                                                                                      "
-  , "</script>                                                                                                                          "
-  ]
-  .map strip
-  .join "\n"
-  .gsub "UA-xxxxxx-x" G.analytics_account_id
diff --git a/Panda/Helper/Env.hs b/Panda/Helper/Env.hs
deleted file mode 100644
--- a/Panda/Helper/Env.hs
+++ /dev/null
@@ -1,38 +0,0 @@
--- this sets some scoping for every module, which prevents importing
--- common modules in every file
-
-module Panda.Helper.Env  (
-    module MPS                 
-  , module Control.Monad       
-  , module Control.Arrow       
-  , module Data.List           
-  , module Network.URI         
-  , module Network.CGI          
-  , module System.Locale       
-  , module System.Time.Parse
-  , module Panda.Helper.Helper
-  , module Data.Maybe
-  , module System.FilePath
-  , module System.IO.UTF8
-  , module Text.XHtml.Strict
-  , module System.Time
-  , module System.Directory
-  , module Data.Foldable
-) where
-  
-import MPS hiding (base, col, sub, date)
-import Control.Monad hiding (join)
-import Control.Arrow ((>>>), (&&&), (***))
-import Data.List hiding (find)
-import Network.URI
-import Network.CGI hiding (Html)
-import System.Locale
-import System.Time.Parse
-import Panda.Helper.Helper
-import Data.Maybe
-import System.FilePath hiding ((<.>))
-import System.IO.UTF8 (readFile, writeFile)
-import Text.XHtml.Strict hiding (select)
-import System.Time
-import System.Directory
-import Data.Foldable (find)
diff --git a/Panda/Helper/Helper.hs b/Panda/Helper/Helper.hs
deleted file mode 100644
--- a/Panda/Helper/Helper.hs
+++ /dev/null
@@ -1,136 +0,0 @@
--- helper module is a central place for reusable functions
--- for this project, more general helpers are usually moved
--- to MPS package, for multi-project usage
-
-{-# OPTIONS -fno-monomorphism-restriction #-}
-
-module Panda.Helper.Helper where
-
-import System.IO.UTF8 (readFile, writeFile)
-import Network.URI
-import Network.CGI
-import System.FilePath.Posix ((</>))
-import Control.Arrow ((>>>), (&&&))
-import Text.XHtml.Strict
-import Control.Monad hiding (join)
-import Data.Maybe
-import Panda.Type.Pager as Pager hiding (empty)
-import MPS hiding (date)
-import Prelude hiding ((.), (/), (^), id, readFile, writeFile)
-import Char
-import Data.List
-import qualified Data.List as L 
-import Panda.Type.Reader
-import System.FilePath.Posix hiding ((<.>))
-import System.Time
-
-
-(/) = (</>)
-infixl 5 /
-
-(^) = (<.>)
-infixl 9 ^
-
--- global
-parse_config_io s = readFile s ^ (\x -> x.filter_comment.lines.map strip .map (split "\\s*=\\s*") .map fill_snd_blank .map tuple2)
-  where fill_snd_blank [x] = [x,""]
-        fill_snd_blank xs = xs
-parse_config = parse_config_io >>> purify
-
-write_config_io s xs = xs.map(\(x, y) -> x ++ " = " ++ y) .join "\n" .writeFile s
-
--- model
-take_extension = takeExtension >>> split "\\." >>> last
-
--- controller
-raw_uri = requestURI ^ (uriPath >>> urlDecode >>> tail >>> remove_trailing_slash >>> from_utf8 )
-remove_trailing_slash s = if s.last.is '/' then s.init else s
-  
-params = do
-  query_string <- requestURI ^ uriQuery 
-  case query_string of
-    '?':s -> s.formDecode .map_snd unescape_unicode_xml.return
-    otherwise -> return []
-
-inputs = getInputs ^ map_snd (strip >>> unescape_unicode_xml)
-
-param_with_default s d = params ^ (lookup s >>> fromMaybe d )
-input_with_default s d = inputs ^ (lookup s >>> fromMaybe d )
-
-get_param s = params ^ lookup s
-get_input s = inputs ^ lookup s
-
-just_param s = get_param s ^ fromJust
-just_input s = get_input s ^ fromJust
-
-full_paginate length total = liftM5 ( Pager length ) current has_next has_previous next previous where
-  current      = param_with_default "page" "1" ^ read
-  has_next     = current ^ ( (* length) >>> (< total.fromIntegral) )
-  has_previous = current ^ (> 1)
-  next         = current ^ (+ 1)
-  previous     = current ^ (+ (-1) )
-
-
-for_current_page p xs = xs.drop ((p.Pager.current - 1) * p.Pager.length) .take (p.Pager.length)
-
--- id: /type/resoruce
-id_to_type id     = id.split "/" .first
-id_to_resource id = id.split "/" .tail.join "/"
-no_navigation     = ""
-home_nav          = "Home"
-
-
--- view
-id               = identifier
-
-css_link l       = itag "link" ! [rel "stylesheet", thetype "text/css", href l]
-js_link l        = itag "script" ! [thetype "text/javascript", src l]
-js_src s         = tag "script" ! [thetype "text/javascript"] << s
-rss_link l       = itag "link" ! [rel "alternate", thetype "application/rss+xml", href l, title "RSS 2.0"]
-favicon_link l   = itag "link" ! [rel "icon", thetype "image/png", href l]
-meta_tag         = meta ! [httpequiv "Content-Type", content "text/html; charset=utf-8"]
-
-div_id s         = thediv ! [id s]
-div_class s      = thediv ! [theclass s]
-div_class_id x y = thediv ! [theclass x, id y]
-
-output_html      = output ... renderHtml
-
-spaced_url = gsub "/" " / "
-
--- config
-parse_list s = s.split "," .map strip .reject null
-not_found = getVar "REQUEST_URI" >>= (fromMaybe "" >>> outputNotFound)
-
-
--- generic
-
-starts_with _ [] = False
-starts_with (x:xs) (y:ys) | x == y = starts_with xs ys
-                          | otherwise = False
-
-ends_with x y = starts_with (x.reverse) (y.reverse)
-
-capitalize (x:xs) = [x].upper ++ xs.lower
-camel_case = split "_" >>> map capitalize >>> join'
-snake_case = gsub "\\B[A-Z]" "_\\&" >>> lower
-show_data = show >>> snake_case
- 
-
--- class
-class DataRenderer a where
-    render_data :: a -> Html
-
-class Resource a where
-    resource_title :: a -> String
-
-class Markable a where
-    markup :: a -> Html
-    
-class Datable a where
-    date :: a -> CalendarTime
-
-class Addressable a where
-    uri :: a -> String
-
- 
diff --git a/Panda/Helper/StateHelper.hs b/Panda/Helper/StateHelper.hs
deleted file mode 100644
--- a/Panda/Helper/StateHelper.hs
+++ /dev/null
@@ -1,43 +0,0 @@
--- trying to merge this helper with the generic one results in cyclic imports
-
-{-# OPTIONS -fno-monomorphism-restriction #-}
-
-module Panda.Helper.StateHelper where
-
-import Panda.Helper.Env
-import qualified Panda.Config.Global as G
-import Prelude hiding ((.), (/), (^), id)
-
-
--- G.root = /blog
--- raw_uri = blog/x
--- full_uri = /blog/x
--- uri = full_uri - (/blog/)
-remove_root s
-  | G.root.is "/" = s
-  | otherwise     = s.slice (G.root.length) (s.length)
-
-uri = raw_uri ^ remove_root
--- uri = raw_uri
-
-
--- global
-parse_date format s = case maybe_d of
-  Nothing -> Nothing
-  Just d -> Just $ if d.ctYear < 1910 then d {ctYear = d.ctYear + 100} else d 
-  where
-    maybe_d = parseCalendarTime defaultTimeLocale format s
-  
-format_date          = formatCalendarTime defaultTimeLocale
-default_date         = parse_date "%Y-%m-%d %T" "2000-1-1 00:00:00".fromJust
-parse_post_date      = parse_date G.post_date_format
-default_parse_date s = s.parse_post_date .fromMaybe default_date
-
--- controller
-paginate = length >>> full_paginate (G.per_page)
-
-cut       = G.cut
-cut_re    = "^\\s*" ++ cut
-match_cut = to_utf8 >>> match cut_re
-is_cut    = match_cut >>> isJust
-split_cut = to_utf8 >>> split cut_re
diff --git a/Panda/Model/Comment.hs b/Panda/Model/Comment.hs
deleted file mode 100644
--- a/Panda/Model/Comment.hs
+++ /dev/null
@@ -1,96 +0,0 @@
-module Panda.Model.Comment where
-
--- env
-import Panda.Helper.Env hiding (title, body, size)
-import Prelude hiding ((.), (/), (^), id, readFile, writeFile)
-import qualified Panda.Config.Global as G
-import Panda.Type.Reader
-import qualified Panda.Model.Post as Post
-import System.Directory
-import Panda.Helper.StateHelper
-import Network.Gravatar
-
-data Comment = Comment
-  { uid    :: String     -- comment/08-09-04 blog title
-  , author :: String
-  , body   :: String
-  , author_email :: String
-  , author_link    :: String
-  }
-  deriving (Show, Eq)
-
-data CommentData = 
-    Author
-  | AuthorEmail
-  | AuthorLink
-  | Body
-  | PostId
-  deriving (Show)
-
-data SpanFilter = 
-    HumanHack 
-  | EmptyField
-  | LeftNumber
-  | RightNumber
-  | Operator
-  deriving (Show)
-
-instance Resource Comment where
-  resource_title = uid >>> spaced_url
-  
-instance Markable Comment where
-  markup x  = render_to_html Markdown (x.body)
-
-instance Datable Comment where
-  date x  = x.uid.split "/".last.default_parse_date
-
-
--- CRUD
-list_for post_id = do
-  has_comments <- doesDirectoryExist d
-  if has_comments
-    then ls d ^ reject (match "\\.meta$" >>> isJust) ^ rsort ^ map (G.comment_id / r /) >>= mapM (from_utf8 >>> get)
-    else return []
-  where
-    d = (G.comment_uri / r)
-    r = post_id.id_to_resource.to_utf8
-
-
-get id = do
-  body <- path.readFile
-  meta <- (path ++ ".meta").parse_config_io
-  let at s         = meta.lookup (s.show_data) .fromJust
-  let author       = at Author
-  let author_email = at AuthorEmail
-  let author_link  = at AuthorLink
-
-  return $ Comment id author body author_email author_link
-  where
-    path = G.flat_uri / id.to_utf8
-
-gravatar_default_size = size 40
-gravatar_link x = gravatarWith (x.author_email) Nothing gravatar_default_size Nothing
-
-create h = do
-  let at s = h.lookup (s.show_data) .fromJust
-  let body        = at Body
-  let post_id     = at PostId
-
-  timestamp <- ( getClockTime >>= toCalendarTime ) ^ format_date G.comment_date_format
-  let comment_path = post_id_to_uid post_id .to_utf8
-  createDirectoryIfMissing True comment_path
-  
-  let uid = comment_path / timestamp
-  writeFile uid body
-  let meta = [Author, AuthorLink, AuthorEmail] .labeling at .map_fst show_data
-  write_config_io (uid ++ ".meta") meta
-
--- extra
-from_post_id x = Comment (post_id_to_uid x) "" "" "" ""
-
-post_id_to_uid x = G.flat_uri / G.comment_id / x.split "/" .last
-uid_to_post_id x = G.post_id / x.split "/" .last
-
-fill_comment_size x = do
-  size <- x.Post.uid.list_for ^ length 
-  return $ x { Post.comment_size = size }
diff --git a/Panda/Model/Post.hs b/Panda/Model/Post.hs
deleted file mode 100644
--- a/Panda/Model/Post.hs
+++ /dev/null
@@ -1,78 +0,0 @@
--- what about the performance?
--- Haskell takes care of that, since IOs are also lazy.
--- Posts are not read unless specifically required, i.e. after pagination
-
-module Panda.Model.Post where
-
--- env
-import Panda.Helper.Env hiding (match, title, body, date)
-import qualified MPS as MPS
-import Prelude hiding ((.), (/), (^), id, readFile)
-import qualified Panda.Config.Global as G
-import Panda.Type.Reader
-import Panda.Helper.StateHelper hiding (uri)
-import Panda.Helper.Helper (date)
-
-data Post = Post 
-  { uid :: String     -- blog/08-09-04 blog title
-  , title :: String
-  , body :: String
-  , tags :: [String]
-  , comment_size :: Int
-  , reader :: Reader
-  }
-  deriving (Show, Eq)
-
-instance Resource Post where
-  resource_title = title
-
-instance Markable Post where
-  markup x  = render_to_html (x.reader) (x.full)
-  
-instance Datable Post where
-  date = uid >>> get_date
-
-instance Addressable Post where
-  uri = uid >>> id_to_uri
-
--- CRUD
-list = ls G.post_uri ^ map from_utf8 ^ rsort ^ map (G.post_id /) >>= mapM get
-
-get id        = liftM4 (Post id (get_title id) ) (get_body id) (return []) (return 0) (return $ get_reader id)
-get_extension = takeExtension
-get_title id  = id.words.tail.unwords.dropExtension
-get_body id   = (G.flat_uri / id.to_utf8) .readFile
-get_reader id = id.take_extension.guess_reader.fromMaybe G.default_reader
-get_date id   = id.words.first.split "/".last.default_parse_date
-
-match s x = [title, body] .map (send_to x >>> lower >>> isInfixOf (s.lower)) .or
-search "" = return []
-search s  = list ^ filter (match s)
-
-summary = body >>> split_cut >>> first >>> from_utf8
-full x | x.body.match_cut.isNothing = x.body
-full x = ( xs.takeWhile not_cut ++ xs.dropWhile not_cut .tail ).unlines where
-  not_cut = is_cut >>> not
-  xs = x.body.lines
-
-has_continue = body >>> match_cut >>> isJust
-
-
--- extra
-id_to_uri id = G.root / ( pretty_date ++ G.url_date_title_seperator ++ formatted_title ++ ext ) where
-  formatted_title = G.url_title_subs.map (\(a,b) -> gsub a b).inject (id.get_title) apply
-  pretty_date     = id.get_date.format_date G.url_date_format
-  ext = id.get_extension
-
-uri_to_id s = G.post_id / (d ++ " " ++ t) where
-  (raw_d, (prefix, title_with_sep)) = s.MPS.match G.url_date_matcher.fromJust.fst
-  raw_t = title_with_sep.drop (G.url_date_title_seperator.length)
-  t = G.url_title_subs.map (\(a,b) -> gsub b a) .inject raw_t apply
-  d = raw_d.parse_date G.url_date_format .fromJust.format_date G.post_date_format
-
-
--- summary
-markup_summary x = post_summary x +++ next where
-  post_summary = (reader &&& summary) >>> splash render_to_html
-  next = if x.has_continue then toHtml $ hotlink (x.uri) << "Read the rest of the post »" else toHtml ""
-  
diff --git a/Panda/Model/Static.hs b/Panda/Model/Static.hs
deleted file mode 100644
--- a/Panda/Model/Static.hs
+++ /dev/null
@@ -1,28 +0,0 @@
-module Panda.Model.Static where
-
--- env
-import Panda.Helper.Env hiding (match, body)
-import Prelude hiding ((.), (/), (^), id, readFile)
-import qualified Panda.Config.Global as G
-import Panda.Type.Reader
-
-data Static = Static 
-  { uid :: String
-  , body :: String
-  , reader :: Reader
-  }
-  deriving (Show, Eq)
-
-instance Resource Static where
-  resource_title = uid >>> spaced_url
-
-instance Markable Static where
-  markup x = render_to_html (x.reader) (x.body)
-  
--- CRUD
-get id        = liftM2 (Static id) (get_body id) (return $ get_reader id)
-get_title     = id_to_resource >>> dropExtension
-get_body id   = (G.flat_uri / id.to_utf8) .readFile
-get_reader id = id.take_extension.guess_reader.fromMaybe G.default_reader
-
-title = uid >>> get_title
diff --git a/Panda/Model/Tag.hs b/Panda/Model/Tag.hs
deleted file mode 100644
--- a/Panda/Model/Tag.hs
+++ /dev/null
@@ -1,39 +0,0 @@
-module Panda.Model.Tag where
-
-import qualified Panda.Model.Post as Post
-
--- env
-import Panda.Helper.Env hiding (name)
-import Prelude hiding ((.), (/), (^), id, readFile)
-import qualified Data.Set as S
-import qualified Panda.Config.Global as G
-
-
-data Tag = Tag 
-  { uid :: String     -- tag/name
-  , name :: String
-  , resources :: S.Set String
-  }
-  deriving (Show, Eq)
-
-instance Resource Tag where
-  resource_title = uid >>> spaced_url
-
--- CRUD
-list             = ls G.tag_uri ^ map from_utf8 ^ map (G.tag_id /) >>= mapM get
-
-get id           = get_resources id ^ Tag id (get_name id)
-get_name id      = id.split "/" .tail.join'
-get_resources id = (G.flat_uri / id.to_utf8) .readFile
-  ^ filter_comment ^ lines ^ map (G.post_id / ) ^ to_set
-
-resource_title_from_name = name_to_id >>> spaced_url
-tag_map' xs       = xs . map (name &&& resources) . to_h
-tag_map           = list ^ tag_map'
-
-for_resource xs x = xs.select (resources >>> has x) .map name
-fill_tag xs x     = x { Post.tags = for_resource xs (x.Post.uid) }
-
--- extra
-sorted xs    = xs.sortBy(compare_by (resources >>> S.size)).reverse
-name_to_id x = G.tag_id / x
diff --git a/Panda/Type/Pager.hs b/Panda/Type/Pager.hs
deleted file mode 100644
--- a/Panda/Type/Pager.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Panda.Type.Pager where
-  
-data Pager = Pager {
-  length :: Int,
-  current :: Int,
-  has_next :: Bool,
-  has_previous :: Bool,
-  next :: Int,
-  previous :: Int
-  }
-  deriving (Eq, Show)
-  
-empty = Pager 0 0 False False 0 0
diff --git a/Panda/Type/Reader.hs b/Panda/Type/Reader.hs
deleted file mode 100644
--- a/Panda/Type/Reader.hs
+++ /dev/null
@@ -1,37 +0,0 @@
-{-# OPTIONS -XDeriveDataTypeable #-}
-
-module Panda.Type.Reader where
-
-import MPS  
-import Prelude hiding ((.), (/), (^), id)
-import qualified Data.Map as Map
-import Text.Pandoc
-import Control.Arrow ((>>>))
-import Text.XHtml.Strict
-import Data.Generics
-
-data Reader = Markdown | RST | HTML | Latex deriving (Show, Eq, Typeable, Data)
-
-readers = 
-  [ (Markdown,  ["markdown", "md"])
-  , (RST,       ["rst"]  )
-  , (HTML,      ["html", "htm"])
-  , (Latex,     ["tex", "latex"])
-  ]
-
-gen_lookup (r, xs) = xs.labeling (const r)
-reader_map         = readers.map gen_lookup.join'.to_h
-guess_reader ext   = reader_map.Map.lookup ext
-
-
-to_html r      = r defaultParserState >>> writeHtml defaultWriterOptions
-
--- this list can go on, as long as there is a library that does
--- the convertion. pretty extensible, isn't it.
-rr Markdown    = to_html readMarkdown
-rr RST         = to_html readRST
-rr HTML        = primHtml
-rr Latex       = to_html readLaTeX
-
-render_to_html = rr
-
diff --git a/Panda/Type/Sidebar.hs b/Panda/Type/Sidebar.hs
deleted file mode 100644
--- a/Panda/Type/Sidebar.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Panda.Type.Sidebar where
-
-import Panda.Helper.Env hiding (body)
-import Prelude hiding ((.), (/), (^), id, readFile)
-import Panda.Type.Reader
-
-data SidebarItem = SidebarItem
-  { name :: String
-  , body :: String
-  , reader :: Reader
-  }
-  deriving (Show, Eq)
-
-instance Markable SidebarItem where
-  markup x  = render_to_html (x.reader) (x.body)
-
-read_sidebar_item default_reader s = liftM2 (SidebarItem name) body (return reader) where
-  body = s.to_utf8.readFile
-  reader = s.take_extension.guess_reader.fromMaybe default_reader
-  name = s.takeFileName.dropExtension
diff --git a/Panda/Type/State.hs b/Panda/Type/State.hs
deleted file mode 100644
--- a/Panda/Type/State.hs
+++ /dev/null
@@ -1,64 +0,0 @@
-module Panda.Type.State where
-
-import qualified Panda.Type.Pager as Pager
-import Panda.Model.Tag
-import Random (randomRs, mkStdGen)
-import System.Time
-import Control.Arrow ((>>>), (&&&), (***))
-import MPS
-import Prelude hiding ((.), (/), (^), id, readFile, writeFile)
-
-data State = State
-  -- model state
-  { uid     :: String       -- current view resource
-  , pager   :: Pager.Pager        -- pager
-
-  -- theme state
-  , tags    :: [Tag]
-  , nav_location :: String
-  , resource_title :: String
-  , human_test_data :: HumanTestData
-  }
-  deriving (Show)
-
-show_left = human_test_data >>> left >>> show
-show_right = human_test_data >>> right >>> show
-show_op = human_test_data >>> op >>> display_op where
-
-read_op "+" = Plus
-read_op "-" = Minus
-read_op x = error ("can not read operator: " ++ x)
-
-display_op Plus = "+"
-display_op Minus = "-"
-
-data HumanTestData = HumanTestData 
-  { left :: Int
-  , right :: Int
-  , op :: Op
-  } deriving (Show)
-
-data Op = Plus | Minus deriving (Show)
-
-empty = State "" Pager.empty [] "" "" empty_human_test
-empty_human_test = HumanTestData 0 0 Plus
-
-(^) = (<.>)
-infixl 9 ^
-
-ops = [Plus, Minus]
-nums = [0, 5, 10, 15, 20]
-simple_eval a b Plus = a + b
-simple_eval a b Minus = a - b
-
-
-mk_human_test = do
-  seed <- (getClockTime >>= toCalendarTime) ^ ctPicosec ^ fromIntegral
-  let (a,b,c) = randomRs (0,100) (mkStdGen seed) .in_group_of 3 .map make_sample .dropWhile (good_test >>> not) .first
-  return $ HumanTestData a b c
-
-  where
-    make_sample [a,b,c] = ((get_num a), (get_num b), (get_op c))
-    good_test = splash3 simple_eval >>> belongs_to nums
-    get_num n = nums.at (n `mod` (nums.length))
-    get_op  n = ops.at (n `mod` (ops.length))
diff --git a/Panda/Type/Theme.hs b/Panda/Type/Theme.hs
deleted file mode 100644
--- a/Panda/Type/Theme.hs
+++ /dev/null
@@ -1,44 +0,0 @@
-module Panda.Type.Theme where
-
-import Prelude hiding ((.))
-import Panda.Helper.Env hiding (name, header)
-
-data Theme = Theme
-  { name        :: String
-  , container   :: String
-  , header      :: String
-  , navigation  :: String
-  , main        :: String
-  , sidebar     :: String
-  , footer      :: String
-  , css         :: [String]
-  , js          :: [String]
-  } deriving (Show, Read)
-  
-to_theme xs = Theme 
-  { name        = at "name"
-  , container   = at "container"
-  , header      = at "header"
-  , navigation  = at "navigation"
-  , main        = at "main"
-  , sidebar     = at "sidebar"
-  , footer      = at "footer"
-  , css         = at "css" .css_list
-  , js          = at "js"  .js_list
-  }
-  where
-    at s = xs.lookup s .fromJust
-    css_list s = s.parse_list.map (\x -> "/theme/" ++ at "name" ++ "/css/" ++ x ++ ".css")
-    js_list s  = s.parse_list.map (\x -> "/theme/" ++ at "name" ++ "/js/" ++ x ++ ".js")
-    
-blank_theme = Theme
-  { name        = ""
-  , container   = ""
-  , header      = ""
-  , navigation  = ""
-  , main        = ""
-  , sidebar     = ""
-  , footer      = ""
-  , css         = [""]
-  , js          = [""]
-  }
diff --git a/Panda/View/Atom/Comment.hs b/Panda/View/Atom/Comment.hs
deleted file mode 100644
--- a/Panda/View/Atom/Comment.hs
+++ /dev/null
@@ -1,66 +0,0 @@
-module Panda.View.Atom.Comment where
-  
-import Panda.Helper.Env hiding (title)
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Config.Global as G
-import Panda.Helper.StateHelper
-import Panda.Type.State hiding (uid)
-
--- view
-import qualified Panda.View.Atom.Tag as Tag
-
--- model
-import Panda.Model.Comment hiding (create)
-
--- render
-instance DataRenderer Comment where
-  render_data = entry
-  
-entry x = toHtml  
-  [ gravatar
-  , cite << a
-  , toHtml " says:"
-  , br
-  , p ! [theclass "small"] << comment_date x
-  , x.markup
-  ]
-  where
-    a = if x.author_link.null then x.author.toHtml else toHtml $ hotlink (x.author_link) << x.author
-    gravatar = thediv ! [theclass "gravatar"] << image ! [src (gravatar_link x)]
-
-comment_date x = toHtml $ x.date.format_date "%b %e, %Y at %I:%M %p"
-
-
--- form
-human_test_question state = ["What", "is", state.show_left, state.show_op, state.show_right, "?"] .join " "
-
-create state x = toHtml
-  [ h2 ! [id "respond"] << "Leave a Response"
-  , gui (G.root / "comment/create") ! [id "commentform"] <<
-    [ field Author 22 1 "Name (required)"
-    , field AuthorEmail 22 2 "Email (hidden)"
-    , field AuthorLink 22 3 "Website"
-    , field HumanHack 22 4 (human_test_question state)
-    , empty_field
-    , hidden_field LeftNumber (state.show_left)
-    , hidden_field RightNumber (state.show_right)
-    , hidden_field Operator (state.show_op)
-    , p << hidden (show_data PostId) (x.uid.uid_to_post_id)
-    , p << textarea ! [name (show_data Body), id "comment", cols "10", rows "20", strAttr "tabindex" "5"] << ""
-    , p << submit "submit" "Submit Comment" ! [strAttr "tabindex" "6"]
-    ]
-  ]
-
-field_with_value v x' s t m = p <<
-  [ label ! [thefor x] << small << m
-  , br 
-  , textfield x ! [size (s.show), strAttr "tabindex" (t.show), value v]
-  ]
-  where x = x'.show_data
-
-field x s t m = field_with_value "" x s t m
-
-hidden_field x m = hidden_field_with_value m x m
-hidden_field_with_value v x m = thespan ! [ thestyle "display: none;" ] << field_with_value v x 22 10 m
-
-empty_field = hidden_field_with_value "" EmptyField "Leave this field empty:"
diff --git a/Panda/View/Atom/Post.hs b/Panda/View/Atom/Post.hs
deleted file mode 100644
--- a/Panda/View/Atom/Post.hs
+++ /dev/null
@@ -1,39 +0,0 @@
-module Panda.View.Atom.Post where
-  
-import Panda.Helper.Env hiding (name, title)
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Config.Global as G
-import Panda.Helper.StateHelper hiding (uri)
-
--- view
-import qualified Panda.View.Atom.Tag as Tag
-
--- model
-import Panda.Model.Post
-
--- render
-instance DataRenderer Post where
-  render_data = entry Full
-
-data RenderStyle = Summary | Full
-
--- instance helpers
-entry style x = div_class "post" << [entry_title, entry_mark, entry_body] where
-  entry_title = x.title_link
-  entry_body  = div_class "entry" << show_content style x
-  entry_mark  = p ! [theclass "small"] << [ post_date, post_tags, post_comments ].map (send_to x)
-
-show_content Summary x = x.markup_summary
-show_content Full    x = x.markup
-
--- post could also be a summary
-render_summary x = if x.is True then entry Summary else render_data
-
-title_link x              = h2 << hotlink (x.uri) << x.title
-post_date x               = toHtml $ x.date.format_date "%b %e, %Y"
-
-post_tags x | x.tags.null = toHtml ""
-post_tags x = " | " +++ "Published in " +++ x.tags.map Tag.tag_link .intersperse (", ".toHtml)
-
-post_comments x | x.comment_size.is 0 = toHtml ""
-post_comments x = " | " +++ hotlink ( x.uri / "#comments") << (x.comment_size.show ++ " Comments")
diff --git a/Panda/View/Atom/Static.hs b/Panda/View/Atom/Static.hs
deleted file mode 100644
--- a/Panda/View/Atom/Static.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Panda.View.Atom.Static where
-  
-import Panda.Helper.Env
-import Panda.Model.Static
-
-instance DataRenderer Static where
-  render_data = markup
diff --git a/Panda/View/Atom/Tag.hs b/Panda/View/Atom/Tag.hs
deleted file mode 100644
--- a/Panda/View/Atom/Tag.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Panda.View.Atom.Tag where
-  
-import Panda.Helper.Env hiding (name)
-import Panda.Model.Tag
-import qualified Panda.Config.Global as G
-import Prelude hiding ((/))
-
-instance DataRenderer Tag where
-  render_data = name >>> tag_link
-
-tag_link s = toHtml $ hotlink (G.root / G.tag_id / s ) << s 
-
diff --git a/Panda/View/Control/Comment.hs b/Panda/View/Control/Comment.hs
deleted file mode 100644
--- a/Panda/View/Control/Comment.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-module Panda.View.Control.Comment where
-  
-import Panda.Helper.Env hiding (body, date)
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Type.State as State
-
-import Panda.Model.Comment hiding (create)
-import qualified Panda.Model.Post as Post
-
--- view
-import Panda.View.Control.Helper
-import Panda.View.Widget.Template
-import Panda.View.Atom.Comment as Comment
-
-
--- api
-styled_entry alt x = li ! [theclass alt] << x.render_data
-
-list [] = []
-list xs = 
-  [ h2 ! [id "comments"] << "Responses"
-  , olist ! [theclass "commentlist" ] << xs.zip (cycle ["comments-alt", ""]).map (splash styled_entry)
-  ]
-  
-create = Comment.create
diff --git a/Panda/View/Control/Helper.hs b/Panda/View/Control/Helper.hs
deleted file mode 100644
--- a/Panda/View/Control/Helper.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Panda.View.Control.Helper where
-
-import Panda.Helper.Env
-import Prelude hiding ((.), (/), (^), id, span)
-  
-import Panda.Type.Pager as Pager
-import qualified Panda.Config.Global as G
-
-
-nav p r = 
-  [ div_class "alignleft" << nav_previous p r
-  , div_class "alignright" << nav_next p r
-  ]
-  
-nav_previous p r = if p.Pager.has_previous 
-  then toHtml $ hotlink ( r' ++ "page=" ++ p.Pager.previous.show ) << previous_sign
-  else spaceHtml
-  where r' = if isSuffixOf "&" r then r else r ++ "?"
-    
-nav_next p r = if p.Pager.has_next
-  then toHtml $ hotlink ( r' ++ "page=" ++ p.Pager.next.show ) << next_sign
-  else spaceHtml
-  where r' = if isSuffixOf "&" r then r else r ++ "?"
-
-next_sign     = "Next Entries »"
-previous_sign = "« Previous Entries"
diff --git a/Panda/View/Control/Post.hs b/Panda/View/Control/Post.hs
deleted file mode 100644
--- a/Panda/View/Control/Post.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Panda.View.Control.Post where
-
--- env
-import Panda.Helper.Env
-import Prelude hiding ((.), (/), (^), id, span)
-
-import qualified Panda.Type.State as State
-import qualified Panda.Config.Global as G
-
--- model
-import qualified Panda.Model.Comment as Comment
-import Panda.View.Atom.Post
-import Panda.Model.Post
-
--- view
-import Panda.View.Control.Helper
-import qualified Panda.View.Control.Comment as CommentV
-import Panda.View.Widget.Template
-
--- entry view
-view state xs x = (x.render_data +++ CommentV.list xs +++ CommentV.create state (x.uid.Comment.from_post_id) ).page state
-
--- list view
-list state = for_current_page p >>> map render >>> (+++ nav p G.root) >>> page state where 
-  p = state.State.pager
-  render = render_summary G.summary_for_root
diff --git a/Panda/View/Control/Search.hs b/Panda/View/Control/Search.hs
deleted file mode 100644
--- a/Panda/View/Control/Search.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Panda.View.Control.Search where
-  
-import Panda.Helper.Env
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Type.State as State
-import qualified Panda.Config.Global as G
-
--- view
-import Panda.View.Atom.Post
-import Panda.View.Control.Helper
-import Panda.View.Widget.Template
-
-view state s = for_current_page p >>> map render_data >>> (+++ nav p ( (G.root / "search") ++ "?s=" ++ s ++ "&")) >>> page state
-  where p = state.State.pager
-
diff --git a/Panda/View/Control/Static.hs b/Panda/View/Control/Static.hs
deleted file mode 100644
--- a/Panda/View/Control/Static.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Panda.View.Control.Static where
-  
-import Panda.Helper.Env
-import Panda.View.Atom.Static
-import Panda.View.Widget.Template
-
-view state = render_data >>> page state
diff --git a/Panda/View/Control/Tag.hs b/Panda/View/Control/Tag.hs
deleted file mode 100644
--- a/Panda/View/Control/Tag.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Panda.View.Control.Tag where
-  
-import Panda.Helper.Env
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Type.State as State
-import qualified Panda.Config.Global as G
-
--- view
-import Panda.View.Atom.Post
-import Panda.View.Control.Helper
-import Panda.View.Widget.Template
-
-
-view state = for_current_page p >>> map render >>> (+++ nav p ( G.root / tag_id)) >>> page state where
-  p = state.State.pager
-  tag_id = state.State.uid
-  render = render_summary G.summary_for_tag
-
diff --git a/Panda/View/Widget/Body.hs b/Panda/View/Widget/Body.hs
deleted file mode 100644
--- a/Panda/View/Widget/Body.hs
+++ /dev/null
@@ -1,8 +0,0 @@
-module Panda.View.Widget.Body where
-
--- env
-import Panda.Helper.Env
-
-body_content x = div_id "maincontent" <<
-  div_class "content" << x
-
diff --git a/Panda/View/Widget/Footer.hs b/Panda/View/Widget/Footer.hs
deleted file mode 100644
--- a/Panda/View/Widget/Footer.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Panda.View.Widget.Footer where
-  
-import Panda.Helper.Env
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Config.Global as G
-
-
-footer c = div_class_id c "footer" <<
-  [ toHtml $ copyright
-  , toHtml $ "2008 "
-  , toHtml $ G.blog_title
-  , toHtml $ br
-  , toHtml $ "Powered by "
-  , toHtml $ hotlink G.panda_url << "Panda"
-  , toHtml $ " using "
-  , toHtml $ hotlink "http://www.haskell.org/" << "Haskell"
-  ]
diff --git a/Panda/View/Widget/Head.hs b/Panda/View/Widget/Head.hs
deleted file mode 100644
--- a/Panda/View/Widget/Head.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Panda.View.Widget.Head where
-
-import Panda.Helper.Env
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Config.Global as G
-import Panda.Type.Theme hiding (header)
-import qualified Panda.Type.State as S
-import Panda.View.Widget.Helper
-
-html_head state = header << ([meta_tag, title_tag state, favicon_tag, rss_tag state] ++ G.theme.css.map css_link ++ G.theme.js.map js_link )
-
-title_tag state = thetitle << [G.blog_title ++ state.S.resource_title.format_title]
-rss_tag state   = rss_link $ rss_url_link_pair state .fst
-favicon_tag = favicon_link G.favicon
-
-format_title [] = ""
-format_title s = " / " ++ s
diff --git a/Panda/View/Widget/Header.hs b/Panda/View/Widget/Header.hs
deleted file mode 100644
--- a/Panda/View/Widget/Header.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Panda.View.Widget.Header where
-  
-import Panda.Helper.Env hiding (header)
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Config.Global as G
-import Panda.View.Widget.SearchBar
-
-header c state =
-  div_class_id c "header" << 
-    [ search_bar
-    , site_name
-    ]
-
-site_name = toHtml $
-  [ toHtml $ hotlink G.root ! [theclass "logo"] << ""
-  , h1 << G.blog_title
-  , div_class "description" << G.blog_subtitle
-  ]
diff --git a/Panda/View/Widget/Helper.hs b/Panda/View/Widget/Helper.hs
deleted file mode 100644
--- a/Panda/View/Widget/Helper.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Panda.View.Widget.Helper where
-
-import Panda.Helper.Env
-import Prelude hiding ((.), (/), (^), id, span)
-  
-import qualified Panda.Config.Global as G
-import qualified Panda.Type.State as State
-import qualified Panda.Model.Tag as Tag
-
-rss_url_link_pair state = 
-  if tagged 
-  then
-    let tag_name = Tag.get_name uid in
-      link ( G.tag_id / tag_name) tag_name
-  else link "" "Home"
-  where
-    uid = State.uid state
-    tagged = uid.match "^tag/.+" .isJust
-    url r = G.root / r / "rss.xml"
-    link r s = (url r, hotlink (url r) ! [ theclass "feedlink" ] << s)
diff --git a/Panda/View/Widget/Navigation.hs b/Panda/View/Widget/Navigation.hs
deleted file mode 100644
--- a/Panda/View/Widget/Navigation.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Panda.View.Widget.Navigation where
-  
-import Panda.Helper.Env hiding (header)
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Config.Global as G
-import qualified Panda.Type.State as State
-
-navigation c state = 
-  div_class_id c "nav" <<
-    div_class "content" <<
-      ulist << G.navigation.map (nav_item (state.State.nav_location))
-
-nav_item nav s = li ! [theclass (home_tag ++ "page_item" ++ current)] << link s where
-  home_tag = if s == home_nav then "first " else ""
-  current = if s == nav then " current_page_item" else ""
-  link x = if x == home_nav then home_link else static_link x
-
-home_link     = hotlink G.root << home_nav
-static_link s = hotlink (G.root / "static" / s) << s.dropExtension
diff --git a/Panda/View/Widget/RSS.hs b/Panda/View/Widget/RSS.hs
deleted file mode 100644
--- a/Panda/View/Widget/RSS.hs
+++ /dev/null
@@ -1,37 +0,0 @@
-module Panda.View.Widget.RSS where
-
--- env
-import Panda.Helper.Env -- hiding (Language)
-import Prelude hiding ((.), (/), (^), id)
-import qualified Panda.Config.Global as G
-import qualified Panda.Type.Pager as Pager
-import Text.RSS
-import qualified Text.RSS as RSS
-
--- model
-import Panda.Model.Post as Post
-
--- RSS
-channel_rss_template = [ RSS.Language "en-us" ]
-
-render_rss x | G.summary_for_rss.is True = x.markup_summary.show
-render_rss x | otherwise                 = x.markup.show
-
-item_rss_template x = 
-  [ Title $ x.Post.title
-  , Description $ x.render_rss
-  , Author $ G.author_email
-  , Link $ x.item_uri
-  , PubDate $ x.date
-  ]
-
-item_uri x = nullURI { uriScheme = "http://", uriPath = host_link (x.uri) }
-rss_uri x  = nullURI { uriScheme = "http://", uriPath = host_link x }
-
-rss categary s xs = RSS title  (rss_uri link) title channel_rss_template (xs.take 20 .map item_rss_template)
-  .rssToXML.showXML
-  where
-    link = categary / s
-    title = if s.empty then G.blog_title else G.blog_title ++ " / " ++ s
-
-host_link s = G.host_name ++ (G.root / s)
diff --git a/Panda/View/Widget/SearchBar.hs b/Panda/View/Widget/SearchBar.hs
deleted file mode 100644
--- a/Panda/View/Widget/SearchBar.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Panda.View.Widget.SearchBar where
-  
-import Panda.Helper.Env hiding (header)
-import qualified Panda.Config.Global as G
-import Prelude hiding ((/))
-
-search_bar = div_id "search" << form ! [action (G.root / "search"), method "get"] << thediv << textfield "s"
diff --git a/Panda/View/Widget/Sidebar.hs b/Panda/View/Widget/Sidebar.hs
deleted file mode 100644
--- a/Panda/View/Widget/Sidebar.hs
+++ /dev/null
@@ -1,37 +0,0 @@
-module Panda.View.Widget.Sidebar where
-
-import Panda.Helper.Env hiding (rss_link)
-import Prelude hiding ((.), (/), (^), id, span)
-import qualified Panda.Type.State as State
-
-import qualified Data.Set as Set
-import qualified Panda.Config.Global as G
-import qualified Panda.Type.Sidebar as Sidebar
-import Panda.View.Widget.Helper
-
--- model
-import qualified Panda.Model.Tag as Tag
-
--- view
-
-sidebar c state = 
-  div_class_id c "sidebar" << unordList formatted_list
-  where
-    stock_list = 
-      [ feed state
-      , tag_list $ state.State.tags
-      ] 
-    custom_list = G.sidebar.map (\x -> h2 << x.Sidebar.name +++ x.markup)
-    formatted_list = (stock_list ++ custom_list) .intersperse hr
-
-feed state = toHtml
-  [ h2 << "Subscribe"
-  , p ! [theclass "feed"] << rss_link state
-  ]
-
-rss_link = rss_url_link_pair >>> snd
-tag_list tags = toHtml
-  [ h2 << "Tags"
-  , unordList $ tags.Tag.sorted.map tag_link]
-  
-tag_link x = ( hotlink (G.root / x.Tag.uid) << x.Tag.name ) +++ ( " (" ++ x.Tag.resources.Set.size.show ++ ")" )
diff --git a/Panda/View/Widget/Template.hs b/Panda/View/Widget/Template.hs
deleted file mode 100644
--- a/Panda/View/Widget/Template.hs
+++ /dev/null
@@ -1,33 +0,0 @@
-{-# OPTIONS -fno-monomorphism-restriction #-}
-
-module Panda.View.Widget.Template (page) where
-
-import Panda.View.Widget.Head
-import Panda.View.Widget.Body
-import Panda.View.Widget.Header
-import Panda.View.Widget.Navigation
-import Panda.View.Widget.Sidebar
-import Panda.View.Widget.Footer
-import qualified Text.XHtml.Strict as Html
-import qualified Panda.Config.Global as G
-import qualified Panda.Type.Theme as T
-
-import Panda.Helper.Env hiding (header, body)
-import Prelude hiding ((.), (/), (^), id, span)
-
--- extension
-import Panda.Extension.Analytics.T
-
-body t state x = Html.body << 
-  [ div_class (t.T.container) << 
-    [ header (t.T.header) state
-    , navigation (t.T.navigation) state
-    , div_id "page" << [ div_class (t.T.main) << body_content x, sidebar (t.T.sidebar) state ]
-    , footer (t.T.footer)
-    ]
-  , primHtml analytics_snippet
-  ]
-
-template t state x = [html_head state, body t state x]
-
-page = template G.theme
diff --git a/changelog.markdown b/changelog.markdown
--- a/changelog.markdown
+++ b/changelog.markdown
@@ -1,3 +1,15 @@
+2008.10.25
+-----------
+
+### Feature
+
+* Modular system: search, comment, analytics are now extensions that can be disabled / enabled
+
+### Fix
+
+* backward compatible with old comment format, no migration required as in 2008.10.24
+* works with kibro 4.1
+
 2008.10.24
 ----------
 
diff --git a/panda.cabal b/panda.cabal
--- a/panda.cabal
+++ b/panda.cabal
@@ -1,5 +1,5 @@
 Name:                 panda
-Version:              2008.10.24
+Version:              2008.10.25
 Build-type:           Simple
 Synopsis:             A simple static blog engine
 Description:          A simple static blog engine
@@ -10,11 +10,12 @@
 Build-Depends:        base
 Cabal-version:        >= 1.2
 category:             Web
-homepage:             http://jinjing.blog.easymic.com/static/panda/readme
+homepage:             http://www.haskell.org/haskellwiki/Panda
 data-files:			  readme.markdown, changelog.markdown
 
 library
-  build-depends: base, cgi, network, haskell98, old-locale, old-time, directory, filepath, containers, mps >= 2008.10.15, parsedate >= 3000.0.0, rss >= 3000.0.1, xhtml, kibro == 0.3, utf8-string >= 0.3.1, pandoc >= 0.46, MissingH, parsec >= 2, gravatar >= 0.3
+  build-depends: base, cgi, network, haskell98, old-locale, old-time, directory, filepath, containers, mps >= 2008.10.15, parsedate >= 3000.0.0, rss >= 3000.0.1, xhtml, kibro == 0.4.1, utf8-string >= 0.3.1, pandoc >= 0.46, MissingH, parsec >= 2, gravatar >= 0.3
+  hs-source-dirs: src/
   exposed-modules:  Panda
                     Panda.Config.Global
 
@@ -60,5 +61,5 @@
                     Panda.View.Widget.Sidebar
                     Panda.View.Widget.Template
                     
- 
+                    Main
                     
diff --git a/readme.markdown b/readme.markdown
--- a/readme.markdown
+++ b/readme.markdown
@@ -1,3 +1,4 @@
 ## Panda: a simple blog engine in Haskell
 
-[Panda homepage](http://www.haskell.org/haskellwiki/Panda)
+* [Panda homepage](http://www.haskell.org/haskellwiki/Panda)
+* [doc](doc/readme)
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,5 @@
+module Main where
+
+import Panda
+
+main = panda
diff --git a/src/Panda.hs b/src/Panda.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda.hs
@@ -0,0 +1,10 @@
+module Panda
+  (
+    module Panda.Controller.Application,
+    panda
+  ) where
+
+import Panda.Controller.Application
+import Kibro
+
+panda = startKibro pages
diff --git a/src/Panda/Config/Global.hs b/src/Panda/Config/Global.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Config/Global.hs
@@ -0,0 +1,97 @@
+module Panda.Config.Global where
+
+import Panda.Helper.Helper
+import MPS
+import Prelude hiding ((.), (/), (^), id, readFile)
+
+import System.FilePath hiding ((<.>))
+import Control.Arrow ((>>>), (&&&), (***))
+import Data.Maybe
+import System.Directory
+
+import Panda.Type.Reader
+import Panda.Type.Sidebar
+import Panda.Type.Theme
+import Panda.Type.Extension
+
+db_id       = "db"
+flat_id     = "."
+post_id     = "blog"
+config_id   = "config"
+tag_id      = "tag"
+comment_id  = "comment"
+sidebar_id  = "sidebar"
+theme_id    = "theme"
+
+db_uri      = db_id
+flat_uri    = db_uri / flat_id
+
+config_uri  = flat_uri / config_id / "site.txt"
+sidebar_uri = flat_uri / config_id / sidebar_id
+post_uri    = flat_uri / post_id
+tag_uri     = flat_uri / tag_id
+comment_uri = flat_uri / comment_id
+theme_uri   = flat_uri / config_id / theme_id
+
+
+-- unsafe, must restart server after changing config file, sorry about that ...
+-- but these configs are read only, and keep the view pure, so no monad headaches involved.
+
+user_config = parse_config config_uri
+
+config_for' s d = user_config.lookup s .fromMaybe d
+config_for s    = config_for' s s
+
+blog_title      = config_for "blog_title"
+blog_subtitle   = config_for "blog_subtitle"
+host_name       = config_for "host_name"
+author_email    = config_for "author_email" 
+
+per_page        = config_for' "per_page" "7" .read :: Int
+navigation      = config_for' "navigation" "About" .parse_list.("Home" :)
+
+panda_url       = "http://github.com/nfjinjing/panda"
+root            
+  | user_root.belongs_to ["/", ""]  = "/"
+  | otherwise                       =  user_root.("/" /).remove_trailing_slash
+  where user_root = config_for' "root" "/"
+
+default_reader  = Markdown
+
+sidebar = config_for "sidebar" .parse_list .map (sidebar_uri /).select (to_utf8 >>> doesFileExist >>> purify )
+  .map (read_sidebar_item default_reader >>> purify)
+  
+favicon = config_for' "favicon" "/favicon.ico"
+
+
+-- extensions
+analytics_account_id = config_for "analytics_account_id"
+extensions = config_for' "extensions" "search, comment, analytics" .parse_list .to_extensions
+
+-- theme
+default_theme = blank_theme
+user_theme_name = config_for "theme"
+user_theme_uri = (theme_uri / user_theme_name) ++ ".txt"
+
+theme = if user_theme_uri.doesFileExist.purify
+  then parse_config user_theme_uri .(("name", user_theme_name) : ) .to_theme
+  else default_theme
+  
+-- custom
+as_l s                   = "[" ++ s ++ "]"
+post_date_format         = config_for' "post_date_format" "%y-%m-%d"
+comment_date_format      = config_for' "comment_date_format" "%y-%m-%d %T"
+url_date_format          = config_for' "url_date_format" "%y-%m-%d"
+url_date_matcher         = config_for' "url_date_matcher" "\\d{2}-\\d{2}-\\d{2}"
+url_title_subs           = config_for' "url_title_subs" "" .as_l .read :: [(String, String)]
+url_date_title_seperator = config_for' "url_date_title_seperator" " "
+
+-- summary
+cut              = config_for' "cut" "✂-----" .to_utf8
+parse_boolean    = belongs_to ["true", "1", "y", "yes", "yeah"]
+summary_for_root = config_for "summary_for_root" .parse_boolean
+summary_for_tag  = config_for "summary_for_tag"  .parse_boolean
+summary_for_rss  = config_for "summary_for_rss"  .parse_boolean
+
+
+
diff --git a/src/Panda/Controller/Application.hs b/src/Panda/Controller/Application.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Controller/Application.hs
@@ -0,0 +1,174 @@
+-- the controller is the most complex module for a reason:
+-- models are made as simple and small as possible
+-- views are pure functions.
+
+-- a note on encoding:
+-- IOs are in utf-8 filter, internally use bytes
+
+{-# OPTIONS -fno-monomorphism-restriction #-}
+
+module Panda.Controller.Application where
+
+import qualified Data.Map as Map
+
+-- env
+import Panda.Helper.Env hiding (tag, uri)
+import Panda.Helper.StateHelper
+import Prelude hiding ((.), (/), (^), id)
+import qualified Panda.Type.State as S
+import qualified Panda.Config.Global as G
+import qualified Panda.Type.Pager as Pager
+import Panda.Type.Extension
+
+-- model
+import qualified Panda.Model.Post as Post
+import qualified Panda.Model.Static as Static
+import qualified Panda.Model.Tag as Tag
+import qualified Panda.Model.Comment as Comment
+
+-- view
+import qualified Panda.View.Control.Post as PostV
+import qualified Panda.View.Widget.Template as T
+import qualified Panda.View.Control.Static as StaticV
+import qualified Panda.View.Control.Tag as TagV
+import qualified Panda.View.Control.Search as SearchV
+import qualified Panda.View.Widget.RSS as RSSV
+
+-- helpers
+-- liftIO: move to an (u)pper level monad
+u = liftIO
+blog_regex = G.url_date_matcher
+
+init_post tags x = do
+  x.Tag.fill_tag tags.Comment.fill_comment_size.u
+
+tag_list = Tag.list.u
+
+
+-- main controller
+
+pages = 
+  [ ("$"                  ,index              )
+  , ("(\\?.+)?$"          ,index              )
+  , ("rss.xml$"           ,index_feed         )
+  , (blog_regex           ,blog               )
+  , ("static/."           ,static             )
+  , ("tag/.*/rss.xml$"    ,tag_feed           )
+  , ("tag/."              ,tag                )
+  , ("search"             ,route_for_extension Search   $ search             )
+  , ("comment/create"     ,route_for_extension Comment  $ comment_create     )
+  ] .map_fst ((G.root /) >>> ("^" ++))
+  ++
+  [ ("^" ++ G.root ++ "$" ,index              )
+  , ("^" ++ G.root ++ "?" ,index              )
+  ]
+  
+
+route_for_extension ext x = if has_extension ext then x else not_found
+
+index = do
+  posts <- Post.list.u
+  p <- paginate posts
+  tags <- tag_list
+  let nav = if p.Pager.current == 1 then home_nav else no_navigation
+  let state = S.empty { S.uid = G.post_id, S.pager = p, S.tags = tags, S.nav_location = nav }
+  
+  posts.mapM (init_post tags) ^ PostV.list state >>= output_html
+
+index_feed = do
+  posts <- Post.list.u
+  posts.RSSV.rss "" "" .output
+
+blog = do
+  id <- uri ^ Post.uri_to_id
+  blog <- Post.get id .u
+  comments <- Comment.list_for id .u
+  tags <- tag_list
+  
+  test_data <- S.mk_human_test .u
+  let state = S.empty { S.uid = id, S.tags = tags, S.resource_title = blog.resource_title, S.human_test_data = test_data }
+  blog.(init_post tags) ^ PostV.view state comments >>= output_html
+
+
+static = do
+  id <- uri
+  static_page <- Static.get id .u
+  tags <- tag_list
+  
+  let nav_id = static_page.Static.uid.id_to_resource
+  let nav   = if nav_id.belongs_to G.navigation then nav_id else no_navigation
+  let state = S.empty { S.uid = id, S.tags = tags, S.nav_location = nav, S.resource_title = static_page.resource_title }
+  StaticV.view state static_page .output_html
+
+tag = do
+  id <- uri
+  tags <- tag_list
+  let tag_name = Tag.get_name id
+  case Tag.tag_map' tags .Map.lookup tag_name of
+    Nothing -> not_found
+    Just post_set -> do
+      posts <- post_set.to_list.rsort.mapM (Post.get) .u >>= mapM (init_post tags)
+      p <- paginate posts
+      let state = S.empty { S.uid = id, S.pager = p, S.tags = tags, S.resource_title = tag_name.Tag.resource_title_from_name }
+      posts.TagV.view state.output_html
+
+tag_feed = do
+  id <- uri ^ (split "/" >>> init >>> join "/")
+  tags <- tag_list
+  let tag_name = Tag.get_name id
+  case Tag.tag_map' tags .Map.lookup tag_name of
+    Nothing -> not_found
+    Just post_set -> do
+      posts <- post_set.to_list.rsort.mapM (Post.get) .u ^ map (Tag.fill_tag tags)
+      posts.RSSV.rss G.tag_id tag_name.output
+
+search = do
+  s <- param_with_default "s" ""
+  posts <- Post.search s .u
+
+  p <- paginate posts
+  tags <- tag_list
+  query <- uri
+  let state = S.empty { S.uid = query, S.pager = p, S.tags = tags, S.resource_title = query }
+
+  posts.mapM (init_post tags) >>= \x -> x.SearchV.view state s.output_html
+
+comment_create = do
+  post_id <- input_with_default (show_data Comment.PostId) (G.post_id / "nothing")
+  exists <- (G.flat_uri / post_id.to_utf8) .doesFileExist .u
+  let valid_path = equalFilePath G.post_id (takeDirectory post_id)
+  checked <- check_create
+  
+  if [checked, valid_path, exists].and
+    then
+      inputs >>= (Comment.create >>> u)
+    else
+      return ()
+  redirect $ (post_id.Post.id_to_uri.to_utf8).urlEncode
+
+get_input_data = show_data >>> get_input
+
+check_human = do
+  x <- [Comment.LeftNumber, Comment.RightNumber, Comment.Operator, Comment.HumanHack].mapM get_input_data ^ map fromJust
+  let [l, r, op, h] = x
+  return $ S.simple_eval (l.read) (r.read) (op.S.read_op) .is (h.read)
+
+-- create helper, should be refactored to some common aspect
+check_create =
+  [ validate Comment.Author     ( length >>> (> 0))
+  , validate Comment.AuthorLink ( const True)
+  , validate Comment.Body       ( length >>> (> 0))
+  , validate Comment.EmptyField ( empty )
+  , validate Comment.LeftNumber ( belongs_to (S.nums.map show))
+  , validate Comment.RightNumber ( belongs_to (S.nums.map show))
+  , validate Comment.Operator ( belongs_to (S.ops.map S.display_op))
+  , validate Comment.HumanHack ( belongs_to (S.nums.map show))
+  , check_human
+  ] .sequence ^ and
+
+
+validate s f = do
+  maybe_s <- get_input_data s
+  case maybe_s of
+    Nothing -> return False
+    Just v -> return $ f v
diff --git a/src/Panda/Extension/Analytics/T.hs b/src/Panda/Extension/Analytics/T.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Extension/Analytics/T.hs
@@ -0,0 +1,19 @@
+module Panda.Extension.Analytics.T where
+
+import Panda.Helper.Env
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Config.Global as G
+
+analytics_snippet = 
+  [ "<script type=\"text/javascript\">                                                                                                  "
+  , "var gaJsHost = ((\"https:\" == document.location.protocol) ? \"https://ssl.\" : \"http://www.\");                                  "
+  , "document.write(unescape(\"%3Cscript src='\" + gaJsHost + \"google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\")); "
+  , "</script>                                                                                                                          "
+  , "<script type=\"text/javascript\">                                                                                                  "
+  , "var pageTracker = _gat._getTracker(\"UA-xxxxxx-x\");                                                                               "
+  , "pageTracker._trackPageview();                                                                                                      "
+  , "</script>                                                                                                                          "
+  ]
+  .map strip
+  .join "\n"
+  .gsub "UA-xxxxxx-x" G.analytics_account_id
diff --git a/src/Panda/Helper/Env.hs b/src/Panda/Helper/Env.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Helper/Env.hs
@@ -0,0 +1,38 @@
+-- this sets some scoping for every module, which prevents importing
+-- common modules in every file
+
+module Panda.Helper.Env  (
+    module MPS                 
+  , module Control.Monad       
+  , module Control.Arrow       
+  , module Data.List           
+  , module Network.URI         
+  , module Network.CGI          
+  , module System.Locale       
+  , module System.Time.Parse
+  , module Panda.Helper.Helper
+  , module Data.Maybe
+  , module System.FilePath
+  , module System.IO.UTF8
+  , module Text.XHtml.Strict
+  , module System.Time
+  , module System.Directory
+  , module Data.Foldable
+) where
+  
+import MPS hiding (base, col, sub, date)
+import Control.Monad hiding (join)
+import Control.Arrow ((>>>), (&&&), (***))
+import Data.List hiding (find)
+import Network.URI
+import Network.CGI hiding (Html)
+import System.Locale
+import System.Time.Parse
+import Panda.Helper.Helper
+import Data.Maybe
+import System.FilePath hiding ((<.>))
+import System.IO.UTF8 (readFile, writeFile)
+import Text.XHtml.Strict hiding (select)
+import System.Time
+import System.Directory
+import Data.Foldable (find)
diff --git a/src/Panda/Helper/Helper.hs b/src/Panda/Helper/Helper.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Helper/Helper.hs
@@ -0,0 +1,138 @@
+-- helper module is a central place for reusable functions
+-- for this project, more general helpers are usually moved
+-- to MPS package, for multi-project usage
+
+{-# OPTIONS -fno-monomorphism-restriction #-}
+
+module Panda.Helper.Helper where
+
+import System.IO.UTF8 (readFile, writeFile)
+import Network.URI
+import Network.CGI
+import System.FilePath.Posix ((</>))
+import Control.Arrow ((>>>), (&&&))
+import Text.XHtml.Strict
+import Control.Monad hiding (join)
+import Data.Maybe
+import Panda.Type.Pager as Pager hiding (empty)
+import MPS hiding (date)
+import Prelude hiding ((.), (/), (^), id, readFile, writeFile)
+import Char
+import Data.List
+import qualified Data.List as L 
+import Panda.Type.Reader
+import System.FilePath.Posix hiding ((<.>))
+import System.Time
+
+
+(/) = (</>)
+infixl 5 /
+
+(^) = (<.>)
+infixl 9 ^
+
+-- global
+parse_config_io s = readFile s ^ (\x -> x.filter_comment.lines.map strip .map (split "\\s*=\\s*") .map fill_snd_blank .map tuple2)
+  where fill_snd_blank [x] = [x,""]
+        fill_snd_blank xs = xs
+parse_config = parse_config_io >>> purify
+
+write_config_io s xs = xs.map(\(x, y) -> x ++ " = " ++ y) .join "\n" .writeFile s
+
+-- model
+take_extension = takeExtension >>> split "\\." >>> last
+
+-- controller
+raw_uri = requestURI ^ (uriPath >>> urlDecode >>> tail >>> remove_trailing_slash >>> from_utf8 )
+remove_trailing_slash s = if s.last.is '/' then s.init else s
+  
+params = do
+  query_string <- requestURI ^ uriQuery 
+  case query_string of
+    '?':s -> s.formDecode .map_snd unescape_unicode_xml.return
+    otherwise -> return []
+
+inputs = getInputs ^ map_snd (strip >>> unescape_unicode_xml)
+
+param_with_default s d = params ^ (lookup s >>> fromMaybe d )
+input_with_default s d = inputs ^ (lookup s >>> fromMaybe d )
+
+get_param s = params ^ lookup s
+get_input s = inputs ^ lookup s
+
+just_param s = get_param s ^ fromJust
+just_input s = get_input s ^ fromJust
+
+full_paginate length total = liftM5 ( Pager length ) current has_next has_previous next previous where
+  current      = param_with_default "page" "1" ^ read
+  has_next     = current ^ ( (* length) >>> (< total.fromIntegral) )
+  has_previous = current ^ (> 1)
+  next         = current ^ (+ 1)
+  previous     = current ^ (+ (-1) )
+
+
+for_current_page p xs = xs.drop ((p.Pager.current - 1) * p.Pager.length) .take (p.Pager.length)
+
+-- id: /type/resoruce
+id_to_type id     = id.split "/" .first
+id_to_resource id = id.split "/" .tail.join "/"
+no_navigation     = ""
+home_nav          = "Home"
+
+
+-- view
+id               = identifier
+
+css_link l       = itag "link" ! [rel "stylesheet", thetype "text/css", href l]
+js_link l        = itag "script" ! [thetype "text/javascript", src l]
+js_src s         = tag "script" ! [thetype "text/javascript"] << s
+rss_link l       = itag "link" ! [rel "alternate", thetype "application/rss+xml", href l, title "RSS 2.0"]
+favicon_link l   = itag "link" ! [rel "icon", thetype "image/png", href l]
+meta_tag         = meta ! [httpequiv "Content-Type", content "text/html; charset=utf-8"]
+
+div_id s         = thediv ! [id s]
+div_class s      = thediv ! [theclass s]
+div_class_id x y = thediv ! [theclass x, id y]
+
+output_html      = output ... renderHtml
+
+spaced_url = gsub "/" " / "
+
+empty_html = toHtml ""
+
+-- config
+parse_list s = s.split "," .map strip .reject null
+not_found = getVar "REQUEST_URI" >>= (fromMaybe "" >>> outputNotFound)
+
+
+-- generic
+
+starts_with _ [] = False
+starts_with (x:xs) (y:ys) | x == y = starts_with xs ys
+                          | otherwise = False
+
+ends_with x y = starts_with (x.reverse) (y.reverse)
+
+capitalize (x:xs) = [x].upper ++ xs.lower
+camel_case = split "_" >>> map capitalize >>> join'
+snake_case = gsub "\\B[A-Z]" "_\\&" >>> lower
+show_data = show >>> snake_case
+ 
+
+-- class
+class DataRenderer a where
+    render_data :: a -> Html
+
+class Resource a where
+    resource_title :: a -> String
+
+class Markable a where
+    markup :: a -> Html
+    
+class Datable a where
+    date :: a -> CalendarTime
+
+class Addressable a where
+    uri :: a -> String
+
+ 
diff --git a/src/Panda/Helper/StateHelper.hs b/src/Panda/Helper/StateHelper.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Helper/StateHelper.hs
@@ -0,0 +1,46 @@
+-- trying to merge this helper with the generic one results in cyclic imports
+
+{-# OPTIONS -fno-monomorphism-restriction #-}
+
+module Panda.Helper.StateHelper where
+
+import Panda.Helper.Env
+import qualified Panda.Config.Global as G
+import Prelude hiding ((.), (/), (^), id)
+
+
+-- G.root = /blog
+-- raw_uri = blog/x
+-- full_uri = /blog/x
+-- uri = full_uri - (/blog/)
+remove_root s
+  | G.root.is "/" = s
+  | otherwise     = s.slice (G.root.length) (s.length)
+
+uri = raw_uri ^ remove_root
+-- uri = raw_uri
+
+
+-- global
+parse_date format s = case maybe_d of
+  Nothing -> Nothing
+  Just d -> Just $ if d.ctYear < 1910 then d {ctYear = d.ctYear + 100} else d 
+  where
+    maybe_d = parseCalendarTime defaultTimeLocale format s
+  
+format_date          = formatCalendarTime defaultTimeLocale
+default_date         = parse_date "%Y-%m-%d %T" "2000-1-1 00:00:00".fromJust
+parse_post_date      = parse_date G.post_date_format
+default_parse_date s = s.parse_post_date .fromMaybe default_date
+
+has_extension x = G.extensions.has x
+for_extension ext x = if has_extension ext then x else toHtml ""
+
+-- controller
+paginate = length >>> full_paginate (G.per_page)
+
+cut       = G.cut
+cut_re    = "^\\s*" ++ cut
+match_cut = to_utf8 >>> match cut_re
+is_cut    = match_cut >>> isJust
+split_cut = to_utf8 >>> split cut_re
diff --git a/src/Panda/Model/Comment.hs b/src/Panda/Model/Comment.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Model/Comment.hs
@@ -0,0 +1,109 @@
+module Panda.Model.Comment where
+
+-- env
+import Panda.Helper.Env hiding (title, body, size, path, meta)
+import Prelude hiding ((.), (/), (^), id, readFile, writeFile)
+import qualified Panda.Config.Global as G
+import Panda.Type.Reader
+import qualified Panda.Model.Post as Post
+import System.Directory
+import Panda.Helper.StateHelper
+import Network.Gravatar
+
+data Comment = Comment
+  { uid    :: String     -- comment/08-09-04 blog title
+  , author :: String
+  , body   :: String
+  , author_email :: String
+  , author_link    :: String
+  }
+  deriving (Show, Eq)
+
+data CommentData = 
+    Author
+  | AuthorEmail
+  | AuthorLink
+  | Body
+  | PostId
+  deriving (Show)
+
+data SpamFilter = 
+    HumanHack 
+  | EmptyField
+  | LeftNumber
+  | RightNumber
+  | Operator
+  deriving (Show)
+
+instance Resource Comment where
+  resource_title = uid >>> spaced_url
+  
+instance Markable Comment where
+  markup x  = render_to_html Markdown (x.body)
+
+instance Datable Comment where
+  date x  = x.uid.split "/".last.default_parse_date
+
+
+-- CRUD
+list_for post_id = do
+  has_comments <- doesDirectoryExist d
+  if has_comments
+    then ls d ^ reject (match "\\.meta$" >>> isJust) ^ rsort ^ map (G.comment_id / r /) >>= mapM (from_utf8 >>> get)
+    else return []
+  where
+    d = (G.comment_uri / r)
+    r = post_id.id_to_resource.to_utf8
+
+path id = G.flat_uri / id.to_utf8
+meta = (++ ".meta")
+
+get id = do
+  meta_exists <- id.path.meta.doesFileExist
+  if meta_exists
+    then get_from_new_format id
+    else get_from_old_format id
+
+get_from_new_format id = do
+  body <- id.path.readFile
+  meta <- id.path.meta.parse_config_io
+  let at s         = meta.lookup (s.show_data) .fromJust
+  let author       = at Author
+  let author_email = at AuthorEmail
+  let author_link  = at AuthorLink
+  return $ Comment id author body author_email author_link
+
+get_from_old_format id = do
+  xs <- id.path.readFile ^ lines
+  let author = xs.first
+  let author_email = ""
+  let author_link = xs.drop 2.first
+  let body = xs.drop 4.unlines
+  return $ Comment id author body author_email author_link
+
+create h = do
+  let at s = h.lookup (s.show_data) .fromJust
+  let body        = at Body
+  let post_id     = at PostId
+
+  timestamp <- ( getClockTime >>= toCalendarTime ) ^ format_date G.comment_date_format
+  let comment_path = post_id_to_uid post_id .to_utf8
+  createDirectoryIfMissing True comment_path
+  
+  let uid = comment_path / timestamp
+  writeFile uid body
+  let meta = [Author, AuthorLink, AuthorEmail] .labeling at .map_fst show_data
+  write_config_io (uid ++ ".meta") meta
+
+-- extra
+from_post_id x = Comment (post_id_to_uid x) "" "" "" ""
+
+post_id_to_uid x = G.flat_uri / G.comment_id / x.split "/" .last
+uid_to_post_id x = G.post_id / x.split "/" .last
+
+fill_comment_size x = do
+  size <- x.Post.uid.list_for ^ length 
+  return $ x { Post.comment_size = size }
+
+gravatar_default_size = size 40
+gravatar_link x       = gravatarWith (x.author_email) Nothing gravatar_default_size Nothing
diff --git a/src/Panda/Model/Post.hs b/src/Panda/Model/Post.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Model/Post.hs
@@ -0,0 +1,78 @@
+-- what about the performance?
+-- Haskell takes care of that, since IOs are also lazy.
+-- Posts are not read unless specifically required, i.e. after pagination
+
+module Panda.Model.Post where
+
+-- env
+import Panda.Helper.Env hiding (match, title, body, date)
+import qualified MPS as MPS
+import Prelude hiding ((.), (/), (^), id, readFile)
+import qualified Panda.Config.Global as G
+import Panda.Type.Reader
+import Panda.Helper.StateHelper hiding (uri)
+import Panda.Helper.Helper (date)
+
+data Post = Post 
+  { uid :: String     -- blog/08-09-04 blog title
+  , title :: String
+  , body :: String
+  , tags :: [String]
+  , comment_size :: Int
+  , reader :: Reader
+  }
+  deriving (Show, Eq)
+
+instance Resource Post where
+  resource_title = title
+
+instance Markable Post where
+  markup x  = render_to_html (x.reader) (x.full)
+  
+instance Datable Post where
+  date = uid >>> get_date
+
+instance Addressable Post where
+  uri = uid >>> id_to_uri
+
+-- CRUD
+list = ls G.post_uri ^ map from_utf8 ^ rsort ^ map (G.post_id /) >>= mapM get
+
+get id        = liftM4 (Post id (get_title id) ) (get_body id) (return []) (return 0) (return $ get_reader id)
+get_extension = takeExtension
+get_title id  = id.words.tail.unwords.dropExtension
+get_body id   = (G.flat_uri / id.to_utf8) .readFile
+get_reader id = id.take_extension.guess_reader.fromMaybe G.default_reader
+get_date id   = id.words.first.split "/".last.default_parse_date
+
+match s x = [title, body] .map (send_to x >>> lower >>> isInfixOf (s.lower)) .or
+search "" = return []
+search s  = list ^ filter (match s)
+
+summary = body >>> split_cut >>> first >>> from_utf8
+full x | x.body.match_cut.isNothing = x.body
+full x = ( xs.takeWhile not_cut ++ xs.dropWhile not_cut .tail ).unlines where
+  not_cut = is_cut >>> not
+  xs = x.body.lines
+
+has_continue = body >>> match_cut >>> isJust
+
+
+-- extra
+id_to_uri id = G.root / ( pretty_date ++ G.url_date_title_seperator ++ formatted_title ++ ext ) where
+  formatted_title = G.url_title_subs.map (\(a,b) -> gsub a b).inject (id.get_title) apply
+  pretty_date     = id.get_date.format_date G.url_date_format
+  ext = id.get_extension
+
+uri_to_id s = G.post_id / (d ++ " " ++ t) where
+  (raw_d, (prefix, title_with_sep)) = s.MPS.match G.url_date_matcher.fromJust.fst
+  raw_t = title_with_sep.drop (G.url_date_title_seperator.length)
+  t = G.url_title_subs.map (\(a,b) -> gsub b a) .inject raw_t apply
+  d = raw_d.parse_date G.url_date_format .fromJust.format_date G.post_date_format
+
+
+-- summary
+markup_summary x = post_summary x +++ next where
+  post_summary = (reader &&& summary) >>> splash render_to_html
+  next = if x.has_continue then toHtml $ hotlink (x.uri) << "Read the rest of the post »" else empty_html
+  
diff --git a/src/Panda/Model/Static.hs b/src/Panda/Model/Static.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Model/Static.hs
@@ -0,0 +1,28 @@
+module Panda.Model.Static where
+
+-- env
+import Panda.Helper.Env hiding (match, body)
+import Prelude hiding ((.), (/), (^), id, readFile)
+import qualified Panda.Config.Global as G
+import Panda.Type.Reader
+
+data Static = Static 
+  { uid :: String
+  , body :: String
+  , reader :: Reader
+  }
+  deriving (Show, Eq)
+
+instance Resource Static where
+  resource_title = uid >>> spaced_url
+
+instance Markable Static where
+  markup x = render_to_html (x.reader) (x.body)
+  
+-- CRUD
+get id        = liftM2 (Static id) (get_body id) (return $ get_reader id)
+get_title     = id_to_resource >>> dropExtension
+get_body id   = (G.flat_uri / id.to_utf8) .readFile
+get_reader id = id.take_extension.guess_reader.fromMaybe G.default_reader
+
+title = uid >>> get_title
diff --git a/src/Panda/Model/Tag.hs b/src/Panda/Model/Tag.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Model/Tag.hs
@@ -0,0 +1,39 @@
+module Panda.Model.Tag where
+
+import qualified Panda.Model.Post as Post
+
+-- env
+import Panda.Helper.Env hiding (name)
+import Prelude hiding ((.), (/), (^), id, readFile)
+import qualified Data.Set as S
+import qualified Panda.Config.Global as G
+
+
+data Tag = Tag 
+  { uid :: String     -- tag/name
+  , name :: String
+  , resources :: S.Set String
+  }
+  deriving (Show, Eq)
+
+instance Resource Tag where
+  resource_title = uid >>> spaced_url
+
+-- CRUD
+list             = ls G.tag_uri ^ map from_utf8 ^ map (G.tag_id /) >>= mapM get
+
+get id           = get_resources id ^ Tag id (get_name id)
+get_name id      = id.split "/" .tail.join'
+get_resources id = (G.flat_uri / id.to_utf8) .readFile
+  ^ filter_comment ^ lines ^ map (G.post_id / ) ^ to_set
+
+resource_title_from_name = name_to_id >>> spaced_url
+tag_map' xs       = xs . map (name &&& resources) . to_h
+tag_map           = list ^ tag_map'
+
+for_resource xs x = xs.select (resources >>> has x) .map name
+fill_tag xs x     = x { Post.tags = for_resource xs (x.Post.uid) }
+
+-- extra
+sorted xs    = xs.sortBy(compare_by (resources >>> S.size)).reverse
+name_to_id x = G.tag_id / x
diff --git a/src/Panda/Type/Pager.hs b/src/Panda/Type/Pager.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Type/Pager.hs
@@ -0,0 +1,13 @@
+module Panda.Type.Pager where
+  
+data Pager = Pager {
+  length :: Int,
+  current :: Int,
+  has_next :: Bool,
+  has_previous :: Bool,
+  next :: Int,
+  previous :: Int
+  }
+  deriving (Eq, Show)
+  
+empty = Pager 0 0 False False 0 0
diff --git a/src/Panda/Type/Reader.hs b/src/Panda/Type/Reader.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Type/Reader.hs
@@ -0,0 +1,37 @@
+{-# OPTIONS -XDeriveDataTypeable #-}
+
+module Panda.Type.Reader where
+
+import MPS  
+import Prelude hiding ((.), (/), (^), id)
+import qualified Data.Map as Map
+import Text.Pandoc
+import Control.Arrow ((>>>))
+import Text.XHtml.Strict
+import Data.Generics
+
+data Reader = Markdown | RST | HTML | Latex deriving (Show, Eq, Typeable, Data)
+
+readers = 
+  [ (Markdown,  ["markdown", "md"])
+  , (RST,       ["rst"]  )
+  , (HTML,      ["html", "htm"])
+  , (Latex,     ["tex", "latex"])
+  ]
+
+gen_lookup (r, xs) = xs.labeling (const r)
+reader_map         = readers.map gen_lookup.join'.to_h
+guess_reader ext   = reader_map.Map.lookup ext
+
+
+to_html r      = r defaultParserState >>> writeHtml defaultWriterOptions
+
+-- this list can go on, as long as there is a library that does
+-- the convertion. pretty extensible, isn't it.
+rr Markdown    = to_html readMarkdown
+rr RST         = to_html readRST
+rr HTML        = primHtml
+rr Latex       = to_html readLaTeX
+
+render_to_html = rr
+
diff --git a/src/Panda/Type/Sidebar.hs b/src/Panda/Type/Sidebar.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Type/Sidebar.hs
@@ -0,0 +1,20 @@
+module Panda.Type.Sidebar where
+
+import Panda.Helper.Env hiding (body)
+import Prelude hiding ((.), (/), (^), id, readFile)
+import Panda.Type.Reader
+
+data SidebarItem = SidebarItem
+  { name :: String
+  , body :: String
+  , reader :: Reader
+  }
+  deriving (Show, Eq)
+
+instance Markable SidebarItem where
+  markup x  = render_to_html (x.reader) (x.body)
+
+read_sidebar_item default_reader s = liftM2 (SidebarItem name) body (return reader) where
+  body = s.to_utf8.readFile
+  reader = s.take_extension.guess_reader.fromMaybe default_reader
+  name = s.takeFileName.dropExtension
diff --git a/src/Panda/Type/State.hs b/src/Panda/Type/State.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Type/State.hs
@@ -0,0 +1,65 @@
+module Panda.Type.State where
+
+import qualified Panda.Type.Pager as Pager
+import Panda.Model.Tag
+import Random (randomRs, mkStdGen)
+import System.Time
+import Control.Arrow ((>>>), (&&&), (***))
+import MPS
+import Prelude hiding ((.), (/), (^), id, readFile, writeFile)
+
+data State = State
+  -- model state
+  { uid     :: String       -- current view resource
+  , pager   :: Pager.Pager        -- pager
+
+  -- theme state
+  , tags    :: [Tag]
+  , nav_location :: String
+  , resource_title :: String
+  , human_test_data :: HumanTestData
+  }
+  deriving (Show)
+
+show_left   = human_test_data >>> left >>> show
+show_right  = human_test_data >>> right >>> show
+show_op     = human_test_data >>> op >>> display_op where
+
+read_op "+" = Plus
+read_op "-" = Minus
+read_op x   = error ("can not read operator: " ++ x)
+
+display_op Plus  = "+"
+display_op Minus = "-"
+
+data HumanTestData = HumanTestData 
+  { left :: Int
+  , right :: Int
+  , op :: Op
+  } deriving (Show)
+
+data Op = Plus | Minus deriving (Show)
+
+empty            = State "" Pager.empty [] "" "" empty_human_test
+empty_human_test = HumanTestData 0 0 Plus
+
+
+(^) = (<.>)
+infixl 9 ^
+
+ops = [Plus, Minus]
+nums = [0, 5, 10, 15, 20]
+simple_eval a b Plus = a + b
+simple_eval a b Minus = a - b
+
+
+mk_human_test = do
+  seed <- (getClockTime >>= toCalendarTime) ^ ctPicosec ^ fromIntegral
+  let (a,b,c) = randomRs (0,100) (mkStdGen seed) .in_group_of 3 .map make_sample .dropWhile (good_test >>> not) .first
+  return $ HumanTestData a b c
+
+  where
+    make_sample [a,b,c] = ((get_num a), (get_num b), (get_op c))
+    good_test = splash3 simple_eval >>> belongs_to nums
+    get_num n = nums.at (n `mod` (nums.length))
+    get_op  n = ops.at (n `mod` (ops.length))
diff --git a/src/Panda/Type/Theme.hs b/src/Panda/Type/Theme.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/Type/Theme.hs
@@ -0,0 +1,44 @@
+module Panda.Type.Theme where
+
+import Prelude hiding ((.))
+import Panda.Helper.Env hiding (name, header)
+
+data Theme = Theme
+  { name        :: String
+  , container   :: String
+  , header      :: String
+  , navigation  :: String
+  , main        :: String
+  , sidebar     :: String
+  , footer      :: String
+  , css         :: [String]
+  , js          :: [String]
+  } deriving (Show, Read)
+  
+to_theme xs = Theme 
+  { name        = at "name"
+  , container   = at "container"
+  , header      = at "header"
+  , navigation  = at "navigation"
+  , main        = at "main"
+  , sidebar     = at "sidebar"
+  , footer      = at "footer"
+  , css         = at "css" .css_list
+  , js          = at "js"  .js_list
+  }
+  where
+    at s = xs.lookup s .fromJust
+    css_list s = s.parse_list.map (\x -> "/theme/" ++ at "name" ++ "/css/" ++ x ++ ".css")
+    js_list s  = s.parse_list.map (\x -> "/theme/" ++ at "name" ++ "/js/" ++ x ++ ".js")
+    
+blank_theme = Theme
+  { name        = ""
+  , container   = ""
+  , header      = ""
+  , navigation  = ""
+  , main        = ""
+  , sidebar     = ""
+  , footer      = ""
+  , css         = [""]
+  , js          = [""]
+  }
diff --git a/src/Panda/View/Atom/Comment.hs b/src/Panda/View/Atom/Comment.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Atom/Comment.hs
@@ -0,0 +1,66 @@
+module Panda.View.Atom.Comment where
+  
+import Panda.Helper.Env hiding (title)
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Config.Global as G
+import Panda.Helper.StateHelper
+import Panda.Type.State hiding (uid)
+
+-- view
+import qualified Panda.View.Atom.Tag as Tag
+
+-- model
+import Panda.Model.Comment hiding (create)
+
+-- render
+instance DataRenderer Comment where
+  render_data = entry
+  
+entry x = toHtml  
+  [ gravatar
+  , cite << a
+  , toHtml " says:"
+  , br
+  , p ! [theclass "small"] << comment_date x
+  , x.markup
+  ]
+  where
+    a = if x.author_link.null then x.author.toHtml else toHtml $ hotlink (x.author_link) << x.author
+    gravatar = thediv ! [theclass "gravatar"] << image ! [src (gravatar_link x)]
+
+comment_date x = toHtml $ x.date.format_date "%b %e, %Y at %I:%M %p"
+
+
+-- form
+human_test_question state = ["What", "is", state.show_left, state.show_op, state.show_right, "?"] .join " "
+
+create state x = toHtml
+  [ h2 ! [id "respond"] << "Leave a Response"
+  , gui (G.root / "comment/create") ! [id "commentform"] <<
+    [ field Author 22 1 "Name (required)"
+    , field AuthorEmail 22 2 "Email (hidden)"
+    , field AuthorLink 22 3 "Website"
+    , field HumanHack 22 4 (human_test_question state)
+    , empty_field
+    , hidden_field LeftNumber (state.show_left)
+    , hidden_field RightNumber (state.show_right)
+    , hidden_field Operator (state.show_op)
+    , p << hidden (show_data PostId) (x.uid.uid_to_post_id)
+    , p << textarea ! [name (show_data Body), id "comment", cols "10", rows "20", strAttr "tabindex" "5"] << ""
+    , p << submit "submit" "Submit Comment" ! [strAttr "tabindex" "6"]
+    ]
+  ]
+
+field_with_value v x' s t m = p <<
+  [ label ! [thefor x] << small << m
+  , br 
+  , textfield x ! [size (s.show), strAttr "tabindex" (t.show), value v]
+  ]
+  where x = x'.show_data
+
+field x s t m = field_with_value "" x s t m
+
+hidden_field x m = hidden_field_with_value m x m
+hidden_field_with_value v x m = thespan ! [ thestyle "display: none;" ] << field_with_value v x 22 10 m
+
+empty_field = hidden_field_with_value "" EmptyField "Leave this field empty:"
diff --git a/src/Panda/View/Atom/Post.hs b/src/Panda/View/Atom/Post.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Atom/Post.hs
@@ -0,0 +1,39 @@
+module Panda.View.Atom.Post where
+  
+import Panda.Helper.Env hiding (name, title)
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Config.Global as G
+import Panda.Helper.StateHelper hiding (uri)
+
+-- view
+import qualified Panda.View.Atom.Tag as Tag
+
+-- model
+import Panda.Model.Post
+
+-- render
+instance DataRenderer Post where
+  render_data = entry Full
+
+data RenderStyle = Summary | Full
+
+-- instance helpers
+entry style x = div_class "post" << [entry_title, entry_mark, entry_body] where
+  entry_title = x.title_link
+  entry_body  = div_class "entry" << show_content style x
+  entry_mark  = p ! [theclass "small"] << [ post_date, post_tags, post_comments ].map (send_to x)
+
+show_content Summary x = x.markup_summary
+show_content Full    x = x.markup
+
+-- post could also be a summary
+render_summary x = if x.is True then entry Summary else render_data
+
+title_link x              = h2 << hotlink (x.uri) << x.title
+post_date x               = toHtml $ x.date.format_date "%b %e, %Y"
+
+post_tags x | x.tags.null = empty_html
+post_tags x = " | " +++ "Published in " +++ x.tags.map Tag.tag_link .intersperse (", ".toHtml)
+
+post_comments x | x.comment_size.is 0 = empty_html
+post_comments x = " | " +++ hotlink ( x.uri / "#comments") << (x.comment_size.show ++ " Comments")
diff --git a/src/Panda/View/Atom/Static.hs b/src/Panda/View/Atom/Static.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Atom/Static.hs
@@ -0,0 +1,7 @@
+module Panda.View.Atom.Static where
+  
+import Panda.Helper.Env
+import Panda.Model.Static
+
+instance DataRenderer Static where
+  render_data = markup
diff --git a/src/Panda/View/Atom/Tag.hs b/src/Panda/View/Atom/Tag.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Atom/Tag.hs
@@ -0,0 +1,12 @@
+module Panda.View.Atom.Tag where
+  
+import Panda.Helper.Env hiding (name)
+import Panda.Model.Tag
+import qualified Panda.Config.Global as G
+import Prelude hiding ((/))
+
+instance DataRenderer Tag where
+  render_data = name >>> tag_link
+
+tag_link s = toHtml $ hotlink (G.root / G.tag_id / s ) << s 
+
diff --git a/src/Panda/View/Control/Comment.hs b/src/Panda/View/Control/Comment.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Control/Comment.hs
@@ -0,0 +1,25 @@
+module Panda.View.Control.Comment where
+  
+import Panda.Helper.Env hiding (body, date)
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Type.State as State
+
+import Panda.Model.Comment hiding (create)
+import qualified Panda.Model.Post as Post
+
+-- view
+import Panda.View.Control.Helper
+import Panda.View.Widget.Template
+import Panda.View.Atom.Comment as Comment
+
+
+-- api
+styled_entry alt x = li ! [theclass alt] << x.render_data
+
+list [] = []
+list xs = 
+  [ h2 ! [id "comments"] << "Responses"
+  , olist ! [theclass "commentlist" ] << xs.zip (cycle ["comments-alt", ""]).map (splash styled_entry)
+  ]
+  
+create = Comment.create
diff --git a/src/Panda/View/Control/Helper.hs b/src/Panda/View/Control/Helper.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Control/Helper.hs
@@ -0,0 +1,26 @@
+module Panda.View.Control.Helper where
+
+import Panda.Helper.Env
+import Prelude hiding ((.), (/), (^), id, span)
+  
+import Panda.Type.Pager as Pager
+import qualified Panda.Config.Global as G
+
+
+nav p r = 
+  [ div_class "alignleft" << nav_previous p r
+  , div_class "alignright" << nav_next p r
+  ]
+  
+nav_previous p r = if p.Pager.has_previous 
+  then toHtml $ hotlink ( r' ++ "page=" ++ p.Pager.previous.show ) << previous_sign
+  else spaceHtml
+  where r' = if isSuffixOf "&" r then r else r ++ "?"
+    
+nav_next p r = if p.Pager.has_next
+  then toHtml $ hotlink ( r' ++ "page=" ++ p.Pager.next.show ) << next_sign
+  else spaceHtml
+  where r' = if isSuffixOf "&" r then r else r ++ "?"
+
+next_sign     = "Next Entries »"
+previous_sign = "« Previous Entries"
diff --git a/src/Panda/View/Control/Post.hs b/src/Panda/View/Control/Post.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Control/Post.hs
@@ -0,0 +1,30 @@
+module Panda.View.Control.Post where
+
+-- env
+import Panda.Helper.Env
+import Prelude hiding ((.), (/), (^), id, span)
+
+import qualified Panda.Type.State as State
+import qualified Panda.Config.Global as G
+
+import Panda.Type.Extension
+import Panda.Helper.StateHelper
+
+-- model
+import qualified Panda.Model.Comment as Comment
+import Panda.View.Atom.Post
+import Panda.Model.Post
+
+-- view
+import Panda.View.Control.Helper
+import qualified Panda.View.Control.Comment as CommentV
+import Panda.View.Widget.Template
+
+-- entry view
+view state xs x = (x.render_data +++ comment_view ).page state
+  where comment_view = for_extension Comment $ CommentV.list xs +++ CommentV.create state (x.uid.Comment.from_post_id)
+
+-- list view
+list state = for_current_page p >>> map render >>> (+++ nav p G.root) >>> page state where 
+  p = state.State.pager
+  render = render_summary G.summary_for_root
diff --git a/src/Panda/View/Control/Search.hs b/src/Panda/View/Control/Search.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Control/Search.hs
@@ -0,0 +1,15 @@
+module Panda.View.Control.Search where
+  
+import Panda.Helper.Env
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Type.State as State
+import qualified Panda.Config.Global as G
+
+-- view
+import Panda.View.Atom.Post
+import Panda.View.Control.Helper
+import Panda.View.Widget.Template
+
+view state s = for_current_page p >>> map render_data >>> (+++ nav p ( (G.root / "search") ++ "?s=" ++ s ++ "&")) >>> page state
+  where p = state.State.pager
+
diff --git a/src/Panda/View/Control/Static.hs b/src/Panda/View/Control/Static.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Control/Static.hs
@@ -0,0 +1,7 @@
+module Panda.View.Control.Static where
+  
+import Panda.Helper.Env
+import Panda.View.Atom.Static
+import Panda.View.Widget.Template
+
+view state = render_data >>> page state
diff --git a/src/Panda/View/Control/Tag.hs b/src/Panda/View/Control/Tag.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Control/Tag.hs
@@ -0,0 +1,18 @@
+module Panda.View.Control.Tag where
+  
+import Panda.Helper.Env
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Type.State as State
+import qualified Panda.Config.Global as G
+
+-- view
+import Panda.View.Atom.Post
+import Panda.View.Control.Helper
+import Panda.View.Widget.Template
+
+
+view state = for_current_page p >>> map render >>> (+++ nav p ( G.root / tag_id)) >>> page state where
+  p = state.State.pager
+  tag_id = state.State.uid
+  render = render_summary G.summary_for_tag
+
diff --git a/src/Panda/View/Widget/Body.hs b/src/Panda/View/Widget/Body.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Widget/Body.hs
@@ -0,0 +1,8 @@
+module Panda.View.Widget.Body where
+
+-- env
+import Panda.Helper.Env
+
+body_content x = div_id "maincontent" <<
+  div_class "content" << x
+
diff --git a/src/Panda/View/Widget/Footer.hs b/src/Panda/View/Widget/Footer.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Widget/Footer.hs
@@ -0,0 +1,17 @@
+module Panda.View.Widget.Footer where
+  
+import Panda.Helper.Env
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Config.Global as G
+
+
+footer c = div_class_id c "footer" <<
+  [ toHtml $ copyright
+  , toHtml $ "2008 "
+  , toHtml $ G.blog_title
+  , toHtml $ br
+  , toHtml $ "Powered by "
+  , toHtml $ hotlink G.panda_url << "Panda"
+  , toHtml $ " using "
+  , toHtml $ hotlink "http://www.haskell.org/" << "Haskell"
+  ]
diff --git a/src/Panda/View/Widget/Head.hs b/src/Panda/View/Widget/Head.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Widget/Head.hs
@@ -0,0 +1,17 @@
+module Panda.View.Widget.Head where
+
+import Panda.Helper.Env
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Config.Global as G
+import Panda.Type.Theme hiding (header)
+import qualified Panda.Type.State as S
+import Panda.View.Widget.Helper
+
+html_head state = header << ([meta_tag, title_tag state, favicon_tag, rss_tag state] ++ G.theme.css.map css_link ++ G.theme.js.map js_link )
+
+title_tag state = thetitle << [G.blog_title ++ state.S.resource_title.format_title]
+rss_tag state   = rss_link $ rss_url_link_pair state .fst
+favicon_tag = favicon_link G.favicon
+
+format_title [] = ""
+format_title s = " / " ++ s
diff --git a/src/Panda/View/Widget/Header.hs b/src/Panda/View/Widget/Header.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Widget/Header.hs
@@ -0,0 +1,18 @@
+module Panda.View.Widget.Header where
+  
+import Panda.Helper.Env hiding (header)
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Config.Global as G
+import Panda.View.Widget.SearchBar
+
+header c state =
+  div_class_id c "header" << 
+    [ search_bar
+    , site_name
+    ]
+
+site_name = toHtml $
+  [ toHtml $ hotlink G.root ! [theclass "logo"] << ""
+  , h1 << G.blog_title
+  , div_class "description" << G.blog_subtitle
+  ]
diff --git a/src/Panda/View/Widget/Helper.hs b/src/Panda/View/Widget/Helper.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Widget/Helper.hs
@@ -0,0 +1,20 @@
+module Panda.View.Widget.Helper where
+
+import Panda.Helper.Env
+import Prelude hiding ((.), (/), (^), id, span)
+  
+import qualified Panda.Config.Global as G
+import qualified Panda.Type.State as State
+import qualified Panda.Model.Tag as Tag
+
+rss_url_link_pair state = 
+  if tagged 
+  then
+    let tag_name = Tag.get_name uid in
+      link ( G.tag_id / tag_name) tag_name
+  else link "" "Home"
+  where
+    uid = State.uid state
+    tagged = uid.match "^tag/.+" .isJust
+    url r = G.root / r / "rss.xml"
+    link r s = (url r, hotlink (url r) ! [ theclass "feedlink" ] << s)
diff --git a/src/Panda/View/Widget/Navigation.hs b/src/Panda/View/Widget/Navigation.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Widget/Navigation.hs
@@ -0,0 +1,19 @@
+module Panda.View.Widget.Navigation where
+  
+import Panda.Helper.Env hiding (header)
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Config.Global as G
+import qualified Panda.Type.State as State
+
+navigation c state = 
+  div_class_id c "nav" <<
+    div_class "content" <<
+      ulist << G.navigation.map (nav_item (state.State.nav_location))
+
+nav_item nav s = li ! [theclass (home_tag ++ "page_item" ++ current)] << link s where
+  home_tag = if s == home_nav then "first " else ""
+  current = if s == nav then " current_page_item" else ""
+  link x = if x == home_nav then home_link else static_link x
+
+home_link     = hotlink G.root << home_nav
+static_link s = hotlink (G.root / "static" / s) << s.dropExtension
diff --git a/src/Panda/View/Widget/RSS.hs b/src/Panda/View/Widget/RSS.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Widget/RSS.hs
@@ -0,0 +1,37 @@
+module Panda.View.Widget.RSS where
+
+-- env
+import Panda.Helper.Env -- hiding (Language)
+import Prelude hiding ((.), (/), (^), id)
+import qualified Panda.Config.Global as G
+import qualified Panda.Type.Pager as Pager
+import Text.RSS
+import qualified Text.RSS as RSS
+
+-- model
+import Panda.Model.Post as Post
+
+-- RSS
+channel_rss_template = [ RSS.Language "en-us" ]
+
+render_rss x | G.summary_for_rss.is True = x.markup_summary.show
+render_rss x | otherwise                 = x.markup.show
+
+item_rss_template x = 
+  [ Title $ x.Post.title
+  , Description $ x.render_rss
+  , Author $ G.author_email
+  , Link $ x.item_uri
+  , PubDate $ x.date
+  ]
+
+item_uri x = nullURI { uriScheme = "http://", uriPath = host_link (x.uri) }
+rss_uri x  = nullURI { uriScheme = "http://", uriPath = host_link x }
+
+rss categary s xs = RSS title  (rss_uri link) title channel_rss_template (xs.take 20 .map item_rss_template)
+  .rssToXML.showXML
+  where
+    link = categary / s
+    title = if s.empty then G.blog_title else G.blog_title ++ " / " ++ s
+
+host_link s = G.host_name ++ (G.root / s)
diff --git a/src/Panda/View/Widget/SearchBar.hs b/src/Panda/View/Widget/SearchBar.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Widget/SearchBar.hs
@@ -0,0 +1,9 @@
+module Panda.View.Widget.SearchBar where
+  
+import Panda.Helper.Env hiding (header)
+import qualified Panda.Config.Global as G
+import Prelude hiding ((/))
+import Panda.Type.Extension
+import Panda.Helper.StateHelper
+
+search_bar = for_extension Search $ div_id "search" << form ! [action (G.root / "search"), method "get"] << thediv << textfield "s"
diff --git a/src/Panda/View/Widget/Sidebar.hs b/src/Panda/View/Widget/Sidebar.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Widget/Sidebar.hs
@@ -0,0 +1,37 @@
+module Panda.View.Widget.Sidebar where
+
+import Panda.Helper.Env hiding (rss_link)
+import Prelude hiding ((.), (/), (^), id, span)
+import qualified Panda.Type.State as State
+
+import qualified Data.Set as Set
+import qualified Panda.Config.Global as G
+import qualified Panda.Type.Sidebar as Sidebar
+import Panda.View.Widget.Helper
+
+-- model
+import qualified Panda.Model.Tag as Tag
+
+-- view
+
+sidebar c state = 
+  div_class_id c "sidebar" << unordList formatted_list
+  where
+    stock_list = 
+      [ feed state
+      , tag_list $ state.State.tags
+      ] 
+    custom_list = G.sidebar.map (\x -> h2 << x.Sidebar.name +++ x.markup)
+    formatted_list = (stock_list ++ custom_list) .intersperse hr
+
+feed state = toHtml
+  [ h2 << "Subscribe"
+  , p ! [theclass "feed"] << rss_link state
+  ]
+
+rss_link = rss_url_link_pair >>> snd
+tag_list tags = toHtml
+  [ h2 << "Tags"
+  , unordList $ tags.Tag.sorted.map tag_link]
+  
+tag_link x = ( hotlink (G.root / x.Tag.uid) << x.Tag.name ) +++ ( " (" ++ x.Tag.resources.Set.size.show ++ ")" )
diff --git a/src/Panda/View/Widget/Template.hs b/src/Panda/View/Widget/Template.hs
new file mode 100644
--- /dev/null
+++ b/src/Panda/View/Widget/Template.hs
@@ -0,0 +1,35 @@
+{-# OPTIONS -fno-monomorphism-restriction #-}
+
+module Panda.View.Widget.Template (page) where
+
+import Panda.View.Widget.Head
+import Panda.View.Widget.Body
+import Panda.View.Widget.Header
+import Panda.View.Widget.Navigation
+import Panda.View.Widget.Sidebar
+import Panda.View.Widget.Footer
+import qualified Text.XHtml.Strict as Html
+import qualified Panda.Config.Global as G
+import qualified Panda.Type.Theme as T
+
+import Panda.Helper.Env hiding (header, body)
+import Prelude hiding ((.), (/), (^), id, span)
+
+-- extension
+import Panda.Extension.Analytics.T
+import Panda.Type.Extension
+import Panda.Helper.StateHelper
+
+body t state x = Html.body << 
+  [ div_class (t.T.container) << 
+    [ header (t.T.header) state
+    , navigation (t.T.navigation) state
+    , div_id "page" << [ div_class (t.T.main) << body_content x, sidebar (t.T.sidebar) state ]
+    , footer (t.T.footer)
+    ]
+  , for_extension Analytics $ primHtml analytics_snippet
+  ]
+
+template t state x = [html_head state, body t state x]
+
+page = template G.theme
