parsav  Check-in [03ea3dffe7]

Overview
Comment:more work on UI and circles
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 03ea3dffe75fb71e9ab8b0502034c8c9011a7ea1ba0b87e189541bd4ee331683
User & Date: lexi on 2021-01-14 01:11:51
Other Links: manifest | tags
Context
2021-01-14
05:31
more progress on ui & circles check-in: e78334fe04 user: lexi tags: trunk
01:11
more work on UI and circles check-in: 03ea3dffe7 user: lexi tags: trunk
2021-01-13
15:01
begin work on circles check-in: a4b4af5ca4 user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [96ec125a09] to [dc07991d31].

    13     13   		params = {uint64}, sql = [[
    14     14   			select domain, key, knownsince, parsav from parsav_servers
    15     15   				where id = $1::bigint
    16     16   		]];
    17     17   	};
    18     18   
    19     19   	conf_get = {
    20         -		params = {rawstring}, sql = [[
           20  +		params = {pstring}, sql = [[
    21     21   			select value from parsav_config
    22     22   				where key = $1::text limit 1
    23     23   		]];
    24     24   	};
    25     25   
    26     26   	conf_set = {
    27         -		params = {rawstring,rawstring}, cmd=true, sql = [[
           27  +		params = {pstring,pstring}, cmd=true, sql = [[
    28     28   			insert into parsav_config (key, value)
    29     29   				values ($1::text, $2::text)
    30     30   				on conflict (key) do update set value = $2::text
    31     31   		]];
    32     32   	};
    33     33   
    34     34   	conf_reset = {
................................................................................
   291    291   		params = {uint64,uint64}, sql = [[
   292    292   			select name, id, owner, array_length(members,1) from parsav_circles where
   293    293   				($1::bigint = 0 or $1::bigint = owner) and
   294    294   				($2::bigint = 0 or $2::bigint = id)
   295    295   		]];
   296    296   	};
   297    297   
   298         -	circle_members_fetch_cid = {
   299         -		params = {uint64, uint64}, sql = [[
   300         -			select unnest(members) from parsav_circles where
   301         -				($1::bigint = 0 or owner = $1::bigint) and
          298  +	circle_create = {
          299  +		params = {uint64,pstring}, sql = [[
          300  +			insert into parsav_circles (owner,name)
          301  +				values ($1::bigint, $2::text)
          302  +			returning id
          303  +		]];
          304  +	};
          305  +
          306  +	circle_destroy = {
          307  +		params = {uint64,uint64}, cmd = true, sql = [[
          308  +			delete from parsav_circles where
          309  +				owner = $1::bigint and
   302    310   				id = $2::bigint
   303    311   		]];
   304    312   	};
          313  +
          314  +	circle_memberships_uid = {
          315  +		params = {uint64, uint64}, sql = [[
          316  +			select name, id, owner, array_length(members,1) from parsav_circles where
          317  +				owner   =  $1::bigint and
          318  +				members @> array[$2::bigint]
          319  +		]];
          320  +	};
          321  +
          322  +	circle_members_fetch_cid = {
          323  +		params = {uint64}, sql = [[
          324  +			select unnest(members) from parsav_circles where
          325  +				id = $1::bigint
          326  +		]];
          327  +	};
   305    328   
   306    329   	circle_members_fetch_name = {
   307    330   		params = {uint64, pstring}, sql = [[
   308    331   			select unnest(members) from parsav_circles where
   309    332   				($1::bigint = 0 or owner = $1::bigint) and
   310    333   				name = $2::text
   311    334   		]];
................................................................................
   557    580   			order by c.tltime desc
   558    581   
   559    582   			limit case when $3::bigint = 0 then null
   560    583   					   else $3::bigint end
   561    584   			offset $4::bigint
   562    585   		]];
   563    586   	};
          587  +
          588  +	timeline_circle_fetch = {
          589  +		params = {uint64, uint64, uint64, uint64, uint64}, sql = [[
          590  +			with circle as (
          591  +				select unnest(members) from parsav_circles where id = $1::bigint
          592  +			)
          593  +
          594  +			select (c.post).*
          595  +			from pg_temp.parsavpg_known_content as c
          596  +
          597  +			where ($2::bigint = 0 or c.tltime   <= $2::bigint) and
          598  +				  ($3::bigint = 0 or $3::bigint <  c.tltime) and
          599  +				  (c.promoter in (table circle) or
          600  +				   c.promoter = (select owner from parsav_circles where id = $1::bigint))
          601  +
          602  +			order by c.tltime desc
          603  +
          604  +			limit case when $4::bigint = 0 then null
          605  +					   else $4::bigint end
          606  +			offset $5::bigint
          607  +		]];
          608  +	};
   564    609   
   565    610   	timeline_actor_fetch = {
   566    611   		params = {uint64, uint64, uint64, uint64, uint64}, sql = [[
   567    612   			with followed as (
   568    613   				select relatee from parsav_rels where
   569    614   					kind = <rel:follow> and
   570    615   					relator = $1::bigint
   571         -			), avoided as (
          616  +			), avoided as ( -- not strictly necessary but lets us minimize how much data needs to be sent back to parsav for filtering
   572    617   				select relatee as avoidee from parsav_rels where
   573    618   					kind = <rel:avoid> or kind = <rel:mute> and
   574    619   					relator = $1::bigint
   575    620   				union select relator as avoidee from parsav_rels where
   576    621   					kind = <rel:exclude> and
   577    622   					relatee = $1::bigint
   578    623   			)
................................................................................
   580    625   			select (c.post).*
   581    626   			from pg_temp.parsavpg_known_content as c
   582    627   
   583    628   			where ($2::bigint = 0 or c.tltime   <= $2::bigint) and
   584    629   				  ($3::bigint = 0 or $3::bigint <  c.tltime) and
   585    630   				  (c.promoter in (table followed) or
   586    631   				   c.promoter = $1::bigint) and
   587         -				  not ((c.post).author in (table avoided))
          632  +				  not ((c.post).author in (table avoided)) and
          633  +				  not (c.promoter      in (table avoided))
   588    634   			order by c.tltime desc
   589    635   
   590    636   			limit case when $4::bigint = 0 then null
   591    637   					   else $4::bigint end
   592    638   			offset $5::bigint
   593    639   		]];
   594    640   	};
................................................................................
   696    742   				artifacts = array_remove(artifacts, $2::bigint)
   697    743   			where id = $1::bigint and
   698    744   				artifacts @> array[$2::bigint]
   699    745   		]];
   700    746   	};
   701    747   
   702    748   	actor_conf_str_get = {
   703         -		params = {uint64, rawstring}, sql = [[
          749  +		params = {uint64, pstring}, sql = [[
   704    750   			select value from parsav_actor_conf_strs where
   705    751   				uid = $1::bigint and
   706    752   				key = $2::text
   707    753   			limit 1
   708    754   		]];
   709    755   	};
   710    756   	actor_conf_str_set = {
   711         -		params = {uint64, rawstring, rawstring}, cmd = true, sql = [[
          757  +		params = {uint64, pstring, pstring}, cmd = true, sql = [[
   712    758   			insert into parsav_actor_conf_strs (uid,key,value)
   713    759   				values ($1::bigint, $2::text, $3::text)
   714    760   			on conflict (uid,key) do update set value = $3::text
   715    761   		]];
   716    762   	};
   717    763   	actor_conf_str_enum = {
   718    764   		params = {uint64}, sql = [[
   719    765   			select value from parsav_actor_conf_strs where uid = $1::bigint
   720    766   		]];
   721    767   	};
   722    768   	actor_conf_str_reset = {
   723         -		params = {uint64, rawstring}, cmd = true, sql = [[
          769  +		params = {uint64, pstring}, cmd = true, sql = [[
   724    770   			delete from parsav_actor_conf_strs where
   725    771   				uid = $1::bigint and ($2::text is null or key = $2::text)
   726    772   		]]
   727    773   	};
   728    774   
   729    775   	actor_conf_int_get = {
   730         -		params = {uint64, rawstring}, sql = [[
          776  +		params = {uint64, pstring}, sql = [[
   731    777   			select value from parsav_actor_conf_ints where
   732    778   				uid = $1::bigint and
   733    779   				key = $2::text
   734    780   			limit 1
   735    781   		]];
   736    782   	};
   737    783   	actor_conf_int_set = {
   738         -		params = {uint64, rawstring, uint64}, cmd = true, sql = [[
          784  +		params = {uint64, pstring, uint64}, cmd = true, sql = [[
   739    785   			insert into parsav_actor_conf_ints (uid,key,value)
   740    786   				values ($1::bigint, $2::text, $3::bigint)
   741    787   			on conflict (uid,key) do update set value = $3::bigint
   742    788   		]];
   743    789   	};
   744    790   	actor_conf_int_enum = {
   745    791   		params = {uint64}, sql = [[
   746    792   			select value from parsav_actor_conf_ints where uid = $1::bigint
   747    793   		]];
   748    794   	};
   749    795   	actor_conf_int_reset = {
   750         -		params = {uint64, rawstring}, cmd = true, sql = [[
          796  +		params = {uint64, pstring}, cmd = true, sql = [[
   751    797   			delete from parsav_actor_conf_ints where
   752    798   				uid = $1::bigint and ($2::text is null or key = $2::text)
   753    799   		]]
   754    800   	};
   755    801   }
   756    802   
   757    803   local struct pqr {
................................................................................
  1274   1320   			return true
  1275   1321   		else
  1276   1322   			lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res))
  1277   1323   			return false
  1278   1324   		end
  1279   1325   	end];
  1280   1326   
  1281         -	conf_get = [terra(src: &lib.store.source, key: rawstring)
         1327  +	conf_get = [terra(src: &lib.store.source, key: pstring)
  1282   1328   		var r = queries.conf_get.exec(src, key)
  1283   1329   		if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
  1284   1330   			defer r:free()
  1285   1331   			return r:String(0,0)
  1286   1332   		end
  1287   1333   	end];
  1288         -	conf_set = [terra(src: &lib.store.source, key: rawstring, val: rawstring)
         1334  +	conf_set = [terra(src: &lib.store.source, key: pstring, val: pstring)
  1289   1335   		queries.conf_set.exec(src, key, val):free() end];
  1290   1336   	conf_reset = [terra(src: &lib.store.source, key: rawstring)
  1291   1337   		queries.conf_reset.exec(src, key):free() end];
  1292   1338   	
  1293   1339   	actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64)
  1294   1340   		var r = queries.actor_fetch_uid.exec(src, uid)
  1295   1341   		if r.sz == 0 then
................................................................................
  1546   1592   	timeline_instance_fetch = [terra(
  1547   1593   		src: &lib.store.source,
  1548   1594   		rg: lib.store.range
  1549   1595   	): lib.mem.lstptr(lib.store.post)
  1550   1596   		var r = pqr { sz = 0 }
  1551   1597   		var A,B,C,D = rg:matrix() -- :/
  1552   1598   		r = queries.timeline_instance_fetch.exec(src,A,B,C,D)
         1599  +		
         1600  +		var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
         1601  +		for i=0,r.sz do
         1602  +			ret.ptr[i] = row_to_post(&r, i) -- MUST FREE ALL
         1603  +			ret.ptr[i].ptr.source = src
         1604  +		end
         1605  +
         1606  +		return ret
         1607  +	end];
         1608  +
         1609  +	timeline_circle_fetch = [terra(
         1610  +		src: &lib.store.source,
         1611  +		cid: uint64,
         1612  +		rg: lib.store.range
         1613  +	): lib.mem.lstptr(lib.store.post)
         1614  +		var r = pqr { sz = 0 }
         1615  +		var A,B,C,D = rg:matrix() -- :/
         1616  +		r = queries.timeline_circle_fetch.exec(src,cid,A,B,C,D)
  1553   1617   		
  1554   1618   		var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
  1555   1619   		for i=0,r.sz do
  1556   1620   			ret.ptr[i] = row_to_post(&r, i) -- MUST FREE ALL
  1557   1621   			ret.ptr[i].ptr.source = src
  1558   1622   		end
  1559   1623   
................................................................................
  2032   2096   	auth_sigtime_user_alter = [terra(
  2033   2097   		src: &lib.store.source,
  2034   2098   		uid: uint64,
  2035   2099   		time: lib.store.timepoint
  2036   2100   	): {} queries.auth_sigtime_user_alter.exec(src, uid, time) end];
  2037   2101   
  2038   2102   	actor_conf_str_enum = nil;
  2039         -	actor_conf_str_get = [terra(src: &lib.store.source, uid: uint64, key: rawstring): pstring
         2103  +	actor_conf_str_get = [terra(
         2104  +		src: &lib.store.source,
         2105  +		pool: &lib.mem.pool,
         2106  +		uid: uint64,
         2107  +		key: pstring
         2108  +	): pstring
  2040   2109   			var r = queries.actor_conf_str_get.exec(src, uid, key)
  2041   2110   			if r.sz > 0 then
  2042         -				var ret = r:String(0,0)
  2043         -				r:free()
  2044         -				return ret
         2111  +				return r:_string(0,0):pdup(pool)
  2045   2112   			else return pstring.null() end
  2046   2113   		end];
  2047         -	actor_conf_str_set = [terra(src: &lib.store.source, uid: uint64, key: rawstring, value: rawstring): {}
         2114  +	actor_conf_str_set = [terra(src: &lib.store.source, uid: uint64, key: pstring, value: pstring): {}
  2048   2115   			queries.actor_conf_str_set.exec(src,uid,key,value) end];
  2049         -	actor_conf_str_reset = [terra(src: &lib.store.source, uid: uint64, key: rawstring): {}
         2116  +	actor_conf_str_reset = [terra(src: &lib.store.source, uid: uint64, key: pstring): {}
  2050   2117   			queries.actor_conf_str_reset.exec(src,uid,key) end];
  2051   2118   
  2052   2119   	actor_conf_int_enum = nil;
  2053         -	actor_conf_int_get = [terra(src: &lib.store.source, uid: uint64, key: rawstring)
         2120  +	actor_conf_int_get = [terra(src: &lib.store.source, uid: uint64, key: pstring)
  2054   2121   			var r = queries.actor_conf_int_get.exec(src, uid, key)
  2055   2122   			if r.sz > 0 then
  2056   2123   				var ret = r:int(uint64,0,0)
  2057   2124   				r:free()
  2058   2125   				return ret, true
  2059   2126   			end
  2060   2127   			return 0, false
  2061   2128   		end];
  2062         -	actor_conf_int_set = [terra(src: &lib.store.source, uid: uint64, key: rawstring, value: uint64): {}
         2129  +	actor_conf_int_set = [terra(src: &lib.store.source, uid: uint64, key: pstring, value: uint64): {}
  2063   2130   			queries.actor_conf_int_set.exec(src,uid,key,value) end];
  2064         -	actor_conf_int_reset = [terra(src: &lib.store.source, uid: uint64, key: rawstring): {}
         2131  +	actor_conf_int_reset = [terra(src: &lib.store.source, uid: uint64, key: pstring): {}
  2065   2132   			queries.actor_conf_int_reset.exec(src,uid,key) end];
  2066   2133   	
  2067   2134   	circle_search = [terra(
  2068   2135   		src: &lib.store.source,
  2069   2136   		pool:&lib.mem.pool,
  2070   2137   		uid: uint64,
  2071   2138   		cid: uint64
................................................................................
  2074   2141   		if res.sz == 0 then return [lib.mem.ptr(lib.store.circle)].null() end
  2075   2142   		defer res:free()
  2076   2143   
  2077   2144   		var rt = pool:alloc(lib.store.circle, res.sz)
  2078   2145   		for i = 0, res.sz do
  2079   2146   			var name = res:_string(i,0)
  2080   2147   			rt(i) = lib.store.circle {
  2081         -				name = name:pdup(pool);
  2082         -				cid = res:int(uint64,i,1);
  2083         -				owner = res:int(uint64,i,2);
  2084         -				memcount = res:int(uint64,i,3);
         2148  +				name = name:pdup(pool);      cid = res:int(uint64,i,1);
         2149  +				owner = res:int(uint64,i,2); memcount = res:int(uint64,i,3);
         2150  +			}
         2151  +		end
         2152  +
         2153  +		return rt
         2154  +	end];
         2155  +
         2156  +	circle_create = [terra(
         2157  +		src: &lib.store.source,
         2158  +		owner: uint64,
         2159  +		name: pstring
         2160  +	): uint64
         2161  +		var r = queries.circle_create.exec(src, owner, name)
         2162  +		if r.sz > 0 then defer r:free() return r:int(uint64,0,0) end
         2163  +		return 0
         2164  +	end];
         2165  +
         2166  +	circle_destroy = [terra(
         2167  +		src: &lib.store.source,
         2168  +		owner: uint64,
         2169  +		cid: uint64
         2170  +	): {} queries.circle_destroy.exec(src, owner, cid) end];
         2171  +
         2172  +	circle_memberships_uid = [terra(
         2173  +		src: &lib.store.source,
         2174  +		pool:&lib.mem.pool,
         2175  +		owner: uint64,
         2176  +		subject: uint64
         2177  +	): lib.mem.ptr(lib.store.circle)
         2178  +		var res = queries.circle_memberships_uid.exec(src, owner, subject)
         2179  +		if res.sz == 0 then return [lib.mem.ptr(lib.store.circle)].null() end
         2180  +		defer res:free()
         2181  +
         2182  +		var rt = pool:alloc(lib.store.circle, res.sz)
         2183  +		for i = 0, res.sz do
         2184  +			var name = res:_string(i,0)
         2185  +			rt(i) = lib.store.circle {
         2186  +				name = name:pdup(pool);      cid = res:int(uint64,i,1);
         2187  +				owner = res:int(uint64,i,2); memcount = res:int(uint64,i,3);
  2085   2188   			}
  2086   2189   		end
  2087   2190   
  2088   2191   		return rt
  2089   2192   	end];
  2090   2193   
  2091   2194   	circle_members_fetch_cid = [terra(
  2092   2195   		src: &lib.store.source,
  2093   2196   		pool:&lib.mem.pool,
  2094         -		uid: uint64,
  2095   2197   		cid: uint64
  2096   2198   	): lib.mem.ptr(uint64)
  2097         -		var res = queries.circle_members_fetch_cid.exec(src,uid,cid)
         2199  +		var res = queries.circle_members_fetch_cid.exec(src,cid)
  2098   2200   		if res.sz == 0 then return [lib.mem.ptr(uint64)].null() end
  2099   2201   		defer res:free()
  2100   2202   
  2101   2203   		var rt = pool:alloc(uint64, res.sz)
  2102   2204   		for i = 0, res.sz do rt(i) = res:int(uint64,i,0) end
  2103   2205   
  2104   2206   		return rt
  2105   2207   	end];
  2106   2208   
  2107   2209   	actor_auth_register_uid = nil; -- TODO better support non-view based auth
  2108   2210   }
  2109   2211   
  2110   2212   return b

Modified config.lua from [a1f76576fd] to [b2a2580dbd].

    51     51   		-- TODO with gzip compression, svg is dramatically superior to webp
    52     52   		-- we should add support for content-encoding headers and pre-compress
    53     53   		-- the damn things before compiling (also making the binary smaller)
    54     54   		{'style.css', 'text/css'};
    55     55   		{'live.js', 'text/javascript'}; -- rrrrrrrr
    56     56   		{'default-avatar.webp', 'image/webp'}; -- needs inkscape-exclusive svg features
    57     57   		{'bell.svg', 'image/svg+xml'};
           58  +		{'gear.svg', 'image/svg+xml'};
    58     59   		{'heart.webp', 'image/webp'};
    59     60   		{'retweet.webp', 'image/webp'};
    60     61   		{'padlock.svg', 'image/svg+xml'};
    61     62   		{'warn.svg', 'image/svg+xml'};
    62     63   		{'query.webp', 'image/webp'};
    63     64   		{'reply.webp', 'image/webp'};
    64     65   		{'file.webp', 'image/webp'};

Modified mem.t from [a251bec86e] to [8c252399a5].

   108    108   	end
   109    109   	terra t.methods.null(): t return t { ptr = nil, ct = 0 } end -- maybe should be a macro?
   110    110   	terra t:ref() return self.ptr ~= nil end
   111    111   	t.metamethods.__not = macro(function(self) return `not self:ref() end)
   112    112   	t.metamethods.__apply = macro(function(self,idx) return `self.ptr[ [idx or 0] ] end)
   113    113   	t.metamethods.__update = macro(function(self,idx,rhs)
   114    114   		return quote self.ptr[idx] = rhs end end)
          115  +	t.metamethods.__cast = function(from,to,exp)
          116  +		if to == t then
          117  +			if from == niltype then return `t.null()
          118  +			elseif from == &ty then return `t {ptr = exp, ct = 1}
          119  +			elseif from == ty then return `t {ptr = &exp, ct = 1}
          120  +			elseif from.N and from.type == ty then
          121  +				return `t {ptr = &exp[0], ct = from.N }
          122  +			end
          123  +			error('invalid cast to ' .. t.name .. ' from ' .. tostring(from))
          124  +		elseif from == t then
          125  +			if     to == &ty  then return `exp.ptr
          126  +			elseif to == ty   then return `@exp.ptr
          127  +			elseif to == bool then return `exp:ref() end
          128  +			error('invalid cast from ' .. t.name .. ' to ' .. tostring(to))
          129  +		end
          130  +		error('invalid pointer cast')
          131  +	end
   115    132   
   116    133   	if not ty:isstruct() then
   117    134   		terra t:cmp_raw(other: &ty)
   118    135   			for i=0, self.ct do
   119    136   				if self.ptr[i] ~= other[i] then return false end
   120    137   			end
   121    138   			return true

Modified parsav.md from [23851a52a8] to [d83ecc5b3e].

   142    142   * ldap for auth (and maybe actors?)
   143    143   * cdb (for static content, maybe? does this make sense?)
   144    144   * mariadb/mysql
   145    145   * the various nosql horrors, e.g. redis, mongo, and so on
   146    146   
   147    147   parsav urgently needs an internationalization framework as well. right now everything is just hardcoded in english. yuck.
   148    148   
   149         -parsav could be significantly improved by adjusting its memory management strategy. instead of allocating everything with lib.mem.heapa (which currently maps to malloc on all platforms), we should allocate a static buffer for the server overlord object which can simply be cleared and re-used for each http request, and enlarged with `realloc` when necessary. the entire region could be `mlock`ed for better performance, and it would no longer be necessary to track and free memory, as the entire buffer would simply be discarded after use (similar to PHP's original memory management strategy). this would remove possibly the largest source of latency in the codebase, as `parsav` is regrettably quite heavy on malloc, performing numerous allocations for each page rendered. **update:** this is now in progress
          149  +parsav could be significantly improved by adjusting its memory management strategy. instead of allocating everything with lib.mem.heapa (which currently maps to malloc on all platforms), we can allocate a static buffer for the server overlord object which can simply be cleared and re-used for each http request, and enlarged with `realloc` when necessary. the entire region could be `mlock`ed for better performance, and it would no longer be necessary to track and free memory, as the entire buffer would simply be discarded after use (similar to PHP's original memory management strategy). this would remove possibly the largest source of latency in the codebase, as `parsav` is regrettably quite heavy on malloc, performing numerous allocations for each page rendered. **update:** this is now in progress, and much of the UI code has been converted; the database code will also need to be converted, however, and this will be too time-consuming to be worth tackling any time soon. new functions should be written to use the memory pooling strategy, however.

Modified parsav.t from [d3351b7874] to [8d6924c4e0].

   490    490   	'render:notices';
   491    491   
   492    492   	'render:media-gallery';
   493    493   
   494    494   	'render:docpage';
   495    495   
   496    496   	'render:conf:profile';
          497  +	'render:conf:circles';
   497    498   	'render:conf:sec';
   498    499   	'render:conf:users';
   499    500   	'render:conf:avi';
   500    501   	'render:conf';
   501    502   	'route';
   502    503   }
   503    504   

Added render/conf/circles.t version [2450dbe8a2].

            1  +-- vim: ft=terra
            2  +local pstr = lib.mem.ptr(int8)
            3  +local pref = lib.mem.ref(int8)
            4  +
            5  +
            6  +local terra 
            7  +render_conf_circles(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr
            8  +	if path.ct == 2 then
            9  +		var circs = co.srv:circle_search(&co.srv.pool, co.who.id, 0)
           10  +		var ui: data.view.conf_circles
           11  +		if circs.ct == 0 then
           12  +			ui.newattr = ' open';
           13  +		else
           14  +			ui.newattr = '';
           15  +			var circlst = co:stra(86)
           16  +			for i = 0, circs.ct do
           17  +				circlst:lpush '<li><a href="/conf/circles/':shpush(circs(i).cid):lpush'">'
           18  +					   :ppush(circs(i).name)
           19  +					   :lpush '</a></li>'
           20  +			end
           21  +			ui.circles = circlst:finalize()
           22  +		end
           23  +
           24  +		return ui:poolstr(&co.srv.pool)
           25  +	elseif path.ct == 3 then
           26  +		var cid, cid_ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct)
           27  +		if not cid_ok then return pstr.null() end
           28  +		var circ = co.srv:circle_search(&co.srv.pool, co.who.id, cid)
           29  +		if not circ then return pstr.null() end
           30  +		var actrs = co.srv:circle_members_fetch_cid(&co.srv.pool, cid)
           31  +
           32  +		var acta = co:stra(86)
           33  +		if actrs:ref() then
           34  +			for i=0, actrs.ct do
           35  +				var a = co:uid2actor(actrs(i))
           36  +				if a ~= nil then
           37  +					acta:lpush '<li><a class="username" href="/':push(a.xid,0):lpush '#rel">'
           38  +					lib.render.nym(a, 0, &acta, false)
           39  +					acta:lpush '</a></li>'
           40  +				end
           41  +			end
           42  +		end
           43  +
           44  +		var ui = data.view.conf_circle_view {
           45  +			circle = circ().name;
           46  +			actors = acta:finalize();
           47  +		}
           48  +
           49  +		return ui:poolstr(&co.srv.pool)
           50  +	else return pstr.null() end
           51  +end
           52  +
           53  +return render_conf_circles

Modified render/conf/profile.t from [5b3736f2f4] to [5726a3ab36].

    10     10   render_conf_profile(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr
    11     11   	var hue: int8[21]
    12     12   	var c = data.view.conf_profile {
    13     13   		handle = cs(co.who.handle);
    14     14   		nym = cs(lib.coalesce(co.who.nym,''));
    15     15   		bio = cs(lib.coalesce(co.who.bio,''));
    16     16   		hue = lib.math.decstr(co.ui_hue, &hue[20]);
           17  +		acl_follow = co:usercfg_str(co.who.id, 'acl-follow');
           18  +		acl_follow_req = co:usercfg_str(co.who.id, 'acl-follow-req');
    17     19   	}
    18     20   	return c:poolstr(&co.srv.pool)
    19     21   end
    20     22   
    21     23   return render_conf_profile

Modified render/conf/users.t from [24aad61532] to [be5a9e1656].

   368    368   		var users: lib.mem.lstptr(lib.store.actor)
   369    369   		if mode == mode_local then
   370    370   			users = co.srv:actor_enum_local()
   371    371   		else
   372    372   			users = co.srv:actor_enum()
   373    373   		end
   374    374   		ulst:lpush('</em></div>')
   375         -		ulst:lpush('<ul class="user-list">')
          375  +		ulst:lpush('<ul class="directory">')
   376    376   		for i=0,users.ct do var usr = users(i).ptr
   377    377   			if mode == mode_staff and usr.rights.rank == 0 then goto skip
   378    378   			elseif mode == mode_peons and usr.rights.rank ~= 0 then goto skip
   379    379   			elseif mode == mode_remote and usr.origin == 0 then goto skip 
   380    380   			elseif mode == mode_peers and usr.epithet == nil then goto skip end
   381    381   			var idlen = lib.math.shorthand.gen(usr.id, &idbuf[0])
   382    382   			ulst:lpush('<li>')

Modified render/nav.t from [9f2c55cf5b] to [5e68ddf43b].

     1      1   -- vim: ft=terra
     2      2   local terra 
     3      3   render_nav(co: &lib.srv.convo)
     4      4   	var t = co:stra(64)
     5      5   	if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
     6         -		t:lpush(' <a accesskey="t" href="/">timeline</a>')
            6  +		t:lpush(' <a accesskey="t" href="/"><u>t</u>imeline</a>')
     7      7   	end
     8      8   	if co.who ~= nil then
     9         -		t:lpush(' <a accesskey="c" href="/compose">compose</a> <a accesskey="p" href="/'):push(co.who.xid,0)
    10         -		t:lpush('">profile</a> <a accesskey="m" href="/media">media</a> <a accesskey="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <div class="ident">@')
            9  +		t:lpush(' <a accesskey="c" href="/compose"><u>c</u>ompose</a> <a accesskey="m" href="/media"><u>m</u>edia</a> <a accesskey="d" href="/doc"><u>d</u>ocs</a> <hr> <a accesskey="p" href="'):push(co.who.xid,0):lpush('" class="ident">')
    11     10   		t:push(co.who.handle,0)
    12         -		t:lpush('</div> <a accesskey="g" href="/logout">log out</a> <a class="bell" accesskey="x" href="/notices">notices</a>')
           11  +		t:lpush('</a> <a accesskey="g" href="/logout">lo<u>g</u> out</a> <a class="gear" accesskey="o" href="/conf">c<u>o</u>nfigure</a> <a class="bell" accesskey="x" href="/notices">notices (<u>x</u>)</a>')
    13     12   	else
    14         -		t:lpush(' <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/login">log in</a>')
           13  +		t:lpush(' <a accesskey="d" href="/doc"><u>d</u>ocs</a> <a accesskey="g" href="/login">lo<u>g</u> in</a>')
    15     14   	end
    16     15   	return t:finalize()
    17     16   end
    18     17   return render_nav

Modified render/profile.t from [0e11aad870] to [06727d1481].

    58     58   		{ id = 'disemvowel', start = {
    59     59   			text = 'disemvowel'; -- translations should not translate this literally
    60     60   			desc = "this user's posts will be ritually mutilated in a humorous fashion as appropriate to the script in which they are written; e.g. the removal of vowels in roman text and deletion of kana in japanese text";
    61     61   		}, stop = {
    62     62   			text = 're-emvowel';
    63     63   			desc = "this user's posts will once again appear normally";
    64     64   		}};
           65  +
           66  +		{ id = 'attenuate', start = {
           67  +			text = 'attenuate'; 
           68  +			desc = "this user will no longer be able to retweet things into your timeline";
           69  +		}, stop = {
           70  +			text = 'amplify';
           71  +			desc = "this user's retweets will be allowed to reach your timeline again";
           72  +		}};
    65     73   
    66     74   		{ id = 'block', start = {
    67     75   			text = 'block';
    68     76   			desc = "this user will not be able to interact with you in any fashion and they will be forced to unfollow you";
    69     77   		}, stop = {
    70     78   			text = 'unblock';
    71     79   			desc = "this user will once again be able to interact with you";

Modified render/timeline.t from [900216dd8b] to [5c434424ea].

    34     34   		sz = 0, run = 0
    35     35   	}
    36     36   	var fetchmode = lib.store.range {
    37     37   		mode = 1; -- T->I
    38     38   		from_time = stoptime;
    39     39   		to_idx = 64;
    40     40   	}
           41  +	var circ: lib.mem.ptr(lib.store.circle) = nil
    41     42   	if mode == modes.follow or mode == modes.mutual then
    42     43   		posts = co.srv:timeline_actor_fetch_uid(co.who.id,fetchmode)
    43     44   	elseif mode == [modes['local']] then
    44     45   		posts = co.srv:timeline_instance_fetch(fetchmode)
    45     46   	elseif mode == modes.fedi then
    46         -	elseif mode == modes.circle then
           47  +	elseif mode == modes.circle and spec:ref() then
           48  +		var cid, ok = lib.math.shorthand.parse(spec.ptr,spec.ct)
           49  +		if ok then
           50  +			circ = co.srv:circle_search(&co.srv.pool,co.who.id,cid)
           51  +			if circ.ct == 1 then
           52  +				posts = co.srv:timeline_circle_fetch(cid,fetchmode)
           53  +			end
           54  +		end
    47     55   	end
    48     56   
    49     57   	var acc = co:stra(1024)
    50         -	var modelabels = arrayof(pstr, 'followed', 'mutuals', 'local instance', 'fediverse', 'circle')
           58  +	var modelabels = arrayof(pstr, '<u>f</u>ollowed', 'm<u>u</u>tuals', '<u>l</u>ocal instance', 'fedi<u>v</u>erse', 'ci<u>r</u>cle')
           59  +	var keybinds = arrayof(pstr, 'f', 'u', 'l', 'v', 'r')
    51     60   	var modelinks = arrayof(pstr, [modes.members])
    52         -	acc:lpush('<div style="text-align: right"><em>showing ')
           61  +	acc:lpush('<div class="kind-picker">showing ')
    53     62   	for i=0, [modelabels.type.N] do
    54     63   		if co.aid ~= 0 or not requires_login(i) then
    55     64   			if i > 0 then acc:lpush(' · ') end
    56         -			if i == mode and not (mode == modes.circle and spec:ref()) then
           65  +			if i == mode and not circ then
    57     66   				acc:lpush('<strong>'):ppush(modelabels[i]):lpush('</strong>')
    58     67   			else
    59         -				acc:lpush('<a href="/tl/'):ppush(modelinks[i]):lpush('">'):ppush(modelabels[i]):lpush('</a>')
           68  +				acc:lpush('<a href="/tl/'):ppush(modelinks[i]):lpush('" accesskey="'):ppush(keybinds[i]):lpush('">')
           69  +				if i == mode and circ:ref() then
           70  +					acc:lpush'<strong>':ppush(modelabels[i]):lpush'</strong> (':ppush(circ().name):lpush(')')
           71  +				else
           72  +					acc:ppush(modelabels[i])
           73  +				end
           74  +				acc:lpush('</a>')
    60     75   			end
    61     76   		end
    62     77   	end
    63         -	acc:lpush('</em></div>')
           78  +	acc:lpush('</div>')
    64     79   	var newest: lib.store.timepoint = 0
    65     80   	if mode == modes.circle and not spec then
    66     81   		var circles = co.srv:circle_search(&co.srv.pool, co.who.id, 0)
    67     82   		acc:lpush '<menu class="circles">'
    68     83   		for i:intptr = 0, circles.ct do
    69         -			acc:lpush '<li><a href="/tl/circle/'
           84  +			acc:lpush '<a href="/tl/circle/'
    70     85   			   :shpush(circles(i).cid)
    71         -			   :lpush '">'
           86  +			if i <= 10 then
           87  +				acc:lpush '" accesskey="':ipush((i+1) % 10)
           88  +			end
           89  +			acc:lpush '">'
    72     90   			   :ppush(circles(i).name)
    73         -			   :lpush '</a></li>'
           91  +			   :lpush '</a>'
    74     92   		end
    75     93   		-- TODO list circles
    76     94   		acc:lpush '</menu>'
    77     95   	else
    78     96   		acc:lpush('<div id="tl" data-live="10">')
    79     97   		for i = 0, posts.sz do
    80     98   			var author = co:uid2actor(posts(i).ptr.author)
    81     99   			if mode == modes.mutual and posts(i).ptr.author ~= co.who.id then
    82    100   				if not author.relationship.recip.follow() then goto skip end
    83    101   			end
    84    102   			if author.relationship.rel.mute() or 
    85    103   			   author.relationship.rel.avoid() or 
    86    104   			   author.relationship.recip.exclude() then goto skip end
          105  +			if posts(i).ptr.rtdby ~= 0 then
          106  +				var rter = co:uid2actor(posts(i).ptr.rtdby)
          107  +				if rter.relationship.rel.mute()
          108  +				or rter.relationship.rel.attenuate()
          109  +				or rter.relationship.rel.avoid()
          110  +				or rter.relationship.recip.exclude() then goto skip end
          111  +			end
    87    112   			lib.render.tweet(co, posts(i).ptr, &acc)
    88    113   			var t = lib.math.biggest(lib.math.biggest(posts(i).ptr.posted, posts(i).ptr.discovered),posts(i).ptr.edited)
    89    114   			if t > newest then newest = t end
    90    115   			::skip:: posts(i):free()
    91    116   		end
    92    117   		if posts.run > 0 then posts:free() end
    93    118   		acc:lpush('</div>')

Modified render/tweet-page.t from [6d8fb63eed] to [1e93b5b498].

    38     38   	if co.aid ~= 0 then
    39     39   		pg:lpush('<form class="action-bar" method="post">')
    40     40   		if not co.srv:post_liked_uid(co.who.id, p.id)
    41     41   			then pg:lpush('<button class="pos" name="act" accesskey="l" value="like">like</button>')
    42     42   			else pg:lpush('<button class="neg" name="act" accesskey="l" value="dislike">dislike</button>')
    43     43   		end
    44     44   		pg:lpush('<button class="pos" name="act" accesskey="r" value="rt">retweet</button>')
           45  +		if co.who.rights.powers.crier() then
           46  +			pg:lpush('<button name="act" accesskey="p" value="promote">promote</button>')
           47  +		end
    45     48   		if p.author == co.who.id then
    46     49   			if co.who.rights.powers.edit() then
    47     50   				pg:lpush('<a class="button" accesskey="e" href="/post/'):rpush(path(1)):lpush('/edit">edit</a>')
    48     51   			end
    49     52   			pg:lpush('<a class="neg button" accesskey="d" href="/post/'):rpush(path(1)):lpush('/del">delete</a>')
    50     53   		elseif co.who.rights.powers.snitch() then
    51         -			pg:lpush('<a class="neg button" accesskey="s" href="/post/'):rpush(path(1)):lpush('/report">report</a>')
           54  +			pg:lpush('<a class="neg button" accesskey="s" href="/post/'):rpush(path(1)):lpush('/snitch">report</a>')
    52     55   		end
    53     56   		-- TODO list user's chosen reaction emoji
    54     57   		pg:lpush('</form>')
    55     58   
    56     59   	end
    57     60   	pg:lpush('<div id="convo" data-live="10">')
    58     61   	render_tweet_replies(co, &pg, p.id)

Modified route.t from [bb5bfba2ee] to [89c9cb6c12].

   254    254   		end
   255    255   	end
   256    256   	defer post:free() -- NOP on null
   257    257   
   258    258   	if path.ct == 3 then
   259    259   		var lnk: lib.str.acc lnk:compose('/post/', path(1))
   260    260   		var lnkp = lnk:finalize() defer lnkp:free()
   261         -		if post:ref() and post(0).author ~= co.who.id then
          261  +		if post:ref() and path(2):cmp(lib.str.lit 'snitch') then
          262  +			if meth_get(meth) then
          263  +				var ui = data.view.report {
          264  +					badtweet = lib.render.tweet(co, post.ptr, nil);
          265  +					clnk = lnkp;
          266  +				}
          267  +
          268  +				co:stdpage([lib.srv.convo.page] {
          269  +					title = 'post :: report';
          270  +					class = 'report';
          271  +					body = ui:poolstr(&co.srv.pool);
          272  +					cache = false;
          273  +				})
          274  +			else
          275  +			end
          276  +			return
          277  +		elseif post:ref() and post(0).author ~= co.who.id then
   262    278   			co:complain(403, 'forbidden', 'you cannot alter other people\'s posts')
   263    279   			return
   264    280   		elseif post:ref() and path(2):cmp(lib.str.lit 'edit') then
   265    281   			if not co:assertpow('edit') then return end
   266    282   			if meth_get(meth) then
   267    283   				lib.render.compose(co, post.ptr, nil)
   268    284   				return
................................................................................
   289    305   				else
   290    306   					conf = data.view.confirm {
   291    307   						title =  'cancel retweet';
   292    308   						query =  'are you sure you want to undo this retweet?';
   293    309   						cancel = '/';
   294    310   					}
   295    311   				end
   296         -				var fr = co.srv.pool:frame()
   297    312   				var body = conf:poolstr(&co.srv.pool) --defer body:free()
   298    313   				co:stdpage([lib.srv.convo.page] {
   299    314   					title =  'post :: delete';
   300    315   					class =  'query';
   301    316   					body = body; cache = false;
   302    317   				})
   303         -				co.srv.pool:reset(fr)
   304    318   				return
   305    319   			elseif meth == method.post then
   306    320   				var act = co:ppostv('act')
   307         -				if act:cmp( 'confirm') then
          321  +				if act:cmp('confirm') then
   308    322   					if post:ref() then
   309         -						post(0).source:post_destroy(post(0).id)
          323  +						post().source:post_destroy(post().id)
   310    324   					elseif rt.kind ~= 0 then
   311    325   						co.srv:post_act_cancel(pid)
   312    326   					end
   313    327   					co:reroute('/') -- TODO maybe return to parent or conversation if possible
   314    328   					return
   315    329   				else goto badop end
   316    330   			end
................................................................................
   480    494   			path(1):cmp('brand')
   481    495   		) then goto nopriv
   482    496   
   483    497   		elseif not co.who.rights.powers.account() and (
   484    498   			path(1):cmp('profile') or
   485    499   			path(1):cmp('sec') or
   486    500   			path(1):cmp('avi') or
   487         -			path(1):cmp('ui')
          501  +			path(1):cmp('ui') or
          502  +			path(1):cmp('circles')
   488    503   		) then goto nopriv
   489    504   
   490    505   		elseif not co.who.rights.powers:affect_users() and (
   491    506   			path(1):cmp(lib.str.lit 'users')
   492    507   		) then goto nopriv end
   493    508   	end
   494    509   
   495    510   	if meth == method.post and path.ct >= 1 then
   496    511   		var user_refresh = false var fail = false
   497         -		if path(1):cmp(lib.str.lit 'profile') then
          512  +		if path(1):cmp('profile') then
   498    513   			lib.dbg('updating profile')
   499    514   			co.who.bio = co:postv('bio')._0
   500    515   			co.who.nym = co:postv('nym')._0
   501    516   			if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end
   502    517   			if co.who.nym ~= nil and @co.who.nym == 0 then co.who.nym = nil end
   503    518   			co.who.source:actor_save(co.who)
   504    519   
................................................................................
   519    534   					co.ui_hue = nhue
   520    535   				end
   521    536   			end
   522    537   			if resethue then
   523    538   				co.srv:actor_conf_int_reset(co.who.id, 'ui-accent')
   524    539   				co.ui_hue = co.srv.cfg.ui_hue
   525    540   			end
          541  +
          542  +			var aclfollow = co:ppostv('acl-follow')
          543  +			var aclfollowreq = co:ppostv('acl-follow-req')
          544  +			if aclfollow:ref() and aclfollow.ct > 0 then
          545  +				co.srv:actor_conf_str_set(co.who.id, 'acl-follow', aclfollow)
          546  +			end
          547  +			if aclfollowreq:ref() and aclfollowreq.ct > 0 then
          548  +				co.srv:actor_conf_str_set(co.who.id, 'acl-follow-req', aclfollowreq)
          549  +			end
   526    550   
   527    551   			msg = 'profile changes saved'
   528    552   			--user_refresh = true -- not really necessary here, actually
   529    553   
   530    554   		elseif path(1):cmp('sec') then
   531    555   			if not credsec_for_uid(co, co.who.id) then return end
   532    556   		elseif path(1):cmp('avi') then
   533    557   			var act = co:ppostv('act')
   534    558   			if act:ref() and act:cmp('clear') then
   535    559   				co.who.avatarid = 0
   536    560   				co.who.source:actor_save(co.who)
   537    561   				msg = 'avatar reset to default'
   538    562   			else goto badop end
          563  +		elseif path(1):cmp('circles') then
          564  +			if meth == method.post then
          565  +				var act = co:ppostv('act')
          566  +				if path.ct == 2 and act:cmp('create') then
          567  +					var newcirc = co:ppostv('name')
          568  +					if newcirc.ct > 0 then
          569  +						co.srv:circle_create(co.who.id, newcirc)
          570  +					end
          571  +				elseif path.ct == 3 and act:cmp('del') then
          572  +					var id, ok = lib.math.shorthand.parse(path(2).ptr,path(2).ct)
          573  +					if not ok then goto e404 end
          574  +					co.srv:circle_destroy(co.who.id, id)
          575  +					co:reroute('/conf/circles')
          576  +					return
          577  +				else goto badop end
          578  +			end
   539    579   		elseif path(1):cmp('users') then
   540    580   			if path.ct >= 3 then
   541    581   				var userid, ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct)
   542    582   				if ok then
   543    583   					var usr = co.srv:actor_fetch_uid(userid)
   544    584   					if usr:ref() then --defer usr:free()
   545    585   						if not co.who:overpowers(usr.ptr) then
................................................................................
   674    714   		end
   675    715   	end
   676    716   	lib.render.conf(co,path,msg)
   677    717   	do return end
   678    718   
   679    719   	::nopriv:: do co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action') return end
   680    720   	::badop:: do co:complain(400,'bad request','the operation you have requested is not meaningful in this context') return end
          721  +	::e404:: do co:complain(404,'not found','the resource you have requested is not known to this server') return end
   681    722   end
   682    723   
   683    724   terra http.user_notices(co: &lib.srv.convo, meth: method.t)
   684    725   	if meth == method.post then
   685    726   		var act = co:ppostv('act')
   686    727   		if act:cmp('clear') then
   687    728   			co.srv:actor_conf_int_set(co.who.id, 'notice-clear-time', lib.osclock.time(nil))
................................................................................
   848    889   	-- we run through those first before giving up and parsing the URI
   849    890   	if uri.ptr == nil or uri.ptr[0] ~= @'/' then
   850    891   		co:complain(404, 'what the hell', 'how did you do that')
   851    892   	elseif uri.ct == 1 then -- root
   852    893   		if (co.srv.cfg.pol_sec == lib.srv.secmode.private or
   853    894   		   co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then
   854    895   		   http.login_form(co, meth)
   855         -		else http.timeline(co, hpath {ptr=nil}) end
          896  +		else http.timeline(co, hpath {ptr=nil,ct=0}) end
   856    897   	elseif uri.ptr[1] == @'@' then
   857    898   		http.actor_profile_xid(co, uri, meth)
   858    899   	elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
   859    900   		if not meth_get(meth) then goto wrongmeth end
   860    901   		if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
   861    902   	elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then
   862    903   		http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5})
   863    904   	elseif lib.str.ncmp('/file/', uri.ptr, 6) == 0 then
   864    905   		http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6})
   865         -	elseif uri:cmp( '/notices') then
          906  +	elseif uri:cmp('/notices') then
   866    907   		if co.aid == 0 then co:reroute('/login') return end
   867    908   		http.user_notices(co,meth)
   868         -	elseif uri:cmp( '/compose') then
          909  +	elseif uri:cmp('/compose') then
   869    910   		if co.aid == 0 then co:reroute('/login') return end
   870    911   		http.post_compose(co,meth)
   871    912   	elseif uri:cmp( '/login') then
   872    913   		if co.aid == 0
   873    914   			then http.login_form(co, meth)
   874    915   			else co:reroute('/')
   875    916   		end

Modified srv.t from [6a947d38ca] to [36e7e8aa84].

    15     15   	overlord: &srv
    16     16   	ui_cue_staff: pstring
    17     17   	ui_cue_founder: pstring
    18     18   	ui_hue: uint16
    19     19   	nranks: uint16
    20     20   	maxinvites: uint16
    21     21   	master: uint64
           22  +
           23  +	usrdef_pol_follow: pstring
           24  +	usrdef_pol_follow_req: pstring
    22     25   }
    23     26   local struct srv {
    24     27   	sources: lib.mem.ptr(lib.store.source)
    25     28   	webmgr: lib.net.mg_mgr
    26     29   	webcon: &lib.net.mg_connection
    27     30   	cfg: cfgcache
    28     31   	id: rawstring
................................................................................
    30     33   }
    31     34   
    32     35   terra cfgcache:free() -- :/
    33     36   	self.secret:free()
    34     37   	self.instance:free()
    35     38   	self.ui_cue_staff:free()
    36     39   	self.ui_cue_founder:free()
           40  +	self.usrdef_pol_follow:free()
           41  +	self.usrdef_pol_follow_req:free()
    37     42   end
    38     43   
    39     44   terra srv:post_enum_author_uid(uid: uint64, r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post))
    40     45   	var all: lib.mem.vec(lib.mem.ptr(lib.store.post)) all:init(64)
    41     46   	for i=0,self.sources.ct do var src = self.sources.ptr + i
    42     47   		if src.handle ~= nil and src.backend.timeline_instance_fetch ~= nil then
    43     48   			var lst = src:post_enum_author_uid(uid,r)
................................................................................
    66     71   		end
    67     72   		return all
    68     73   	end
    69     74   end
    70     75   
    71     76   deftlfetch('instance_fetch')
    72     77   deftlfetch('actor_fetch_uid', uint64)
           78  +deftlfetch('circle_fetch', uint64)
    73     79   
    74     80   srv.metamethods.__methodmissing = macro(function(meth, self, ...)
    75     81   	local primary, ptr, stat, simple, oid = 0,1,2,3,4
    76     82   	local tk, rt = primary
    77     83   	local expr = {...}
    78     84   	for _,f in pairs(lib.store.backend.entries) do
    79     85   		local fn = f.field or f[1]
................................................................................
   163    169   
   164    170   struct convo.page {
   165    171   	title: pstring
   166    172   	body: pstring
   167    173   	class: pstring
   168    174   	cache: bool
   169    175   }
          176  +
          177  +local usrdefs = {
          178  +	str = {
          179  +		['acl-follow'    ] = {cfgfld = 'usrdef_pol_follow', fallback = 'local'};
          180  +		['acl-follow-req'] = {cfgfld = 'usrdef_pol_follow_req', fallback = 'all'};
          181  +	};
          182  +}
          183  +
          184  +terra convo:usercfg_str(uid: uint64, setting: pstring): pstring
          185  +	var set = self.srv:actor_conf_str_get(&self.srv.pool, uid, setting)
          186  +	if not set then
          187  +		[(function()
          188  +			local q = quote return pstring.null() end
          189  +			for key, dfl in pairs(usrdefs.str) do
          190  +				local rv
          191  +				if dfl.cfgfld then
          192  +					rv = quote
          193  +						var cf = self.srv.cfg.[dfl.cfgfld]
          194  +					in terralib.select(not cf, pstring([dfl.fallback]), cf) end
          195  +				elseif dfl.lit then rv = dfl.lit end
          196  +				q = quote
          197  +					if setting:cmp([key]) then return [rv] else [q] end
          198  +				end
          199  +			end
          200  +			return q
          201  +		end)()]
          202  +	else return set end
          203  +end
   170    204   
   171    205   -- this is unfortunately necessary to work around a terra bug
   172    206   -- it can't seem to handle forward-declarations of structs in C
   173    207   
   174    208   local getpeer
   175    209   do local struct strucheader {
   176    210   		next: &lib.net.mg_connection
................................................................................
  1078   1112   			self.master = wma(0).id
  1079   1113   			wma:free()
  1080   1114   		end
  1081   1115   	end
  1082   1116   
  1083   1117   	self.ui_cue_staff = self.overlord:conf_get('ui-profile-cue-staff')
  1084   1118   	self.ui_cue_founder = self.overlord:conf_get('ui-profile-cue-master')
         1119  +
         1120  +	self.usrdef_pol_follow = self.overlord:conf_get('user-default-acl-follow')
         1121  +	self.usrdef_pol_follow_req = self.overlord:conf_get('user-default-acl-follow-req')
  1085   1122   end
  1086   1123   
  1087   1124   return {
  1088   1125   	overlord = srv;
  1089   1126   	convo = convo;
  1090   1127   	route = route;
  1091   1128   	secmode = secmode;
  1092   1129   }

Added static/gear.svg version [0623970aec].

            1  +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
            2  +<!-- Created with Inkscape (http://www.inkscape.org/) -->
            3  +
            4  +<svg
            5  +   xmlns:dc="http://purl.org/dc/elements/1.1/"
            6  +   xmlns:cc="http://creativecommons.org/ns#"
            7  +   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
            8  +   xmlns:svg="http://www.w3.org/2000/svg"
            9  +   xmlns="http://www.w3.org/2000/svg"
           10  +   xmlns:xlink="http://www.w3.org/1999/xlink"
           11  +   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
           12  +   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
           13  +   width="0.2in"
           14  +   height="0.2in"
           15  +   viewBox="0 0 5.0800002 5.0800002"
           16  +   version="1.1"
           17  +   id="svg8"
           18  +   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
           19  +   sodipodi:docname="gear.svg">
           20  +  <defs
           21  +     id="defs2">
           22  +    <linearGradient
           23  +       inkscape:collect="always"
           24  +       id="linearGradient863">
           25  +      <stop
           26  +         style="stop-color:#ffffff;stop-opacity:0.5518868"
           27  +         offset="0"
           28  +         id="stop859" />
           29  +      <stop
           30  +         style="stop-color:#ffffff;stop-opacity:0;"
           31  +         offset="1"
           32  +         id="stop861" />
           33  +    </linearGradient>
           34  +    <radialGradient
           35  +       inkscape:collect="always"
           36  +       xlink:href="#linearGradient863"
           37  +       id="radialGradient865"
           38  +       cx="2.5399754"
           39  +       cy="295.73944"
           40  +       fx="2.5399754"
           41  +       fy="295.91135"
           42  +       r="1.6620824"
           43  +       gradientTransform="matrix(2.9231276,0,0,2.7982783,-11.452033,-531.55554)"
           44  +       gradientUnits="userSpaceOnUse" />
           45  +    <radialGradient
           46  +       inkscape:collect="always"
           47  +       xlink:href="#linearGradient863"
           48  +       id="radialGradient865-3"
           49  +       cx="2.5399754"
           50  +       cy="295.73944"
           51  +       fx="2.5399754"
           52  +       fy="295.91135"
           53  +       r="1.6620824"
           54  +       gradientTransform="matrix(11.048041,0,0,10.57617,-21.779867,-3114.7234)"
           55  +       gradientUnits="userSpaceOnUse" />
           56  +    <radialGradient
           57  +       inkscape:collect="always"
           58  +       xlink:href="#linearGradient863"
           59  +       id="radialGradient980"
           60  +       gradientUnits="userSpaceOnUse"
           61  +       gradientTransform="matrix(2.9231276,0,0,2.7982783,-11.452033,-531.55554)"
           62  +       cx="4.5320868"
           63  +       cy="295.40598"
           64  +       fx="4.5320868"
           65  +       fy="295.57788"
           66  +       r="1.6620824" />
           67  +    <radialGradient
           68  +       inkscape:collect="always"
           69  +       xlink:href="#linearGradient863"
           70  +       id="radialGradient980-9"
           71  +       gradientUnits="userSpaceOnUse"
           72  +       gradientTransform="matrix(11.048041,0,0,10.57617,-45.140697,-3114.3717)"
           73  +       cx="4.5320868"
           74  +       cy="295.40598"
           75  +       fx="4.5320868"
           76  +       fy="295.57788"
           77  +       r="1.6620824" />
           78  +    <radialGradient
           79  +       inkscape:collect="always"
           80  +       xlink:href="#linearGradient863"
           81  +       id="radialGradient1008"
           82  +       gradientUnits="userSpaceOnUse"
           83  +       gradientTransform="matrix(2.9231276,0,0,2.7982783,-11.452033,-531.55554)"
           84  +       cx="5.1548281"
           85  +       cy="294.85077"
           86  +       fx="5.1548281"
           87  +       fy="295.02267"
           88  +       r="1.6620824" />
           89  +    <radialGradient
           90  +       inkscape:collect="always"
           91  +       xlink:href="#linearGradient863"
           92  +       id="radialGradient1028"
           93  +       gradientUnits="userSpaceOnUse"
           94  +       gradientTransform="matrix(2.7672014,0,0,2.6490119,-10.744057,-487.4606)"
           95  +       cx="4.5320868"
           96  +       cy="295.40598"
           97  +       fx="4.5320868"
           98  +       fy="295.57788"
           99  +       r="1.6620824" />
          100  +    <radialGradient
          101  +       inkscape:collect="always"
          102  +       xlink:href="#linearGradient863"
          103  +       id="radialGradient1031"
          104  +       gradientUnits="userSpaceOnUse"
          105  +       gradientTransform="matrix(11.048041,0,0,10.57617,-43.283274,-3112.3484)"
          106  +       cx="4.5320868"
          107  +       cy="295.40598"
          108  +       fx="4.5320868"
          109  +       fy="295.57788"
          110  +       r="1.6620824" />
          111  +  </defs>
          112  +  <sodipodi:namedview
          113  +     id="base"
          114  +     pagecolor="#2f000f"
          115  +     bordercolor="#666666"
          116  +     borderopacity="1.0"
          117  +     inkscape:pageopacity="0"
          118  +     inkscape:pageshadow="2"
          119  +     inkscape:zoom="11.2"
          120  +     inkscape:cx="5.6507928"
          121  +     inkscape:cy="22.571234"
          122  +     inkscape:document-units="mm"
          123  +     inkscape:current-layer="layer1"
          124  +     showgrid="false"
          125  +     units="in"
          126  +     inkscape:window-width="1920"
          127  +     inkscape:window-height="1042"
          128  +     inkscape:window-x="0"
          129  +     inkscape:window-y="38"
          130  +     inkscape:window-maximized="0" />
          131  +  <metadata
          132  +     id="metadata5">
          133  +    <rdf:RDF>
          134  +      <cc:Work
          135  +         rdf:about="">
          136  +        <dc:format>image/svg+xml</dc:format>
          137  +        <dc:type
          138  +           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
          139  +        <dc:title></dc:title>
          140  +      </cc:Work>
          141  +    </rdf:RDF>
          142  +  </metadata>
          143  +  <g
          144  +     inkscape:label="Layer 1"
          145  +     inkscape:groupmode="layer"
          146  +     id="layer1"
          147  +     transform="translate(0,-291.91998)">
          148  +    <path
          149  +       style="opacity:1;vector-effect:none;fill:url(#radialGradient1031);fill-opacity:1;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          150  +       d="M 12.113281 2.0234375 A 4.1239589 4.1239589 0 0 0 11.5625 2.1816406 A 4.1239589 4.1239589 0 0 0 11.044922 2.4238281 L 11.435547 3.4628906 A 3.0271613 3.0271613 0 0 0 10.544922 4.2949219 L 9.5332031 3.8359375 A 4.1239589 4.1239589 0 0 0 9.0625 4.875 L 10.068359 5.3320312 A 3.0271613 3.0271613 0 0 0 10.029297 6.5527344 L 8.9921875 6.9414062 A 4.1239589 4.1239589 0 0 0 9.1503906 7.4921875 A 4.1239589 4.1239589 0 0 0 9.3925781 8.0117188 L 10.429688 7.6210938 A 3.0271613 3.0271613 0 0 0 11.263672 8.5117188 L 10.804688 9.5234375 A 4.1239589 4.1239589 0 0 0 11.84375 9.9921875 L 12.302734 8.984375 A 3.0271613 3.0271613 0 0 0 13.521484 9.0253906 L 13.910156 10.0625 A 4.1239589 4.1239589 0 0 0 14.458984 9.90625 A 4.1239589 4.1239589 0 0 0 14.978516 9.6640625 L 14.587891 8.625 A 3.0271613 3.0271613 0 0 0 15.478516 7.7910156 L 16.490234 8.2519531 A 4.1239589 4.1239589 0 0 0 16.958984 7.2109375 L 15.953125 6.7539062 A 3.0271613 3.0271613 0 0 0 15.994141 5.5351562 L 17.03125 5.1464844 A 4.1239589 4.1239589 0 0 0 16.875 4.5957031 A 4.1239589 4.1239589 0 0 0 16.630859 4.0761719 L 15.591797 4.4667969 A 3.0271613 3.0271613 0 0 0 14.759766 3.5761719 L 15.21875 2.5664062 A 4.1239589 4.1239589 0 0 0 14.179688 2.09375 L 13.720703 3.1015625 A 3.0271613 3.0271613 0 0 0 12.501953 3.0605469 L 12.113281 2.0234375 z M 13.074219 4.421875 A 1.6230732 1.5835511 84.711522 0 1 14.494141 5.4765625 A 1.6230732 1.5835511 84.711522 0 1 13.572266 7.5644531 A 1.6230732 1.5835511 84.711522 0 1 11.529297 6.609375 A 1.6230732 1.5835511 84.711522 0 1 12.453125 4.5214844 A 1.6230732 1.5835511 84.711522 0 1 12.916016 4.4238281 A 1.6230732 1.5835511 84.711522 0 1 13.074219 4.421875 z "
          151  +       transform="matrix(0.26458333,0,0,0.26458333,0,291.91998)"
          152  +       id="path1024" />
          153  +    <path
          154  +       style="opacity:1;vector-effect:none;fill:url(#radialGradient1028);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          155  +       d="m 1.8205469,293.82898 a 1.2578638,1.2578638 0 0 0 -0.1824712,0.0142 v 0.29596 a 0.96596175,0.96596175 0 0 0 -0.359072,0.14921 l -0.2093772,-0.20938 a 1.2578638,1.2578638 0 0 0 -0.25731862,0.25781 l 0.20839872,0.2084 a 0.96596175,0.96596175 0 0 0 -0.14871646,0.35956 H 0.57749238 a 1.2578638,1.2578638 0 0 0 -0.0151652,0.18198 1.2578638,1.2578638 0 0 0 0.014676,0.18247 h 0.29498693 a 0.96596175,0.96596175 0 0 0 0.14920569,0.35956 l -0.20839876,0.20791 a 1.2578638,1.2578638 0 0 0 0.25731866,0.25781 l 0.2083988,-0.2084 a 0.96596175,0.96596175 0 0 0 0.3595612,0.14921 v 0.2945 a 1.2578638,1.2578638 0 0 0 0.1824712,0.0147 1.2578638,1.2578638 0 0 0 0.1814929,-0.0142 v -0.29547 a 0.96596175,0.96596175 0 0 0 0.3595612,-0.14872 l 0.2088879,0.20889 a 1.2578638,1.2578638 0 0 0 0.2573188,-0.25732 l -0.208399,-0.2084 a 0.96596175,0.96596175 0 0 0 0.1487165,-0.36005 h 0.2954763 a 1.2578638,1.2578638 0 0 0 0.014675,-0.18247 1.2578638,1.2578638 0 0 0 -0.014187,-0.18198 H 2.7681252 a 0.96596175,0.96596175 0 0 0 -0.1487165,-0.35907 l 0.2088881,-0.20938 a 1.2578638,1.2578638 0 0 0 -0.2578079,-0.25732 l -0.2083987,0.2084 a 0.96596175,0.96596175 0 0 0 -0.3600504,-0.14872 v -0.29498 a 1.2578638,1.2578638 0 0 0 -0.1814929,-0.0147 z m 0,0.67656 a 0.58097694,0.58097694 0 0 1 0.5806792,0.58117 0.58097694,0.58097694 0 0 1 -0.5806792,0.58068 0.58097694,0.58097694 0 0 1 -0.5811683,-0.58068 0.58097694,0.58097694 0 0 1 0.5811683,-0.58117 z"
          156  +       id="path1020"
          157  +       inkscape:connector-curvature="0" />
          158  +    <path
          159  +       id="circle917"
          160  +       style="opacity:1;vector-effect:none;fill:url(#radialGradient1008);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;font-variant-east_asian:normal"
          161  +       d="m 3.6161875,293.51895 a 0.1735429,0.1735429 0 0 1 -0.1735429,0.17354 0.1735429,0.1735429 0 0 1 -0.1735429,-0.17354 0.1735429,0.1735429 0 0 1 0.1735429,-0.17354 0.1735429,0.1735429 0 0 1 0.1735429,0.17354 z m -1.5573776,1.56775 a 0.23850755,0.23850755 0 0 1 -0.2385075,0.23851 0.23850755,0.23850755 0 0 1 -0.2385076,-0.23851 0.23850755,0.23850755 0 0 1 0.2385076,-0.23851 0.23850755,0.23850755 0 0 1 0.2385075,0.23851 z" />
          162  +  </g>
          163  +</svg>

Added static/logo.svg version [0ada266140].

            1  +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
            2  +<!-- Created with Inkscape (http://www.inkscape.org/) -->
            3  +
            4  +<svg
            5  +   xmlns:dc="http://purl.org/dc/elements/1.1/"
            6  +   xmlns:cc="http://creativecommons.org/ns#"
            7  +   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
            8  +   xmlns:svg="http://www.w3.org/2000/svg"
            9  +   xmlns="http://www.w3.org/2000/svg"
           10  +   xmlns:xlink="http://www.w3.org/1999/xlink"
           11  +   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
           12  +   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
           13  +   width="1in"
           14  +   height="1in"
           15  +   viewBox="0 0 25.4 25.400001"
           16  +   version="1.1"
           17  +   id="svg8"
           18  +   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
           19  +   sodipodi:docname="logo.svg">
           20  +  <defs
           21  +     id="defs2">
           22  +    <linearGradient
           23  +       id="linearGradient920"
           24  +       inkscape:collect="always">
           25  +      <stop
           26  +         id="stop916"
           27  +         offset="0"
           28  +         style="stop-color:#f8bac8;stop-opacity:1" />
           29  +      <stop
           30  +         id="stop918"
           31  +         offset="1"
           32  +         style="stop-color:#ea2b55;stop-opacity:0" />
           33  +    </linearGradient>
           34  +    <linearGradient
           35  +       id="linearGradient3597"
           36  +       inkscape:collect="always">
           37  +      <stop
           38  +         id="stop3593"
           39  +         offset="0"
           40  +         style="stop-color:#bc0087;stop-opacity:1" />
           41  +      <stop
           42  +         id="stop3595"
           43  +         offset="1"
           44  +         style="stop-color:#6f004e;stop-opacity:1" />
           45  +    </linearGradient>
           46  +    <marker
           47  +       inkscape:stockid="Arrow1Lstart"
           48  +       orient="auto"
           49  +       refY="0.0"
           50  +       refX="0.0"
           51  +       id="marker3321"
           52  +       style="overflow:visible"
           53  +       inkscape:isstock="true">
           54  +      <path
           55  +         id="path3319"
           56  +         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
           57  +         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
           58  +         transform="scale(0.8) translate(12.5,0)" />
           59  +    </marker>
           60  +    <marker
           61  +       inkscape:stockid="Arrow1Lstart"
           62  +       orient="auto"
           63  +       refY="0.0"
           64  +       refX="0.0"
           65  +       id="marker3281"
           66  +       style="overflow:visible"
           67  +       inkscape:isstock="true">
           68  +      <path
           69  +         id="path3279"
           70  +         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
           71  +         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
           72  +         transform="scale(0.8) translate(12.5,0)" />
           73  +    </marker>
           74  +    <marker
           75  +       inkscape:stockid="Arrow1Lstart"
           76  +       orient="auto"
           77  +       refY="0.0"
           78  +       refX="0.0"
           79  +       id="marker3247"
           80  +       style="overflow:visible"
           81  +       inkscape:isstock="true">
           82  +      <path
           83  +         id="path3245"
           84  +         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
           85  +         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
           86  +         transform="scale(0.8) translate(12.5,0)" />
           87  +    </marker>
           88  +    <marker
           89  +       inkscape:stockid="Arrow1Lstart"
           90  +       orient="auto"
           91  +       refY="0.0"
           92  +       refX="0.0"
           93  +       id="marker3219"
           94  +       style="overflow:visible"
           95  +       inkscape:isstock="true">
           96  +      <path
           97  +         id="path3217"
           98  +         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
           99  +         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
          100  +         transform="scale(0.8) translate(12.5,0)" />
          101  +    </marker>
          102  +    <marker
          103  +       inkscape:stockid="Arrow1Lstart"
          104  +       orient="auto"
          105  +       refY="0.0"
          106  +       refX="0.0"
          107  +       id="marker3197"
          108  +       style="overflow:visible"
          109  +       inkscape:isstock="true">
          110  +      <path
          111  +         id="path3195"
          112  +         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
          113  +         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
          114  +         transform="scale(0.8) translate(12.5,0)" />
          115  +    </marker>
          116  +    <marker
          117  +       inkscape:stockid="Arrow1Lstart"
          118  +       orient="auto"
          119  +       refY="0.0"
          120  +       refX="0.0"
          121  +       id="Arrow1Lstart"
          122  +       style="overflow:visible"
          123  +       inkscape:isstock="true">
          124  +      <path
          125  +         id="path2919"
          126  +         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
          127  +         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
          128  +         transform="scale(0.8) translate(12.5,0)" />
          129  +    </marker>
          130  +    <linearGradient
          131  +       inkscape:collect="always"
          132  +       id="linearGradient2907">
          133  +      <stop
          134  +         style="stop-color:#4d003a;stop-opacity:0.12264151"
          135  +         offset="0"
          136  +         id="stop2903" />
          137  +      <stop
          138  +         style="stop-color:#4d003a;stop-opacity:0.91509432"
          139  +         offset="1"
          140  +         id="stop2905" />
          141  +    </linearGradient>
          142  +    <linearGradient
          143  +       id="linearGradient2893"
          144  +       inkscape:collect="always">
          145  +      <stop
          146  +         id="stop2889"
          147  +         offset="0"
          148  +         style="stop-color:#e00092;stop-opacity:1;" />
          149  +      <stop
          150  +         id="stop2891"
          151  +         offset="1"
          152  +         style="stop-color:#e00092;stop-opacity:0.01886792" />
          153  +    </linearGradient>
          154  +    <linearGradient
          155  +       inkscape:collect="always"
          156  +       id="linearGradient1775">
          157  +      <stop
          158  +         style="stop-color:#fef5f7;stop-opacity:1;"
          159  +         offset="0"
          160  +         id="stop1771" />
          161  +      <stop
          162  +         style="stop-color:#ea2b55;stop-opacity:0"
          163  +         offset="1"
          164  +         id="stop1773" />
          165  +    </linearGradient>
          166  +    <linearGradient
          167  +       inkscape:collect="always"
          168  +       id="linearGradient929">
          169  +      <stop
          170  +         style="stop-color:#e00092;stop-opacity:1;"
          171  +         offset="0"
          172  +         id="stop925" />
          173  +      <stop
          174  +         style="stop-color:#e00092;stop-opacity:0"
          175  +         offset="1"
          176  +         id="stop927" />
          177  +    </linearGradient>
          178  +    <linearGradient
          179  +       inkscape:collect="always"
          180  +       id="linearGradient871">
          181  +      <stop
          182  +         style="stop-color:#fff8fd;stop-opacity:1;"
          183  +         offset="0"
          184  +         id="stop867" />
          185  +      <stop
          186  +         style="stop-color:#ff85dc;stop-opacity:1"
          187  +         offset="1"
          188  +         id="stop869" />
          189  +    </linearGradient>
          190  +    <linearGradient
          191  +       inkscape:collect="always"
          192  +       id="linearGradient863">
          193  +      <stop
          194  +         style="stop-color:#ff0ebb;stop-opacity:1"
          195  +         offset="0"
          196  +         id="stop859" />
          197  +      <stop
          198  +         style="stop-color:#98006b;stop-opacity:1"
          199  +         offset="1"
          200  +         id="stop861" />
          201  +    </linearGradient>
          202  +    <linearGradient
          203  +       inkscape:collect="always"
          204  +       id="linearGradient855">
          205  +      <stop
          206  +         style="stop-color:#ff1ab2;stop-opacity:1"
          207  +         offset="0"
          208  +         id="stop851" />
          209  +      <stop
          210  +         style="stop-color:#5e003e;stop-opacity:1"
          211  +         offset="1"
          212  +         id="stop853" />
          213  +    </linearGradient>
          214  +    <linearGradient
          215  +       inkscape:collect="always"
          216  +       xlink:href="#linearGradient855"
          217  +       id="linearGradient857"
          218  +       x1="204.78238"
          219  +       y1="192.05012"
          220  +       x2="215.23915"
          221  +       y2="192.05012"
          222  +       gradientUnits="userSpaceOnUse"
          223  +       gradientTransform="matrix(1.1660945,0,0,1.1660945,-34.675674,-31.692532)" />
          224  +    <radialGradient
          225  +       inkscape:collect="always"
          226  +       xlink:href="#linearGradient871"
          227  +       id="radialGradient873"
          228  +       cx="12.700097"
          229  +       cy="284.29999"
          230  +       fx="12.700097"
          231  +       fy="284.29999"
          232  +       r="4.7926919"
          233  +       gradientTransform="matrix(1.5582358,0,0,1.558162,-7.0897352,-158.99167)"
          234  +       gradientUnits="userSpaceOnUse" />
          235  +    <linearGradient
          236  +       inkscape:collect="always"
          237  +       xlink:href="#linearGradient2893"
          238  +       id="linearGradient931"
          239  +       x1="10.734375"
          240  +       y1="68.553711"
          241  +       x2="74.320656"
          242  +       y2="68.553711"
          243  +       gradientUnits="userSpaceOnUse"
          244  +       gradientTransform="matrix(0.30281136,0,0,0.30279703,-1.8350659,269.43243)" />
          245  +    <linearGradient
          246  +       inkscape:collect="always"
          247  +       xlink:href="#linearGradient929"
          248  +       id="linearGradient935"
          249  +       gradientUnits="userSpaceOnUse"
          250  +       gradientTransform="matrix(-0.30281136,0,0,-0.30279703,27.235067,299.16751)"
          251  +       x1="10.734375"
          252  +       y1="68.553711"
          253  +       x2="72.836235"
          254  +       y2="68.553711" />
          255  +    <radialGradient
          256  +       inkscape:collect="always"
          257  +       xlink:href="#linearGradient1775"
          258  +       id="radialGradient1777"
          259  +       cx="35.70248"
          260  +       cy="275.61121"
          261  +       fx="35.70248"
          262  +       fy="275.61121"
          263  +       r="5.0648633"
          264  +       gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)"
          265  +       gradientUnits="userSpaceOnUse" />
          266  +    <radialGradient
          267  +       inkscape:collect="always"
          268  +       xlink:href="#linearGradient1775"
          269  +       id="radialGradient1785"
          270  +       cx="54.650433"
          271  +       cy="276.4209"
          272  +       fx="55.498787"
          273  +       fy="276.4209"
          274  +       r="5.0648627"
          275  +       gradientTransform="matrix(-2.5056647,-4.662863e-8,0,-0.4043156,191.5861,387.46216)"
          276  +       gradientUnits="userSpaceOnUse" />
          277  +    <radialGradient
          278  +       inkscape:collect="always"
          279  +       xlink:href="#linearGradient1775"
          280  +       id="radialGradient1787"
          281  +       gradientUnits="userSpaceOnUse"
          282  +       gradientTransform="matrix(-2.5354014,-4.7182009e-8,0,-0.40911394,193.02586,388.9687)"
          283  +       cx="54.650433"
          284  +       cy="276.4209"
          285  +       fx="55.498787"
          286  +       fy="276.4209"
          287  +       r="5.0648627" />
          288  +    <radialGradient
          289  +       inkscape:collect="always"
          290  +       xlink:href="#linearGradient1775"
          291  +       id="radialGradient1789"
          292  +       gradientUnits="userSpaceOnUse"
          293  +       gradientTransform="matrix(-2.5354014,-4.7182009e-8,0,-0.40911394,193.02586,388.9687)"
          294  +       cx="54.650433"
          295  +       cy="276.4209"
          296  +       fx="55.498787"
          297  +       fy="276.4209"
          298  +       r="5.0648627" />
          299  +    <radialGradient
          300  +       inkscape:collect="always"
          301  +       xlink:href="#linearGradient1775"
          302  +       id="radialGradient1791"
          303  +       gradientUnits="userSpaceOnUse"
          304  +       gradientTransform="matrix(-2.5056647,-4.662863e-8,0,-0.4043156,191.5861,387.46216)"
          305  +       cx="54.650433"
          306  +       cy="276.4209"
          307  +       fx="55.498787"
          308  +       fy="276.4209"
          309  +       r="5.0648627" />
          310  +    <radialGradient
          311  +       inkscape:collect="always"
          312  +       xlink:href="#linearGradient1775"
          313  +       id="radialGradient1793"
          314  +       gradientUnits="userSpaceOnUse"
          315  +       gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)"
          316  +       cx="35.70248"
          317  +       cy="275.61121"
          318  +       fx="35.70248"
          319  +       fy="275.61121"
          320  +       r="5.0648633" />
          321  +    <radialGradient
          322  +       inkscape:collect="always"
          323  +       xlink:href="#linearGradient1775"
          324  +       id="radialGradient1795"
          325  +       gradientUnits="userSpaceOnUse"
          326  +       gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)"
          327  +       cx="35.70248"
          328  +       cy="275.61121"
          329  +       fx="35.70248"
          330  +       fy="275.61121"
          331  +       r="5.0648633" />
          332  +    <radialGradient
          333  +       inkscape:collect="always"
          334  +       xlink:href="#linearGradient1775"
          335  +       id="radialGradient1797"
          336  +       gradientUnits="userSpaceOnUse"
          337  +       gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)"
          338  +       cx="35.70248"
          339  +       cy="275.61121"
          340  +       fx="35.70248"
          341  +       fy="275.61121"
          342  +       r="5.0648633" />
          343  +    <linearGradient
          344  +       inkscape:collect="always"
          345  +       xlink:href="#linearGradient855"
          346  +       id="linearGradient1805"
          347  +       gradientUnits="userSpaceOnUse"
          348  +       gradientTransform="matrix(1.1660945,0,0,1.1660945,-68.579304,2.5050309)"
          349  +       x1="204.78238"
          350  +       y1="192.05012"
          351  +       x2="215.23915"
          352  +       y2="192.05012" />
          353  +    <linearGradient
          354  +       inkscape:collect="always"
          355  +       xlink:href="#linearGradient863"
          356  +       id="linearGradient1807"
          357  +       gradientUnits="userSpaceOnUse"
          358  +       gradientTransform="matrix(1.1660945,0,0,1.1660945,-50.264227,-46.989234)"
          359  +       x1="20.094147"
          360  +       y1="284.52951"
          361  +       x2="12.70009"
          362  +       y2="277.13544" />
          363  +    <linearGradient
          364  +       inkscape:collect="always"
          365  +       xlink:href="#linearGradient855"
          366  +       id="linearGradient1812"
          367  +       gradientUnits="userSpaceOnUse"
          368  +       gradientTransform="matrix(0.94368798,0.94364332,-0.94368798,0.94364332,-4.2492401,-95.102051)"
          369  +       x1="204.78238"
          370  +       y1="192.05012"
          371  +       x2="215.23915"
          372  +       y2="192.05012" />
          373  +    <radialGradient
          374  +       inkscape:collect="always"
          375  +       xlink:href="#linearGradient1775"
          376  +       id="radialGradient2879"
          377  +       gradientUnits="userSpaceOnUse"
          378  +       gradientTransform="matrix(-2.5056647,-4.662863e-8,0,-0.4043156,191.5861,387.46216)"
          379  +       cx="54.650433"
          380  +       cy="276.4209"
          381  +       fx="55.498787"
          382  +       fy="276.4209"
          383  +       r="5.0648627" />
          384  +    <radialGradient
          385  +       inkscape:collect="always"
          386  +       xlink:href="#linearGradient1775"
          387  +       id="radialGradient2901"
          388  +       gradientUnits="userSpaceOnUse"
          389  +       gradientTransform="matrix(-2.5354014,-4.7182009e-8,0,-0.40911394,193.02586,388.9687)"
          390  +       cx="54.650433"
          391  +       cy="276.4209"
          392  +       fx="55.498787"
          393  +       fy="276.4209"
          394  +       r="5.0648627" />
          395  +    <radialGradient
          396  +       inkscape:collect="always"
          397  +       xlink:href="#linearGradient2907"
          398  +       id="radialGradient2909"
          399  +       cx="210.21639"
          400  +       cy="192.25669"
          401  +       fx="210.21639"
          402  +       fy="192.25669"
          403  +       r="5.949986"
          404  +       gradientTransform="matrix(1.1727006,0,0,1.1727006,-36.505524,-33.40402)"
          405  +       gradientUnits="userSpaceOnUse" />
          406  +    <linearGradient
          407  +       inkscape:collect="always"
          408  +       xlink:href="#linearGradient3597"
          409  +       id="linearGradient2917"
          410  +       x1="15.985148"
          411  +       y1="289.71997"
          412  +       x2="7.5198202"
          413  +       y2="281.35651"
          414  +       gradientUnits="userSpaceOnUse"
          415  +       gradientTransform="matrix(1.1727283,0,0,1.1726728,-2.1931076,-49.432382)" />
          416  +    <radialGradient
          417  +       inkscape:collect="always"
          418  +       xlink:href="#linearGradient1775"
          419  +       id="radialGradient904"
          420  +       cx="203.03343"
          421  +       cy="185.0731"
          422  +       fx="203.03343"
          423  +       fy="185.0731"
          424  +       r="7.9787183"
          425  +       gradientUnits="userSpaceOnUse"
          426  +       gradientTransform="matrix(1.484981,0,0,1.484981,-99.129567,-90.419502)" />
          427  +    <radialGradient
          428  +       inkscape:collect="always"
          429  +       xlink:href="#linearGradient920"
          430  +       id="radialGradient914"
          431  +       gradientUnits="userSpaceOnUse"
          432  +       gradientTransform="matrix(1.484981,0,0,1.484981,-501.1906,-492.48021)"
          433  +       cx="203.03343"
          434  +       cy="185.0731"
          435  +       fx="203.03343"
          436  +       fy="185.0731"
          437  +       r="7.9787183" />
          438  +  </defs>
          439  +  <sodipodi:namedview
          440  +     id="base"
          441  +     pagecolor="#48032c"
          442  +     bordercolor="#666666"
          443  +     borderopacity="1.0"
          444  +     inkscape:pageopacity="0"
          445  +     inkscape:pageshadow="2"
          446  +     inkscape:zoom="4.0000001"
          447  +     inkscape:cx="52.332667"
          448  +     inkscape:cy="84.328551"
          449  +     inkscape:document-units="mm"
          450  +     inkscape:current-layer="layer1"
          451  +     showgrid="false"
          452  +     units="in"
          453  +     inkscape:window-width="1920"
          454  +     inkscape:window-height="1042"
          455  +     inkscape:window-x="0"
          456  +     inkscape:window-y="38"
          457  +     inkscape:window-maximized="0"
          458  +     inkscape:snap-intersection-paths="true"
          459  +     inkscape:object-paths="true"
          460  +     inkscape:snap-global="true" />
          461  +  <metadata
          462  +     id="metadata5">
          463  +    <rdf:RDF>
          464  +      <cc:Work
          465  +         rdf:about="">
          466  +        <dc:format>image/svg+xml</dc:format>
          467  +        <dc:type
          468  +           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
          469  +        <dc:title />
          470  +      </cc:Work>
          471  +    </rdf:RDF>
          472  +  </metadata>
          473  +  <g
          474  +     inkscape:label="Layer 1"
          475  +     inkscape:groupmode="layer"
          476  +     id="layer1"
          477  +     transform="translate(0,-271.59998)">
          478  +    <rect
          479  +       transform="matrix(0.70710678,-0.70710678,-0.70710678,-0.70710678,0,0)"
          480  +       y="-217.65018"
          481  +       x="-199.68983"
          482  +       height="15.957437"
          483  +       width="15.957437"
          484  +       id="rect912"
          485  +       style="opacity:1;vector-effect:none;fill:url(#radialGradient914);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
          486  +    <rect
          487  +       style="opacity:1;vector-effect:none;fill:url(#linearGradient1805);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          488  +       id="rect1801"
          489  +       width="12.193584"
          490  +       height="12.193584"
          491  +       x="170.21625"
          492  +       y="220.35686"
          493  +       transform="rotate(45)" />
          494  +    <path
          495  +       inkscape:connector-curvature="0"
          496  +       id="path1803"
          497  +       style="fill:none;stroke:url(#linearGradient1807);stroke-width:2.0650003;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
          498  +       d="m -35.454712,279.49101 v 10.08081 m 2e-6,-10.08081 5.040403,5.04041 -5.040403,5.0404 -5.040405,-5.0404 z" />
          499  +    <path
          500  +       id="path1693"
          501  +       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          502  +       d="m 34.893237,267.49632 v -3.19965 m -1.685,-0.0372 h 3.369999 v 3.27405 h -3.369999 z" />
          503  +    <path
          504  +       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          505  +       d="m 37.184806,267.53352 v -3.27405 h 3.369999 v 3.27405"
          506  +       id="rect1709"
          507  +       inkscape:connector-curvature="0"
          508  +       sodipodi:nodetypes="cccc" />
          509  +    <path
          510  +       id="path1717"
          511  +       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          512  +       d="m 41.180522,265.89649 h 3.331704 m 0.01915,-1.63702 v 3.27405 h -3.369999 v -3.27405" />
          513  +    <path
          514  +       id="path1723"
          515  +       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          516  +       d="m 45.157089,265.89649 h 3.331704 m -3.350851,1.63703 v -3.27405 h 3.369999 v 3.27405" />
          517  +    <path
          518  +       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          519  +       d="m 49.11451,267.53352 v -3.27405 h 3.369999 v 3.27405"
          520  +       id="rect1727"
          521  +       inkscape:connector-curvature="0"
          522  +       sodipodi:nodetypes="cccc" />
          523  +    <path
          524  +       id="path1737"
          525  +       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          526  +       d="m 54.776077,267.49632 v -3.19965 m -1.684997,3.23685 v -3.27405 h 3.369999 v 3.27405" />
          527  +    <rect
          528  +       style="opacity:1;vector-effect:none;fill:url(#radialGradient2909);fill-opacity:1;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          529  +       id="rect2877"
          530  +       width="13.955104"
          531  +       height="13.955101"
          532  +       x="203.03781"
          533  +       y="185.07791"
          534  +       transform="matrix(0.7071235,0.70709006,-0.7071235,0.70709006,0,0)" />
          535  +    <path
          536  +       style="opacity:1;vector-effect:none;fill:url(#linearGradient1812);fill-opacity:1;stroke:url(#linearGradient2917);stroke-width:0.07055556;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          537  +       d="m 12.699879,274.43273 -9.8679832,9.86752 9.8679832,9.86692 9.867983,-9.86692 z m -0.0012,2.12077 7.441943,7.441 -7.441944,7.43864 -7.4389871,-7.43864 6.6056641,-6.60535 z m -1.179899,4.52421 -2.9169251,2.91679 2.9169251,2.91679 z m 2.362757,0 v 5.83121 l 2.916924,-2.91442 z"
          538  +       id="rect836"
          539  +       inkscape:connector-curvature="0" />
          540  +    <path
          541  +       id="path845"
          542  +       style="fill:none;stroke:url(#radialGradient873);stroke-width:0.41804168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
          543  +       d="m 12.700008,278.22531 v 11.53678 m 3e-6,-11.53678 5.768659,5.76839 -5.768659,5.76839 -5.7686618,-5.76839 z"
          544  +       inkscape:connector-curvature="0" />
          545  +    <path
          546  +       style="opacity:1;vector-effect:none;fill:url(#linearGradient931);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          547  +       d="M 24.425539,283.38063 12.699879,295.10395 2.3593445,284.77218 1.4154249,285.71666 12.699879,296.99998 25.370051,284.32449 Z"
          548  +       id="path883"
          549  +       inkscape:connector-curvature="0"
          550  +       sodipodi:nodetypes="ccccccc" />
          551  +    <path
          552  +       sodipodi:nodetypes="ccccccc"
          553  +       inkscape:connector-curvature="0"
          554  +       id="path933"
          555  +       d="M 0.9744608,285.21933 12.700121,273.496 23.040656,283.82778 23.984576,282.8833 12.700121,271.59998 0.0299491,284.27546 Z"
          556  +       style="opacity:1;vector-effect:none;fill:url(#linearGradient935);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
          557  +    <g
          558  +       id="g1764"
          559  +       transform="matrix(0.50352276,-0.50349892,0.50352276,0.50349892,-154.92304,162.62301)"
          560  +       style="stroke:url(#radialGradient1777);stroke-width:0.28087056;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none">
          561  +      <path
          562  +         inkscape:connector-curvature="0"
          563  +         id="path1749"
          564  +         style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:url(#radialGradient1793);stroke-width:0.28087056;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          565  +         d="m 34.056591,276.36215 v -1.50187 m -0.848355,-0.0175 h 1.696711 v 1.5368 h -1.696711 z" />
          566  +      <path
          567  +         style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:url(#radialGradient1795);stroke-width:0.28087056;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          568  +         d="m 37.327005,276.37961 v -1.5368 h 1.69671 v 1.5368"
          569  +         id="path1751"
          570  +         inkscape:connector-curvature="0"
          571  +         sodipodi:nodetypes="cccc" />
          572  +      <path
          573  +         inkscape:connector-curvature="0"
          574  +         id="path1753"
          575  +         style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:url(#radialGradient1797);stroke-width:0.28087056;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          576  +         d="m 41.455454,275.61121 h 1.677431 m 0.0096,-0.7684 v 1.5368 h -1.696711 v -1.5368" />
          577  +    </g>
          578  +    <g
          579  +       id="g1769"
          580  +       transform="matrix(0.4999787,-0.49995505,0.4999787,0.49995505,-140.73822,173.71849)"
          581  +       style="stroke:url(#radialGradient2879);stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none">
          582  +      <path
          583  +         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#radialGradient1787);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
          584  +         d="m 45.103283,274.84576 v 1.72137 0.16798 h 0.335972 v -0.16798 -0.60871 h 1.381436 v 0.60871 0.16798 h 0.335972 v -0.16798 -1.72137 z m 0.335972,0.334 h 1.381436 v 0.44269 h -1.381436 z"
          585  +         id="path1755"
          586  +         inkscape:connector-curvature="0" />
          587  +      <path
          588  +         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#radialGradient1789);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
          589  +         d="m 49.271309,274.84576 v 1.72137 0.16798 h 0.335971 v -0.16798 -1.38737 h 1.381437 v 1.38737 0.16798 h 0.333996 v -0.16798 -1.72137 z"
          590  +         id="path1757"
          591  +         inkscape:connector-curvature="0" />
          592  +      <path
          593  +         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#radialGradient2901);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
          594  +         d="m 53.439335,274.84576 v 1.72137 0.16798 h 0.335971 v -0.16798 -1.38737 h 0.521745 v 1.36958 0.16798 h 0.335971 v -0.16798 -1.36958 h 0.521744 v 1.38737 0.16798 h 0.335972 v -0.16798 -1.72137 z"
          595  +         id="path1759"
          596  +         inkscape:connector-curvature="0" />
          597  +    </g>
          598  +    <rect
          599  +       style="opacity:1;vector-effect:none;fill:url(#radialGradient904);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
          600  +       id="rect896"
          601  +       width="15.957437"
          602  +       height="15.957437"
          603  +       x="202.37122"
          604  +       y="184.41054"
          605  +       transform="rotate(45)" />
          606  +  </g>
          607  +</svg>

Modified static/style.scss from [d08e77f56c] to [2db0a5b279].

    33     33   	text-underline-offset: 0.1em;
    34     34   	&:hover, &:focus {
    35     35   		color: white;
    36     36   		text-shadow: 0 0 15px tone(20%);
    37     37   		text-decoration-color: tone(10%,-0.1);
    38     38   		outline: none;
    39     39   	}
           40  +	u {
           41  +		text-decoration-color: tone(10%,-0.3);
           42  +	}
    40     43   }
    41     44   a[href^="//"],
    42     45   a[href^="http://"],
    43     46   a[href^="https://"] { // external link
    44     47   	&:hover::after {
    45     48   		color: black;
    46     49   		background-color: white;
................................................................................
    70     73   
    71     74   .button, a[href].button { // 🙄
    72     75   	@extend %sans;
    73     76   	font-size: 14pt;
    74     77   	box-sizing: border-box;
    75     78   	padding: 0.1in 0.2in;
    76     79   	border: 1px solid black;
           80  +	border-bottom: 2px solid black;
    77     81   	color: otone(25%);
    78     82   	text-shadow: 1px 1px black;
    79     83   	text-decoration: none;
    80     84   	text-align: center;
    81     85   	cursor: default;
    82     86   	user-select: none;
    83     87   	-webkit-user-drag: none;
    84     88   	-webkit-app-region: no-drag;
    85     89   	--icon: url(/s/heart.webp);
    86     90   	background-image: linear-gradient(to bottom,
    87         -		otone(-47%),
    88         -		otone(-50%) 15%,
    89         -		otone(-50%) 75%,
    90         -		otone(-53%)
           91  +		otone(-41%),
           92  +		otone(-43%) 15%,
           93  +		otone(-46%) 75%,
           94  +		otone(-50%)
    91     95   	);
    92     96   	&:hover, &:focus {
    93     97   		@extend %glow;
    94     98   		outline: none;
    95     99   		color: tone(-55%);
    96    100   		text-shadow: none;
    97    101   		background: linear-gradient(to bottom,
    98    102   			otone(-27%),
    99    103   			otone(-30%) 15%,
   100         -			otone(-30%) 75%,
          104  +			otone(-32%) 75%,
   101    105   			otone(-35%)
   102    106   		);
   103    107   	}
   104    108   	&:active {
   105    109   		color: black;
   106    110   		padding-bottom: calc(0.1in - 2px);
   107    111   		padding-top: calc(0.1in + 2px);
          112  +		border: 1px solid black;
          113  +		border-top: 2px solid black;
   108    114   		background: linear-gradient(to top,
   109    115   			otone(-25%),
   110    116   			otone(-30%) 15%,
   111         -			otone(-30%) 75%,
          117  +			otone(-32%) 75%,
   112    118   			otone(-35%)
   113    119   		);
   114    120   	}
   115    121   }
   116    122   
   117    123   button { @extend .button;
   118         -	&:first-of-type {
          124  +	form > &:first-of-type, menu > &:first-of-type {
   119    125   		@extend .button;
   120    126   		color: white;
   121    127   		box-shadow: inset 0 1px  otone(-25%),
   122    128   		            inset 0 -1px otone(-50%);
   123    129   		background: linear-gradient(to bottom,
   124    130   			otone(-35%),
   125    131   			otone(-40%) 15%,
   126         -			otone(-40%) 75%,
          132  +			otone(-43%) 75%,
   127    133   			otone(-45%)
   128    134   		);
   129    135   		&:hover, &:focus {
   130    136   			box-shadow: inset 0 1px  otone(-15%),
   131    137   						inset 0 -1px otone(-40%);
   132    138   		}
   133    139   		&:active {
   134    140   			box-shadow: inset 0 1px  otone(-50%),
   135    141   						inset 0 -1px otone(-25%);
   136    142   			background: linear-gradient(to top,
   137    143   				otone(-30%),
   138    144   				otone(-35%) 15%,
   139         -				otone(-35%) 75%,
          145  +				otone(-38%) 75%,
   140    146   				otone(-40%)
   141    147   			);
   142    148   		}
   143    149   	}
   144    150   	//&:hover { font-weight: bold; }
   145    151   }
   146    152   
................................................................................
   209    215   		}
   210    216   		nav {
   211    217   			all: unset;
   212    218   			display: flex;
   213    219   			justify-content: flex-end;
   214    220   			align-items: center;
   215    221   			grid-column: 2/3; grid-row: 1/2;
   216         -			.ident {
          222  +			hr {
          223  +				width: 1px;
          224  +				height: 1.5em;
          225  +				border: none;
          226  +				border-left: 1px solid tone(-40%);
          227  +				margin-left: 0.5em;
          228  +			}
          229  +			a[href].ident {
   217    230   				color: tone(-20%);
   218    231   				margin-left: 0.2em;
   219         -				border-left: 1px solid tone(-40%);
   220    232   				padding-left: 0.5em;
          233  +				&::before {
          234  +					content: '@';
          235  +					display: inline-block; // remove underline - i don't want to know why this works
          236  +					opacity: 0.7;
          237  +				}
   221    238   			}
   222    239   			> a[href] {
   223    240   				display: block;
   224    241   				padding: 0.25in 0.10in;
   225    242   				//padding: calc((25% - 1em)/2) 0.15in;
   226    243   				&, &::after { transition: 0.3s; }
   227    244   				text-shadow: 1px 1px 1px black;
   228    245   				&:hover{
   229    246   					transform: scale(1.2);
   230    247   				}
   231    248   			}
   232         -			> a[href].bell {
   233         -				content: url(/s/bell.svg);
          249  +			> a[href].bell, a[href].gear {
   234    250   				height: 2em;
   235         -				padding: 0.125in 0.10in;
          251  +				padding: 0.125in 0.05in;
   236    252   				filter: drop-shadow(1px 1px 3px tone(-5%));
   237    253   				&:hover {
   238    254   					filter: drop-shadow(1px 1px 3px tone(-5%))
   239    255   						drop-shadow(0 0 10px tone(-5%));
   240    256   				}
   241    257   			}
          258  +			> a[href].bell { content: url(/s/bell.svg); }
          259  +			> a[href].gear { content: url(/s/gear.svg); }
   242    260   		}
   243    261   	}
   244    262   }
   245    263   
   246    264   main {
   247    265   	@extend %content;
   248    266   	display: block;
................................................................................
   285    303   				border-radius: 5px;
   286    304   				border: 1px solid transparent;
   287    305   				&.on {
   288    306   					background-color: tone(-30%, -0.7);
   289    307   					box-shadow: 0 0 10px tone(-30%);
   290    308   					border-color: tone(-20%);
   291    309   				}
   292         -				> button, > p { display: block; }
          310  +				> button, > p, > a[href] { display: block; }
   293    311   				> p { text-align: center; font-size: 80%; margin: 0; margin-top: 0.1in; }
   294         -				> button {
          312  +				> button, > a[href] {
          313  +					width: max-content;
   295    314   					margin: auto;
   296    315   				}
   297    316   				&:last-child:nth-child(2n-1) {
   298    317   					grid-column: 1/3;
   299    318   				}
   300    319   			}
   301    320   
................................................................................
   479    498   	padding: 0.1in;
   480    499   	> img { grid-column: 1/2; grid-row: 1/3; width: 1in; height: 1in;}
   481    500   	> textarea {
   482    501   		grid-column: 2/6; grid-row: 1/2; height: 3in;
   483    502   		resize: vertical;
   484    503   		margin-bottom: 0.08in;
   485    504   	}
   486         -	> input[name="acl"] { grid-column: 2/3; grid-row: 2/3; }
   487    505   	> button[value="post"] { grid-column: 5/6; grid-row: 2/3; }
   488    506   	> button[value="attach"] { grid-column: 4/5; grid-row: 2/3; }
   489    507   	a.help[href] { margin-right: 0.05in }
   490    508   }
          509  +
          510  +input[name="acl"] { grid-column: 2/3; grid-row: 2/3; }
   491    511   
   492    512   a.help[href] {
   493    513   	display: block;
   494    514   	text-align: center;
   495    515   	padding: 0.09in 0.2in;
   496    516   	background: tone(-40%);
   497    517   	border: 1px solid black;
................................................................................
   694    714   	> a[href] {
   695    715   		display: block;
   696    716   		text-align: left;
   697    717   	}
   698    718   	> a[href] + a[href] {
   699    719   		border-top: none;
   700    720   	}
          721  +	.button, .button:active {
          722  +		border: 1px solid black;
          723  +	}
   701    724   	hr {
   702    725   		border: none;
   703    726   	}
   704    727   }
   705    728   
   706    729   menu { all: unset; display: block; }
   707    730   body.conf main {
................................................................................
   887    910   .color-picker {
   888    911   	/* implemented using javascript, alas */
   889    912   	@extend %box;
   890    913   	label { text-shadow: 1px 1px black; }
   891    914   	padding: 0.1in;
   892    915   }
   893    916   
   894         -ul.user-list {
          917  +ul.directory {
   895    918   	list-style-type: none;
   896    919   	margin: 0.5em 0;
   897    920   	padding: 0;
   898    921   	box-shadow: 0 0 10px -3px black inset;
   899    922   	border: 1px solid tone(-50%);
   900    923   	li {
   901    924   		background-color: tone(-20%, -0.8);
................................................................................
   921    944   		text-align: center;
   922    945   		padding: 0.3em 0;
   923    946   		margin: 0.2em 0.1em;
   924    947   		cursor: default;
   925    948   	}
   926    949   }
   927    950   
   928         -.button, a[href] {
   929         -	.neg { --co:  30 }
   930         -	.pos { --co: -30 }
   931         -}
          951  +.neg { --co:  30 !important }
          952  +.pos { --co: -30 !important }
   932    953   
   933    954   .pick-list {
   934    955   	display: flex;
   935    956   	flex-flow: row wrap;
   936    957   	padding: 0.1in;
   937    958   	background-color: tone(-50%);
   938    959   	border: 1px solid tone(-53%);
................................................................................
  1210   1231   			padding: 0 0.2in;
  1211   1232   			max-height: calc(100vh - 3in);
  1212   1233   			overflow-y: scroll;
  1213   1234   			text-align: justify;
  1214   1235   		}
  1215   1236   	}
  1216   1237   }
         1238  +
         1239  +div.kind-picker {
         1240  +	text-align: right;
         1241  +	font-style: italic;
         1242  +	padding: 0.2em;
         1243  +}
         1244  +
         1245  +body.timeline {
         1246  +	menu.circles {
         1247  +		@extend %box;
         1248  +		width: 3in;
         1249  +		margin-right: 0;
         1250  +		margin-left: auto;
         1251  +		padding: 0.1in;
         1252  +		a[href] {
         1253  +			transition: 0.4s;
         1254  +			text-align: center;
         1255  +			display: block;
         1256  +			padding: 0.4em;
         1257  +			background: linear-gradient(to right, tone(-30%, -0.6), transparent) no-repeat;
         1258  +			background-position: -3in 0;
         1259  +			text-decoration: none;
         1260  +			& + a[href] {
         1261  +				border-bottom: 1px solid tone(-40%);
         1262  +				border-image: linear-gradient(to right, transparent, tone(-45%), transparent) 1 0 0 / 1px;
         1263  +			}
         1264  +			&:hover {
         1265  +				background-position: 0 0;
         1266  +			}
         1267  +		}
         1268  +	}
         1269  +}

Modified store.t from [8e00371af1] to [0871cb7e85].

    11     11   		'follow',
    12     12   		'sub', -- get a notification for every post
    13     13   		'mute', -- posts will be completely hidden at all times
    14     14   		'block', -- no interactions will be permitted, but posts will remain visible
    15     15   		'silence', -- messages will not be accepted
    16     16   		'collapse', -- posts will be collapsed by default
    17     17   		'disemvowel', -- posts will be ritually humiliated, but shown
           18  +		'attenuate', -- user's retweets will not be shown
    18     19   		'avoid', -- posts will be kept out of the timeline but will show on users' posts and in conversations
    19     20   		'exclude', -- own posts will not be visible to this user
    20     21   	};
    21     22   	credset = lib.set {
    22     23   		'pw', 'otp', 'challenge', 'trust'
    23     24   	};
    24     25   	privset = lib.set {
................................................................................
   254    255   	owner: uint64
   255    256   	desc: str
   256    257   	folder: str
   257    258   	mime: str
   258    259   	url: str
   259    260   }
   260    261   
   261         -m.user_conf_funcs = function(be,n,ty,rty,rty2)
          262  +m.user_conf_funcs = function(be,n,mem,ty,rty,rty2)
   262    263   	rty = rty or ty
   263    264   	local gt
   264         -	if not rty2 -- what the fuck?
   265         -		then gt = {&m.source, uint64, rawstring} -> rty;
   266         -		else gt = {&m.source, uint64, rawstring} -> {rty, rty2};
          265  +	if not mem then
          266  +		if not rty2 -- what the fuck?
          267  +			then gt = {&m.source, uint64, lib.str.t} -> rty;
          268  +			else gt = {&m.source, uint64, lib.str.t} -> {rty, rty2};
          269  +		end
          270  +	else
          271  +		if not rty2 -- what the fuck?
          272  +			then gt = {&m.source, &lib.mem.pool, uint64, lib.str.t} -> rty;
          273  +			else gt = {&m.source, &lib.mem.pool, uint64, lib.str.t} -> {rty, rty2};
          274  +		end
   267    275   	end
   268    276   	for k, t in pairs {
   269         -		enum = {&m.source, uint64, rawstring} -> lib.mem.ptr(rty);
          277  +		enum = {&m.source, &lib.mem.pool, uint64, lib.str.t} -> lib.mem.ptr(rty);
   270    278   		get = gt;
   271         -		set = {&m.source, uint64, rawstring, ty} -> {};
   272         -		reset = {&m.source, uint64, rawstring} -> {};
          279  +		set = {&m.source, uint64, lib.str.t, ty} -> {};
          280  +		reset = {&m.source, uint64, lib.str.t} -> {};
   273    281   	} do
   274    282   		be.entries[#be.entries+1] = {
   275    283   			field = 'actor_conf_'..n..'_'..k, type = t
   276    284   		}
   277    285   	end
   278    286   end
   279    287   
................................................................................
   345    353   	comment: str
   346    354   	netmask: m.inet
   347    355   	privs: m.privset
   348    356   	blacklist: bool
   349    357   }
   350    358   
   351    359   -- backends only handle content on the local server
          360  +local pstring = lib.str.t
   352    361   struct m.backend { id: rawstring
   353    362   	open: &m.source -> &opaque
   354    363   	close: &m.source -> {}
   355    364   	dbsetup: &m.source -> bool -- creates the schema needed to call conprep (called only once per database e.g. with `parsav db init`)
   356    365   	conprep: {&m.source, m.prepmode.t} -> {} -- prepares queries and similar tasks that require the schema to already be in place
   357    366   	obliterate_everything: &m.source -> bool -- wipes everything parsav-related out of the database
   358    367   
................................................................................
   361    370   	-- these two functions are special, in that they should be called
   362    371   	-- directly on a specific backend, rather than passed down to the
   363    372   	-- backends by the server; that is pathological behavior that will
   364    373   	-- not have the desired effect
   365    374   
   366    375   	server_setup_self: {&m.source, rawstring, lib.mem.ptr(uint8)} -> {}
   367    376   
   368         -	conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
   369         -	conf_set: {&m.source, rawstring, rawstring} -> {}
          377  +	conf_get: {&m.source, lib.str.t} -> lib.mem.ptr(int8)
          378  +	conf_set: {&m.source, lib.str.t, lib.str.t} -> {}
   370    379   	conf_reset: {&m.source, rawstring} -> {}
   371    380   
   372    381   	actor_create: {&m.source, &m.actor} -> uint64
   373    382   	actor_save: {&m.source, &m.actor} -> {}
   374    383   	actor_save_privs: {&m.source, &m.actor} -> {}
   375    384   	actor_purge_uid: {&m.source, uint64} -> {}
   376    385   	actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor)
................................................................................
   471    480   			-- emoji: pstring (null to delete previous reaction, otherwise adds/changes)
   472    481   	post_act_cancel: {&m.source, uint64} -> {}
   473    482   	post_liked_uid: {&m.source, uint64, uint64} -> bool
   474    483   	post_reacted_uid: {&m.source, uint64, uint64} -> bool
   475    484   	post_act_fetch_notice: {&m.source, uint64} -> m.notice
   476    485   
   477    486   	circle_search:  {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(m.circle)
   478         -	circle_create: {&m.source, uint64, pstring} -> {}
          487  +	circle_create: {&m.source, uint64, lib.str.t} -> uint64
   479    488   	circle_destroy: {&m.source, uint64, uint64} -> {}
   480         -	circle_members_fetch_cid:  {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(uint64)
          489  +	circle_members_fetch_cid:  {&m.source, &lib.mem.pool, uint64} -> lib.mem.ptr(uint64)
   481    490   	circle_members_fetch_name: {&m.source, &lib.mem.pool, uint64, pstring} -> lib.mem.ptr(uint64)
   482    491   	circle_members_add_uid: {&m.source, uint64, uint64} -> {}
   483    492   	circle_members_del_uid: {&m.source, uint64, uint64} -> {}
          493  +	circle_memberships_uid: {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(m.circle)
   484    494   
   485    495   	thread_latest_arrival_calc: {&m.source, uint64} -> m.timepoint
   486    496   
   487    497   	artifact_instantiate: {&m.source, lib.mem.ptr(uint8), lib.mem.ptr(int8)} -> uint64
   488    498   		-- instantiate an artifact in the database, either installing a new
   489    499   		-- artifact or returning the id of an existing artifact with the same hash
   490    500   			-- artifact: bytea
................................................................................
   539    549   	nkvd_sanction_vacate: {&m.source, uint64} -> {}
   540    550   	nkvd_sanction_enum_target: {&m.source, uint64} -> {}
   541    551   	nkvd_sanction_enum_issuer: {&m.source, uint64} -> {}
   542    552   	nkvd_sanction_review: {&m.source, m.timepoint} -> {}
   543    553   
   544    554   	timeline_actor_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.lstptr(m.post)
   545    555   	timeline_instance_fetch: {&m.source, m.range} -> lib.mem.lstptr(m.post)
          556  +	timeline_circle_fetch: {&m.source, uint64, m.range} -> lib.mem.lstptr(m.post)
   546    557   }
   547    558   
   548         -m.user_conf_funcs(m.backend, 'str', rawstring, lib.mem.ptr(int8))
   549         -m.user_conf_funcs(m.backend, 'int', intptr, intptr, bool)
          559  +m.user_conf_funcs(m.backend, 'str', true,  lib.str.t, lib.str.t)
          560  +m.user_conf_funcs(m.backend, 'int', false, intptr, intptr, bool)
   550    561   
   551    562   struct m.source {
   552    563   	backend: &m.backend
   553    564   	id: lib.mem.ptr(int8)
   554    565   	handle: &opaque
   555    566   	string: lib.mem.ptr(int8)
   556    567   }

Modified tpl.t from [586a17ee3f] to [7216746946].

    34     34   	str = str:gsub('%s+[\n$]','')
    35     35   	str = str:gsub('\n','')
    36     36   	str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed
    37     37   	str = str:gsub(tplchar .. '%?([-%w]+)', function(file)
    38     38   		if not docs[file] then docs[file] = data.doc[file] end
    39     39   		return string.format('<a href="#help-%s" class="help">?</a>', file)
    40     40   	end)
    41         -	for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!]?)(%w+)()') do
           41  +	for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!]?)([-a-zA-Z0-9_]+)()') do
    42     42   		if string.sub(str,start-1,start-1) ~= '\\' then
    43     43   			segs[#segs+1] = string.sub(str,last,start-1)
    44         -			fields[#segs] = { key = key, mode = (mode ~= '' and mode or nil) }
           44  +			fields[#segs] = { key = key:gsub('-','_'), mode = (mode ~= '' and mode or nil) }
    45     45   			last = stop
    46     46   		end
    47     47   	end
    48     48   	segs[#segs+1] = string.sub(str,last)
    49     49   
    50     50   	for i, s in ipairs(segs) do
    51     51   		segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o)

Added view/conf-circles.tpl version [9128c77c5c].

            1  +<ul class="directory">
            2  +	@circles
            3  +</ul>
            4  +
            5  +<details@newattr>
            6  +	<summary>create new circle</summary>
            7  +	<form method="post">
            8  +		<div class="elem">
            9  +			<label for="name">circle name</label>
           10  +			<input type="text" id="name" name="name" placeholder="dorks">
           11  +		</div>
           12  +		<button name="act" value="create">create</button>
           13  +	</form>
           14  +</details>

Modified view/conf-profile.tpl from [48e88ad45a] to [7ff9d56e20].

     1      1   <form method="post">
     2      2   	<div class="elem"><label>handle</label> <div class="txtbox">@!handle</div></div>
     3      3   	<div class="elem"><label for="nym">display name</label> <input type="text" name="nym" id="nym" placeholder="j. random poster" value="@:nym"></div>
     4      4   	<div class="elem"><label for="bio">bio</label><textarea name="bio" id="bio" placeholder="tall, dark, and mysterious">@!bio</textarea></div>
     5      5   	<div class="elem color-picker"><label for="hue">accent</label><input type="range" min="0" max="360" value="@hue" name="hue" id="hue" data-color-pick></div>
            6  +	<div class="elem-group">
            7  +		<div class="elem">
            8  +			<label for="acl-follow">who can follow me</label>
            9  +			<input type="text" class="acl" name="acl-follow"
           10  +				   id="acl-follow" placeholder="allow local" value="@:acl-follow">
           11  +		</div>
           12  +		<div class="elem">
           13  +			<label for="acl-follow-req">who can request to follow me</label>
           14  +			<input type="text" class="acl" name="acl-follow-req"
           15  +				   id="acl-follow-req" placeholder="deny +dorks" value="@:acl-follow-req">
           16  +		</div>
           17  +	</div>
     6     18   	<menu class="choice vertical">
     7     19   		<button>commit all</button>
     8     20   		<button name="act" value="reset-hue">use server colors</button>
     9     21   	</menu> 
    10     22   </form>

Modified view/docskel.tpl from [e84fb6bf18] to [e2ac83ad65].

     5      5   		<link rel="stylesheet" type="text/css" href="/s/style.css">
     6      6   		<script type="text/javascript" src="/s/live.js" async></script>
     7      7   	</head>
     8      8   	<body class="@class"@attr>
     9      9   		<header><div>
    10     10   			<h1>@title</h1>
    11     11   			<nav>
    12         -				<a accesskey="i" href="/instance">instance</a>
           12  +				<a accesskey="i" href="/instance"><u>i</u>nstance</a>
    13     13   				@navlinks
    14     14   			</nav>
    15     15   		</div></header>
    16     16   		<main>
    17     17   			@body
    18     18   		</main>
    19     19   	</body>
    20     20   </html>

Modified view/load.lua from [3d222d2b19] to [bd867ef191].

     6      6   local sources = {
     7      7   	'docskel';
     8      8   	'confirm';
     9      9   	'tweet';
    10     10   	'profile';
    11     11   	'compose';
    12     12   	'notice';
           13  +	'report';
    13     14   
    14     15   	'media-gallery';
    15     16   	'media-upload';
    16     17   	'media-image';
    17     18   	'media-text';
    18     19   
    19     20   	'login-username';
    20     21   	'login-challenge';
    21     22   
    22     23   	'conf';
    23     24   	'conf-profile';
           25  +	'conf-circles';
           26  +	'conf-circle-view';
    24     27   	'conf-sec';
    25     28   	'conf-sec-credmg';
    26     29   	'conf-sec-pwnew';
    27     30   	'conf-sec-keynew';
    28     31   	'conf-user-ctl';
    29     32   }
    30     33   

Modified view/profile.tpl from [240a608504] to [ae30d737ce].

    38     38   		<button name="act" value="encircle">commit</button>
    39     39   	</details>
    40     40   	<details>
    41     41   		<summary>sanctions</summary>
    42     42   		<menu>
    43     43   			@sanctions
    44     44   			<div class="opt">
    45         -				<button class="neg" name="act" value="report">report</button>
           45  +				<a class="neg button" href="/@:xid/report">report</a>
    46     46   				<p>if this user is violating instance rules, you can report this behavior to moderation staff and ask them to take action. please do not report users simply because you dislike them; this is what the above options are for.</p>
    47     47   			</div>
    48     48   		</menu>
    49     49   	</details>
    50     50   </div></form>

Added view/report.tpl version [34643d2a6d].

            1  +<form method="post">
            2  +	<p>if you feel that this post has violated the rules of this instance, you can report it to the local moderation team and request that they take action. this may include suppressing the offending post, suspending the user, or passing complaints onwards to the offending user's instance, if remote. please explain how you believe the post violates instance rules and what, if any, action you believe is appropriate.</p>
            3  +	@badtweet
            4  +	<div class="elem">
            5  +		<label for="report">report</label>
            6  +		<textarea id="report" name="report" placeholder="this running dog is spreading counterrevolutionary agitprop amongst the people, sir! he must be dealt with by the cadres at once"></textarea>
            7  +	</div>
            8  +	<menu class="horizontal choice">
            9  +		<button>issue report</button>
           10  +		<a class="button" href="@clnk">cancel</a>
           11  +	</menu>
           12  +</form>